Compare commits

..

18 Commits

Author SHA1 Message Date
Ac_K
3fbacd0f49 ava: Rework DLC Manager, Add various fixes and cleanup (#3896)
* Fixes Everything Part.2

* Change sorting, fix remove and heading
2022-11-25 12:41:34 +01:00
dependabot[bot]
7aa6abc120 nuget: bump SharpZipLib from 1.3.3 to 1.4.1 (#3893)
Bumps [SharpZipLib](https://github.com/icsharpcode/SharpZipLib) from 1.3.3 to 1.4.1.
- [Release notes](https://github.com/icsharpcode/SharpZipLib/releases)
- [Changelog](https://github.com/icsharpcode/SharpZipLib/blob/master/docs/Changes.txt)
- [Commits](https://github.com/icsharpcode/SharpZipLib/compare/v1.3.3...v1.4.1)

---
updated-dependencies:
- dependency-name: SharpZipLib
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-24 20:30:17 +01:00
Mary-nyan
548bfd60a2 chore: Update Ryujinx.SDL2-CS to 2.24.2 (#3892)
* chore: Update Ryujinx.SDL2-CS to 2.24.2

* Disable SDL_HINT_JOYSTICK_HIDAPI_COMBINE_JOY_CONS
2022-11-24 20:13:16 +01:00
riperiperi
65778a6b78 GPU: Don't trigger uploads for redundant buffer updates (#3828)
* Initial implementation

* Actually do The Thing

* Add remark about performance to IVirtualMemoryManager
2022-11-24 15:50:15 +01:00
Mary-nyan
f4e879a1e6 Reduce usage of Marshal.PtrToStructure and Marshal.StructureToPtr (#3805)
* common: Make BinaryReaderExtensions Read & Write take unamanged types

This allows us to not rely on Marshal.PtrToStructure and Marshal.StructureToPtr for those.

* common: Make MemoryHelper Read & Write takes unamanged types

* Update Marshal.SizeOf => Unsafe.SizeOf when appropriate and start moving software applet to unmanaged types
2022-11-24 15:26:29 +01:00
Ac_K
a1ddaa2736 ui: Fixes disposing on GTK/Avalonia and Firmware Messages on Avalonia (#3885)
* ui: Only wait on _exitEvent when MainLoop is active under GTK

This fixes a dispose issue under Horizon/GTK, we don't check if the ApplicationClient is null so it throw NCE. We don't check if the main loop is active and waiting an event which is set in the main loop... So that could lead to a freeze.

Everything works fine in GTK now.

Related issue: https://github.com/Ryujinx/Ryujinx/issues/3873

As a side note, same kind of issue appear in Avalonia UI too. Firmware's popup doesn't show anything and the emulator just freeze.

* TSRBerry's change

Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>

* Fix Avalonia crashing/freezing

* Add Avalonia OpenGL fixes

* Fix firmware popup on windows

* Fixes everything

* Add _initialized bool to VulkanRenderer and OpenGL Window

Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>
2022-11-24 15:08:27 +01:00
Mary-nyan
008286b79f Ryujinx.Ava: Add missing redefinition of app name (#3890)
Before this, Ryujinx would possibly report as "Avalonia Application".
2022-11-24 14:52:39 +01:00
gdkchan
a0c77f8d11 Fix NRE on Avalonia for error applets with unknown error message (#3888) 2022-11-24 09:31:00 +01:00
riperiperi
ece36b274d GAL: Send all buffer assignments at once rather than individually (#3881)
* GAL: Send all buffer assignments at once rather than individually

The `(int first, BufferRange[] ranges)` method call has very significant performance implications when the bindings are spread out, which they generally always are in Vulkan. This change makes it so that these methods are only called a maximum of one time per draw.

Significantly improves GPU thread performance in Pokemon Scarlet/Violet.

* Address Feedback

Removed SetUniformBuffers(int first, ReadOnlySpan<BufferRange> buffers)
2022-11-24 07:50:59 +00:00
riperiperi
f3cc2e5703 GPU: Access non-prefetch command buffers directly (#3882)
* GPU: Access non-prefetch command buffers directly

Saves allocating new arrays for them constantly - they can be quite small so it can be very wasteful. About 0.4% of GPU thread in SMO, but was a bit higher in S/V when I checked.

Assumes that non-prefetch command buffers won't be randomly clobbered before they finish executing, though that's probably a safe bet.

* Small change while I'm here

* Address feedback
2022-11-24 01:56:55 +00:00
riperiperi
5a39d3c4a1 GPU: Relax locking on Buffer Cache (#3883)
I did this on ncbuffer2 when we were using it for LDN 3, but I noticed that it can apply to the current buffer manager too, and it's an easy performance win.

The only buffer access that can come from another thread is the overlap search for buffers that have been unmapped. Everything else, including modifications, come from the main GPU thread. That means we only need to lock the range list when it's being modified, as that's the only time where we'll cause a race with the unmapped handler.

This has a significant performance improvements in situations where FIFO is high, like the other two PRs. Joined together they give a nice boost (73.6 master -> 79 -> 83 fps in SMO).
2022-11-24 01:41:16 +00:00
dependabot[bot]
cc51a03af9 nuget: bump Avalonia from 0.10.15 to 0.10.18 (#3817)
Bumps [Avalonia](https://github.com/AvaloniaUI/Avalonia) from 0.10.15 to 0.10.18.
- [Release notes](https://github.com/AvaloniaUI/Avalonia/releases)
- [Commits](https://github.com/AvaloniaUI/Avalonia/compare/0.10.15...0.10.18)

---
updated-dependencies:
- dependency-name: Avalonia
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-24 01:26:53 +00:00
Ac_K
567c64e149 ava: Fix JsonSerializer warnings (#3884)
Since we move to .NET7, JsonSerializer now needs to have explicit options as arguments, which leads to some warnings in Avalonia project. This is fixed by using our `JsonHelper` class.
2022-11-23 17:55:26 +00:00
Alex Barney
36f00985d3 Update to LibHac 0.17.0 (#3878)
* Update to LibHac 0.17.0

* Don't clear SD card saves when starting the emulator

This was an old workaround for errors that happened when a user's SD card encryption seed changed. SD card saves have been unencrypted for over a year, so we should be fine to remove the workaround.
2022-11-23 18:32:35 +01:00
WilliamWsyHK
748d87adcc Stub IFriendService: 1 (Cancel) (#3841)
* Add friend/Cancel. Closes #3824

* Update according to review comments.

* Add comment base on request

* Update Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/IFriendService.cs

Co-authored-by: Ac_K <Acoustik666@gmail.com>
2022-11-23 16:25:49 +01:00
Emmanuel Hansen
0fd47ff490 remove redundant logs (#3877) 2022-11-23 11:28:46 +01:00
gdkchan
f088c3d344 Do not update shader state for DrawTextures (#3876) 2022-11-21 18:16:00 +01:00
TSRBerry
905a191e28 Use upstream unicorn for Ryujinx.Tests.Unicorn (#3771)
* unicorn: Add modified ver of unicorns const gen

* unicorn: Use upstream consts

These consts were generated from the dev branch of unicorn

* unicorn: Split common consts into multiple enums

* unicorn: Remove arch prefix from consts

* unicorn: Add new windows dll

Windows 10 - MSVC x64 shared build

* unicorn: Use absolute path for const generation

* unicorn: Remove fspcr patch

* unicorn: Fix using the wrong file extension

For some reason _NativeLibraryExtension evaluates to ".so" even on Windows.

* unicorn: Add linux shared object again

* unicron: Add DllImportResolver

* unicorn: Try to import unicorn using an absolute path

* unicorn: Add clean target

* unicorn: Replace IsUnicornAvailable() methods

* unicorn: Skip tests instead of silently passing them if unicorn is missing

* unicorn: Write error message to stderr

* unicorn: Make Interface static

* unicron: Include prefixed unicorn libs (libunicorn.so)

Co-authored-by: merry <git@mary.rs>

* unicorn: Add lib prefix to shared object for linux

Co-authored-by: merry <git@mary.rs>
2022-11-20 20:18:21 +01:00
100 changed files with 2053 additions and 1420 deletions

View File

@@ -21,6 +21,8 @@ namespace Ryujinx.Ava
{
public override void Initialize()
{
Name = $"Ryujinx {Program.Version}";
AvaloniaXamlLoader.Load(this);
}

View File

@@ -60,7 +60,7 @@ namespace Ryujinx.Ava
private const float VolumeDelta = 0.05f;
private static readonly Cursor InvisibleCursor = new Cursor(StandardCursorType.None);
private static readonly Cursor InvisibleCursor = new Cursor(StandardCursorType.None);
private readonly long _ticksPerFrame;
private readonly Stopwatch _chrono;
@@ -349,7 +349,10 @@ namespace Ryujinx.Ava
_isActive = false;
_renderingThread.Join();
if (_renderingThread.IsAlive)
{
_renderingThread.Join();
}
DisplaySleep.Restore();
@@ -378,7 +381,7 @@ namespace Ryujinx.Ava
_gpuCancellationTokenSource.Cancel();
_gpuCancellationTokenSource.Dispose();
_chrono.Stop();
}
@@ -393,7 +396,7 @@ namespace Ryujinx.Ava
Renderer?.MakeCurrent();
Device.DisposeGpu();
Renderer?.MakeCurrent(null);
}
@@ -417,7 +420,6 @@ namespace Ryujinx.Ava
public async Task<bool> LoadGuestApplication()
{
InitializeSwitchInstance();
MainWindow.UpdateGraphicsConfig();
SystemVersion firmwareVersion = ContentManager.GetCurrentFirmwareVersion();
@@ -428,17 +430,16 @@ namespace Ryujinx.Ava
{
if (userError == UserError.NoFirmware)
{
string message = string.Format(LocaleManager.Instance["DialogFirmwareInstallEmbeddedMessage"],
firmwareVersion.VersionString);
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(
LocaleManager.Instance["DialogFirmwareNoFirmwareInstalledMessage"], message,
LocaleManager.Instance["InputDialogYes"], LocaleManager.Instance["InputDialogNo"], "");
LocaleManager.Instance["DialogFirmwareNoFirmwareInstalledMessage"],
string.Format(LocaleManager.Instance["DialogFirmwareInstallEmbeddedMessage"], firmwareVersion.VersionString),
LocaleManager.Instance["InputDialogYes"],
LocaleManager.Instance["InputDialogNo"],
"");
if (result != UserResult.Yes)
{
Dispatcher.UIThread.Post(async () => await
UserErrorDialog.ShowUserErrorDialog(userError, _parent));
await UserErrorDialog.ShowUserErrorDialog(userError, _parent);
Device.Dispose();
return false;
@@ -447,8 +448,7 @@ namespace Ryujinx.Ava
if (!SetupValidator.TryFixStartApplication(ContentManager, ApplicationPath, userError, out _))
{
Dispatcher.UIThread.Post(async () => await
UserErrorDialog.ShowUserErrorDialog(userError, _parent));
await UserErrorDialog.ShowUserErrorDialog(userError, _parent);
Device.Dispose();
return false;
@@ -461,11 +461,9 @@ namespace Ryujinx.Ava
_parent.RefreshFirmwareStatus();
string message = string.Format(LocaleManager.Instance["DialogFirmwareInstallEmbeddedSuccessMessage"], firmwareVersion.VersionString);
await ContentDialogHelper.CreateInfoDialog(
string.Format(LocaleManager.Instance["DialogFirmwareInstalledMessage"], firmwareVersion.VersionString),
message,
string.Format(LocaleManager.Instance["DialogFirmwareInstallEmbeddedSuccessMessage"], firmwareVersion.VersionString),
LocaleManager.Instance["InputDialogOk"],
"",
LocaleManager.Instance["RyujinxInfo"]);
@@ -473,9 +471,7 @@ namespace Ryujinx.Ava
}
else
{
Dispatcher.UIThread.Post(async () => await
UserErrorDialog.ShowUserErrorDialog(userError, _parent));
await UserErrorDialog.ShowUserErrorDialog(userError, _parent);
Device.Dispose();
return false;
@@ -514,7 +510,7 @@ namespace Ryujinx.Ava
}
else if (File.Exists(ApplicationPath))
{
switch (System.IO.Path.GetExtension(ApplicationPath).ToLowerInvariant())
switch (Path.GetExtension(ApplicationPath).ToLowerInvariant())
{
case ".xci":
{
@@ -602,7 +598,7 @@ namespace Ryujinx.Ava
if (Renderer.IsVulkan)
{
string preferredGpu = ConfigurationState.Instance.Graphics.PreferredGpu.Value;
renderer = new VulkanRenderer(Renderer.CreateVulkanSurface, VulkanHelper.GetRequiredInstanceExtensions, preferredGpu);
}
else

View File

@@ -410,6 +410,8 @@
"DlcManagerTableHeadingContainerPathLabel": "Container Path",
"DlcManagerTableHeadingFullPathLabel": "Full Path",
"DlcManagerRemoveAllButton": "Remove All",
"DlcManagerEnableAllButton": "Enable All",
"DlcManagerDisableAllButton": "Disable All",
"MenuBarOptionsChangeLanguage": "Change Language",
"CommonSort": "Sort",
"CommonShowNames": "Show Names",
@@ -567,7 +569,7 @@
"DlcWindowTitle": "Manage Game DLC",
"UpdateWindowTitle": "Manage Game Updates",
"CheatWindowHeading": "Cheats Available for {0} [{1}]",
"DlcWindowHeading": "DLC Available for {0} [{1}]",
"DlcWindowHeading": "{0} Downloadable Content(s) available for {1} ({2})",
"UserProfilesEditProfile": "Edit Selected",
"Cancel": "Cancel",
"Save": "Save",

View File

@@ -1,5 +1,6 @@
using Ryujinx.Ava.Ui.ViewModels;
using Ryujinx.Common;
using Ryujinx.Common.Utilities;
using Ryujinx.Ui.Common.Configuration;
using System.Collections.Concurrent;
using System.Collections.Generic;
@@ -93,7 +94,7 @@ namespace Ryujinx.Ava.Common.Locale
return;
}
var strings = JsonSerializer.Deserialize<Dictionary<string, string>>(languageJson);
var strings = JsonHelper.Deserialize<Dictionary<string, string>>(languageJson);
foreach (var item in strings)
{

View File

@@ -23,19 +23,18 @@ namespace Ryujinx.Ava
{
internal class Program
{
public static double WindowScaleFactor { get; set; }
public static double ActualScaleFactor { get; set; }
public static string Version { get; private set; }
public static string ConfigurationPath { get; private set; }
public static bool PreviewerDetached { get; private set; }
public static RenderTimer RenderTimer { get; private set; }
public static double WindowScaleFactor { get; set; }
public static double ActualScaleFactor { get; set; }
public static string Version { get; private set; }
public static string ConfigurationPath { get; private set; }
public static bool PreviewerDetached { get; private set; }
public static RenderTimer RenderTimer { get; private set; }
[DllImport("user32.dll", SetLastError = true)]
public static extern int MessageBoxA(IntPtr hWnd, string text, string caption, uint type);
private const uint MB_ICONWARNING = 0x30;
private const int BaseDpi = 96;
private const int BaseDpi = 96;
public static void Main(string[] args)
{
@@ -43,7 +42,7 @@ namespace Ryujinx.Ava
if (OperatingSystem.IsWindows() && !OperatingSystem.IsWindowsVersionAtLeast(10, 0, 17134))
{
MessageBoxA(IntPtr.Zero, "You are running an outdated version of Windows.\n\nStarting on June 1st 2022, Ryujinx will only support Windows 10 1803 and newer.\n", $"Ryujinx {Version}", MB_ICONWARNING);
_ = MessageBoxA(IntPtr.Zero, "You are running an outdated version of Windows.\n\nStarting on June 1st 2022, Ryujinx will only support Windows 10 1803 and newer.\n", $"Ryujinx {Version}", MB_ICONWARNING);
}
PreviewerDetached = true;
@@ -64,16 +63,16 @@ namespace Ryujinx.Ava
.With(new X11PlatformOptions
{
EnableMultiTouch = true,
EnableIme = true,
UseEGL = false,
UseGpu = false
EnableIme = true,
UseEGL = false,
UseGpu = false
})
.With(new Win32PlatformOptions
{
EnableMultitouch = true,
UseWgl = false,
AllowEglInitialization = false,
CompositionBackdropCornerRadius = 8f,
EnableMultitouch = true,
UseWgl = false,
AllowEglInitialization = false,
CompositionBackdropCornerRadius = 8.0f,
})
.UseSkia()
.AfterSetup(_ =>
@@ -122,12 +121,10 @@ namespace Ryujinx.Ava
PrintSystemInfo();
// Enable OGL multithreading on the driver, when available.
BackendThreading threadingMode = ConfigurationState.Instance.Graphics.BackendThreading;
DriverUtilities.ToggleOGLThreading(threadingMode == BackendThreading.Off);
DriverUtilities.ToggleOGLThreading(ConfigurationState.Instance.Graphics.BackendThreading == BackendThreading.Off);
// Check if keys exists.
bool hasSystemProdKeys = File.Exists(Path.Combine(AppDataManager.KeysDirPath, "prod.keys"));
if (!hasSystemProdKeys)
if (!File.Exists(Path.Combine(AppDataManager.KeysDirPath, "prod.keys")))
{
if (!(AppDataManager.Mode == AppDataManager.LaunchMode.UserProfile && File.Exists(Path.Combine(AppDataManager.KeysDirPathUser, "prod.keys"))))
{
@@ -143,7 +140,7 @@ namespace Ryujinx.Ava
public static void ReloadConfig()
{
string localConfigurationPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json");
string localConfigurationPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json");
string appDataConfigurationPath = Path.Combine(AppDataManager.BaseDirPath, "Config.json");
// Now load the configuration as the other subsystems are now registered
@@ -197,8 +194,7 @@ namespace Ryujinx.Ava
Logger.Notice.Print(LogClass.Application, $"Ryujinx Version: {Version}");
SystemInfo.Gather().Print();
var enabledLogs = Logger.GetEnabledLevels();
Logger.Notice.Print(LogClass.Application, $"Logs Enabled: {(enabledLogs.Count == 0 ? "<None>" : string.Join(", ", enabledLogs))}");
Logger.Notice.Print(LogClass.Application, $"Logs Enabled: {(Logger.GetEnabledLevels().Count == 0 ? "<None>" : string.Join(", ", Logger.GetEnabledLevels()))}");
if (AppDataManager.Mode == AppDataManager.LaunchMode.Custom)
{

View File

@@ -18,7 +18,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="0.10.15" />
<PackageReference Include="Avalonia" Version="0.10.18" />
<PackageReference Include="Avalonia.Desktop" Version="0.10.15" />
<PackageReference Include="Avalonia.Diagnostics" Version="0.10.15" />
<PackageReference Include="Avalonia.Controls.DataGrid" Version="0.10.15" />
@@ -37,7 +37,7 @@
<PackageReference Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.10.1" />
<PackageReference Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.10.1" />
<PackageReference Include="SPB" Version="0.0.4-build28" />
<PackageReference Include="SharpZipLib" Version="1.3.3" />
<PackageReference Include="SharpZipLib" Version="1.4.1" />
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.4" />
</ItemGroup>

View File

@@ -57,14 +57,14 @@ namespace Ryujinx.Ava.Ui.Applet
bool opened = false;
UserResult response = await ContentDialogHelper.ShowDeferredContentDialog(_parent,
title,
message,
"",
LocaleManager.Instance["DialogOpenSettingsWindowLabel"],
"",
LocaleManager.Instance["SettingsButtonClose"],
(int)Symbol.Important,
UserResult response = await ContentDialogHelper.ShowDeferredContentDialog(_parent,
title,
message,
"",
LocaleManager.Instance["DialogOpenSettingsWindowLabel"],
"",
LocaleManager.Instance["SettingsButtonClose"],
(int)Symbol.Important,
deferEvent,
async (window) =>
{
@@ -168,7 +168,7 @@ namespace Ryujinx.Ava.Ui.Applet
object response = await msgDialog.Run();
if (response != null && buttons.Length > 1 && (int)response != buttons.Length - 1)
if (response != null && buttons != null && buttons.Length > 1 && (int)response != buttons.Length - 1)
{
showDetails = true;
}

View File

@@ -49,7 +49,7 @@ namespace Ryujinx.Ava.Ui.Controls
{
throw new PlatformNotSupportedException();
}
var flags = OpenGLContextFlags.Compat;
if (_graphicsDebugLevel != GraphicsDebugLevel.None)
{
@@ -69,12 +69,12 @@ namespace Ryujinx.Ava.Ui.Controls
public void MakeCurrent()
{
Context.MakeCurrent(_window);
Context?.MakeCurrent(_window);
}
public void MakeCurrent(NativeWindowBase window)
{
Context.MakeCurrent(window);
Context?.MakeCurrent(window);
}
public void SwapBuffers()

View File

@@ -1,8 +1,22 @@
namespace Ryujinx.Ava.Ui.Models
using Ryujinx.Ava.Ui.ViewModels;
namespace Ryujinx.Ava.Ui.Models
{
public class DownloadableContentModel
public class DownloadableContentModel : BaseModel
{
public bool Enabled { get; set; }
private bool _enabled;
public bool Enabled
{
get => _enabled;
set
{
_enabled = value;
OnPropertyChanged();
}
}
public string TitleId { get; }
public string ContainerPath { get; }
public string FullPath { get; }

View File

@@ -8,6 +8,7 @@ using Ryujinx.Ava.Ui.Models;
using Ryujinx.Ava.Ui.Windows;
using Ryujinx.Common;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Utilities;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
@@ -189,7 +190,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
{
amiiboJsonString = File.ReadAllText(_amiiboJsonPath);
if (await NeedsUpdate(JsonSerializer.Deserialize<Amiibo.AmiiboJson>(amiiboJsonString).LastUpdated))
if (await NeedsUpdate(JsonHelper.Deserialize<Amiibo.AmiiboJson>(amiiboJsonString).LastUpdated))
{
amiiboJsonString = await DownloadAmiiboJson();
}
@@ -206,7 +207,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
}
}
_amiiboList = JsonSerializer.Deserialize<Amiibo.AmiiboJson>(amiiboJsonString).Amiibo;
_amiiboList = JsonHelper.Deserialize<Amiibo.AmiiboJson>(amiiboJsonString).Amiibo;
_amiiboList = _amiiboList.OrderBy(amiibo => amiibo.AmiiboSeries).ToList();
ParseAmiiboData();

View File

@@ -460,8 +460,6 @@ namespace Ryujinx.Ava.Ui.ViewModels
{
using IGamepad gamepad = _mainWindow.InputManager.KeyboardDriver.GetGamepad(id);
Logger.Info?.Print(LogClass.Configuration, $"{GetShortGamepadName(gamepad.Name)} has been connected with ID: {gamepad.Id}");
if (gamepad != null)
{
Devices.Add((DeviceType.Keyboard, id, $"{GetShortGamepadName(gamepad.Name)}"));
@@ -472,8 +470,6 @@ namespace Ryujinx.Ava.Ui.ViewModels
{
using IGamepad gamepad = _mainWindow.InputManager.GamepadDriver.GetGamepad(id);
Logger.Info?.Print(LogClass.Configuration, $"{GetShortGamepadName(gamepad.Name)} has been connected with ID: {gamepad.Id}");
if (gamepad != null)
{
if (Devices.Any(controller => GetShortGamepadId(controller.Id) == GetShortGamepadId(gamepad.Id)))

View File

@@ -19,6 +19,7 @@ using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using Ryujinx.HLE;
using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS;
using Ryujinx.Modules;
using Ryujinx.Ui.App.Common;
using Ryujinx.Ui.Common;
@@ -47,6 +48,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
private string _loadHeading;
private string _cacheLoadStatus;
private string _searchText;
private Timer _searchTimer;
private string _dockedStatusText;
private string _fifoStatusText;
private string _gameStatusText;
@@ -115,10 +117,20 @@ namespace Ryujinx.Ava.Ui.ViewModels
{
_searchText = value;
RefreshView();
_searchTimer?.Dispose();
_searchTimer = new Timer(TimerCallback, null, 1000, 0);
}
}
private void TimerCallback(object obj)
{
RefreshView();
_searchTimer.Dispose();
_searchTimer = null;
}
public ReadOnlyObservableCollection<ApplicationData> AppsObservableList
{
get => _appsObservableList;
@@ -200,22 +212,19 @@ namespace Ryujinx.Ava.Ui.ViewModels
private string _showUikey = "F4";
private string _pauseKey = "F5";
private string _screenshotkey = "F8";
private float _volume;
private float _volume;
private string _backendText;
public ApplicationData SelectedApplication
{
get
{
switch (Glyph)
return Glyph switch
{
case Glyph.List:
return _owner.GameList.SelectedApplication;
case Glyph.Grid:
return _owner.GameGrid.SelectedApplication;
default:
return null;
}
Glyph.List => _owner.GameList.SelectedApplication,
Glyph.Grid => _owner.GameGrid.SelectedApplication,
_ => null,
};
}
}
@@ -408,6 +417,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
{
_owner.AppHost.Device.SetVolume(_volume);
}
OnPropertyChanged(nameof(VolumeStatusText));
OnPropertyChanged(nameof(VolumeMuted));
OnPropertyChanged();
@@ -477,38 +487,36 @@ namespace Ryujinx.Ava.Ui.ViewModels
internal void Sort(bool isAscending)
{
IsAscending = isAscending;
RefreshView();
}
internal void Sort(ApplicationSort sort)
{
SortMode = sort;
RefreshView();
}
private IComparer<ApplicationData> GetComparer()
{
switch (SortMode)
return SortMode switch
{
case ApplicationSort.LastPlayed:
return new Models.Generic.LastPlayedSortComparer(IsAscending);
case ApplicationSort.FileSize:
return new Models.Generic.FileSizeSortComparer(IsAscending);
case ApplicationSort.TotalTimePlayed:
return new Models.Generic.TimePlayedSortComparer(IsAscending);
case ApplicationSort.Title:
return IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.TitleName) : SortExpressionComparer<ApplicationData>.Descending(app => app.TitleName);
case ApplicationSort.Favorite:
return !IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Favorite) : SortExpressionComparer<ApplicationData>.Descending(app => app.Favorite);
case ApplicationSort.Developer:
return IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Developer) : SortExpressionComparer<ApplicationData>.Descending(app => app.Developer);
case ApplicationSort.FileType:
return IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.FileExtension) : SortExpressionComparer<ApplicationData>.Descending(app => app.FileExtension);
case ApplicationSort.Path:
return IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Path) : SortExpressionComparer<ApplicationData>.Descending(app => app.Path);
default:
return null;
}
ApplicationSort.LastPlayed => new Models.Generic.LastPlayedSortComparer(IsAscending),
ApplicationSort.FileSize => new Models.Generic.FileSizeSortComparer(IsAscending),
ApplicationSort.TotalTimePlayed => new Models.Generic.TimePlayedSortComparer(IsAscending),
ApplicationSort.Title => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.TitleName)
: SortExpressionComparer<ApplicationData>.Descending(app => app.TitleName),
ApplicationSort.Favorite => !IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Favorite)
: SortExpressionComparer<ApplicationData>.Descending(app => app.Favorite),
ApplicationSort.Developer => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Developer)
: SortExpressionComparer<ApplicationData>.Descending(app => app.Developer),
ApplicationSort.FileType => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.FileExtension)
: SortExpressionComparer<ApplicationData>.Descending(app => app.FileExtension),
ApplicationSort.Path => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Path)
: SortExpressionComparer<ApplicationData>.Descending(app => app.Path),
_ => null,
};
}
private void RefreshView()
@@ -611,40 +619,31 @@ namespace Ryujinx.Ava.Ui.ViewModels
}
}
public bool IsSortedByFavorite => SortMode == ApplicationSort.Favorite;
public bool IsSortedByTitle => SortMode == ApplicationSort.Title;
public bool IsSortedByDeveloper => SortMode == ApplicationSort.Developer;
public bool IsSortedByFavorite => SortMode == ApplicationSort.Favorite;
public bool IsSortedByTitle => SortMode == ApplicationSort.Title;
public bool IsSortedByDeveloper => SortMode == ApplicationSort.Developer;
public bool IsSortedByLastPlayed => SortMode == ApplicationSort.LastPlayed;
public bool IsSortedByTimePlayed => SortMode == ApplicationSort.TotalTimePlayed;
public bool IsSortedByType => SortMode == ApplicationSort.FileType;
public bool IsSortedBySize => SortMode == ApplicationSort.FileSize;
public bool IsSortedByPath => SortMode == ApplicationSort.Path;
public bool IsSortedByType => SortMode == ApplicationSort.FileType;
public bool IsSortedBySize => SortMode == ApplicationSort.FileSize;
public bool IsSortedByPath => SortMode == ApplicationSort.Path;
public string SortName
{
get
{
switch (SortMode)
return SortMode switch
{
case ApplicationSort.Title:
return LocaleManager.Instance["GameListHeaderApplication"];
case ApplicationSort.Developer:
return LocaleManager.Instance["GameListHeaderDeveloper"];
case ApplicationSort.LastPlayed:
return LocaleManager.Instance["GameListHeaderLastPlayed"];
case ApplicationSort.TotalTimePlayed:
return LocaleManager.Instance["GameListHeaderTimePlayed"];
case ApplicationSort.FileType:
return LocaleManager.Instance["GameListHeaderFileExtension"];
case ApplicationSort.FileSize:
return LocaleManager.Instance["GameListHeaderFileSize"];
case ApplicationSort.Path:
return LocaleManager.Instance["GameListHeaderPath"];
case ApplicationSort.Favorite:
return LocaleManager.Instance["CommonFavorite"];
}
return string.Empty;
ApplicationSort.Title => LocaleManager.Instance["GameListHeaderApplication"],
ApplicationSort.Developer => LocaleManager.Instance["GameListHeaderDeveloper"],
ApplicationSort.LastPlayed => LocaleManager.Instance["GameListHeaderLastPlayed"],
ApplicationSort.TotalTimePlayed => LocaleManager.Instance["GameListHeaderTimePlayed"],
ApplicationSort.FileType => LocaleManager.Instance["GameListHeaderFileExtension"],
ApplicationSort.FileSize => LocaleManager.Instance["GameListHeaderFileSize"],
ApplicationSort.Path => LocaleManager.Instance["GameListHeaderPath"],
ApplicationSort.Favorite => LocaleManager.Instance["CommonFavorite"],
_ => string.Empty,
};
}
}
@@ -668,6 +667,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
get => KeyGesture.Parse(_showUikey); set
{
_showUikey = value.ToString();
OnPropertyChanged();
}
}
@@ -677,6 +677,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
get => KeyGesture.Parse(_screenshotkey); set
{
_screenshotkey = value.ToString();
OnPropertyChanged();
}
}
@@ -686,14 +687,15 @@ namespace Ryujinx.Ava.Ui.ViewModels
get => KeyGesture.Parse(_pauseKey); set
{
_pauseKey = value.ToString();
OnPropertyChanged();
}
}
public bool IsGridSmall => ConfigurationState.Instance.Ui.GridSize == 1;
public bool IsGridSmall => ConfigurationState.Instance.Ui.GridSize == 1;
public bool IsGridMedium => ConfigurationState.Instance.Ui.GridSize == 2;
public bool IsGridLarge => ConfigurationState.Instance.Ui.GridSize == 3;
public bool IsGridHuge => ConfigurationState.Instance.Ui.GridSize == 4;
public bool IsGridLarge => ConfigurationState.Instance.Ui.GridSize == 3;
public bool IsGridHuge => ConfigurationState.Instance.Ui.GridSize == 4;
public int GridSizeScale
{
@@ -728,14 +730,14 @@ namespace Ryujinx.Ava.Ui.ViewModels
if (_owner.AppHost.Device.System.SearchingForAmiibo(out int deviceId))
{
string titleId = _owner.AppHost.Device.Application.TitleIdText.ToUpper();
string titleId = _owner.AppHost.Device.Application.TitleIdText.ToUpper();
AmiiboWindow window = new(_showAll, _lastScannedAmiiboId, titleId);
await window.ShowDialog(_owner);
if (window.IsScanned)
{
_showAll = window.ViewModel.ShowAllAmiibo;
_showAll = window.ViewModel.ShowAllAmiibo;
_lastScannedAmiiboId = window.ScannedAmiibo.GetId();
_owner.AppHost.Device.System.ScanAmiibo(deviceId, _lastScannedAmiiboId, window.ViewModel.UseRandomUuid);
@@ -766,8 +768,9 @@ namespace Ryujinx.Ava.Ui.ViewModels
private void ApplicationLibrary_ApplicationCountUpdated(object sender, ApplicationCountUpdatedEventArgs e)
{
StatusBarProgressValue = e.NumAppsLoaded;
StatusBarProgressValue = e.NumAppsLoaded;
StatusBarProgressMaximum = e.NumAppsFound;
LocaleManager.Instance.UpdateDynamicValue("StatusBarGamesLoaded", StatusBarProgressValue, StatusBarProgressMaximum);
Dispatcher.UIThread.Post(() =>
@@ -792,9 +795,11 @@ namespace Ryujinx.Ava.Ui.ViewModels
await Dispatcher.UIThread.InvokeAsync(() =>
{
Applications.Clear();
_owner.LoadProgressBar.IsVisible = true;
StatusBarProgressMaximum = 0;
StatusBarProgressValue = 0;
StatusBarProgressMaximum = 0;
StatusBarProgressValue = 0;
LocaleManager.Instance.UpdateDynamicValue("StatusBarGamesLoaded", 0, 0);
});
@@ -842,12 +847,12 @@ namespace Ryujinx.Ava.Ui.ViewModels
}
});
dialog.Filters.Add(new FileDialogFilter { Name = "NSP", Extensions = { "nsp" } });
dialog.Filters.Add(new FileDialogFilter { Name = "NSP", Extensions = { "nsp" } });
dialog.Filters.Add(new FileDialogFilter { Name = "PFS0", Extensions = { "pfs0" } });
dialog.Filters.Add(new FileDialogFilter { Name = "XCI", Extensions = { "xci" } });
dialog.Filters.Add(new FileDialogFilter { Name = "NCA", Extensions = { "nca" } });
dialog.Filters.Add(new FileDialogFilter { Name = "NRO", Extensions = { "nro" } });
dialog.Filters.Add(new FileDialogFilter { Name = "NSO", Extensions = { "nso" } });
dialog.Filters.Add(new FileDialogFilter { Name = "XCI", Extensions = { "xci" } });
dialog.Filters.Add(new FileDialogFilter { Name = "NCA", Extensions = { "nca" } });
dialog.Filters.Add(new FileDialogFilter { Name = "NRO", Extensions = { "nro" } });
dialog.Filters.Add(new FileDialogFilter { Name = "NSO", Extensions = { "nso" } });
string[] files = await dialog.ShowAsync(_owner);
@@ -878,10 +883,12 @@ namespace Ryujinx.Ava.Ui.ViewModels
{
ShowUiKey = new KeyGesture(showUiKey, KeyModifiers.None);
}
if (AvaloniaMappingHelper.TryGetAvaKey((Ryujinx.Input.Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Screenshot, out var screenshotKey))
{
ScreenshotKey = new KeyGesture(screenshotKey, KeyModifiers.None);
}
if (AvaloniaMappingHelper.TryGetAvaKey((Ryujinx.Input.Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Pause, out var pauseKey))
{
PauseKey = new KeyGesture(pauseKey, KeyModifiers.None);
@@ -941,9 +948,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
_lastFullscreenToggle = Environment.TickCount64;
WindowState state = _owner.WindowState;
if (state == WindowState.FullScreen)
if (_owner.WindowState == WindowState.FullScreen)
{
_owner.WindowState = WindowState.Normal;
@@ -971,8 +976,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
{
if (IsGameRunning)
{
ConfigurationState.Instance.System.EnableDockedMode.Value =
!ConfigurationState.Instance.System.EnableDockedMode.Value;
ConfigurationState.Instance.System.EnableDockedMode.Value = !ConfigurationState.Instance.System.EnableDockedMode.Value;
}
}
@@ -985,6 +989,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
else if (IsGameRunning)
{
await Task.Delay(100);
_owner.AppHost?.ShowExitPrompt();
}
}
@@ -994,6 +999,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
_owner.SettingsWindow = new(_owner.VirtualFileSystem, _owner.ContentManager);
await _owner.SettingsWindow.ShowDialog(_owner);
LoadConfigurableHotKeys();
}
@@ -1004,9 +1010,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
public async void OpenAboutWindow()
{
AboutWindow window = new();
await window.ShowDialog(_owner);
await new AboutWindow().ShowDialog(_owner);
}
public void ChangeLanguage(object obj)
@@ -1020,7 +1024,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
try
{
ProgressMaximum = total;
ProgressValue = current;
ProgressValue = current;
switch (state)
{
@@ -1030,13 +1034,13 @@ namespace Ryujinx.Ava.Ui.ViewModels
{
case PtcLoadingState.Start:
case PtcLoadingState.Loading:
LoadHeading = LocaleManager.Instance["CompilingPPTC"];
LoadHeading = LocaleManager.Instance["CompilingPPTC"];
IsLoadingIndeterminate = false;
break;
case PtcLoadingState.Loaded:
LoadHeading = string.Format(LocaleManager.Instance["LoadingHeading"], TitleName);
LoadHeading = string.Format(LocaleManager.Instance["LoadingHeading"], TitleName);
IsLoadingIndeterminate = true;
CacheLoadStatus = "";
CacheLoadStatus = "";
break;
}
break;
@@ -1046,13 +1050,13 @@ namespace Ryujinx.Ava.Ui.ViewModels
{
case ShaderCacheLoadingState.Start:
case ShaderCacheLoadingState.Loading:
LoadHeading = LocaleManager.Instance["CompilingShaders"];
LoadHeading = LocaleManager.Instance["CompilingShaders"];
IsLoadingIndeterminate = false;
break;
case ShaderCacheLoadingState.Loaded:
LoadHeading = string.Format(LocaleManager.Instance["LoadingHeading"], TitleName);
LoadHeading = string.Format(LocaleManager.Instance["LoadingHeading"], TitleName);
IsLoadingIndeterminate = true;
CacheLoadStatus = "";
CacheLoadStatus = "";
break;
}
break;
@@ -1065,14 +1069,12 @@ namespace Ryujinx.Ava.Ui.ViewModels
public void OpenUserSaveDirectory()
{
var selection = SelectedApplication;
ApplicationData selection = SelectedApplication;
if (selection != null)
{
Task.Run(() =>
{
if (!ulong.TryParse(selection.TitleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture,
out ulong titleIdNumber))
if (!ulong.TryParse(selection.TitleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdNumber))
{
Dispatcher.UIThread.Post(async () =>
{
@@ -1082,8 +1084,8 @@ namespace Ryujinx.Ava.Ui.ViewModels
return;
}
var userId = new LibHac.Fs.UserId((ulong)_owner.AccountManager.LastOpenedUser.UserId.High, (ulong)_owner.AccountManager.LastOpenedUser.UserId.Low);
var saveDataFilter = SaveDataFilter.Make(titleIdNumber, saveType: default, userId, saveDataId: default, index: default);
UserId userId = new((ulong)_owner.AccountManager.LastOpenedUser.UserId.High, (ulong)_owner.AccountManager.LastOpenedUser.UserId.Low);
SaveDataFilter saveDataFilter = SaveDataFilter.Make(titleIdNumber, saveType: default, userId, saveDataId: default, index: default);
OpenSaveDirectory(in saveDataFilter, selection, titleIdNumber);
});
}
@@ -1091,8 +1093,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
public void ToggleFavorite()
{
var selection = SelectedApplication;
ApplicationData selection = SelectedApplication;
if (selection != null)
{
selection.Favorite = !selection.Favorite;
@@ -1108,11 +1109,10 @@ namespace Ryujinx.Ava.Ui.ViewModels
public void OpenModsDirectory()
{
var selection = SelectedApplication;
ApplicationData selection = SelectedApplication;
if (selection != null)
{
string modsBasePath = _owner.VirtualFileSystem.ModLoader.GetModsBasePath();
string modsBasePath = _owner.VirtualFileSystem.ModLoader.GetModsBasePath();
string titleModsPath = _owner.VirtualFileSystem.ModLoader.GetTitleDir(modsBasePath, selection.TitleId);
OpenHelper.OpenFolder(titleModsPath);
@@ -1121,12 +1121,12 @@ namespace Ryujinx.Ava.Ui.ViewModels
public void OpenSdModsDirectory()
{
var selection = SelectedApplication;
ApplicationData selection = SelectedApplication;
if (selection != null)
{
string sdModsBasePath = _owner.VirtualFileSystem.ModLoader.GetSdModsBasePath();
string titleModsPath = _owner.VirtualFileSystem.ModLoader.GetTitleDir(sdModsBasePath, selection.TitleId);
string titleModsPath = _owner.VirtualFileSystem.ModLoader.GetTitleDir(sdModsBasePath, selection.TitleId);
OpenHelper.OpenFolder(titleModsPath);
}
@@ -1134,13 +1134,11 @@ namespace Ryujinx.Ava.Ui.ViewModels
public void OpenPtcDirectory()
{
var selection = SelectedApplication;
ApplicationData selection = SelectedApplication;
if (selection != null)
{
string ptcDir = Path.Combine(AppDataManager.GamesDirPath, selection.TitleId, "cache", "cpu");
string mainPath = Path.Combine(ptcDir, "0");
string ptcDir = Path.Combine(AppDataManager.GamesDirPath, selection.TitleId, "cache", "cpu");
string mainPath = Path.Combine(ptcDir, "0");
string backupPath = Path.Combine(ptcDir, "1");
if (!Directory.Exists(ptcDir))
@@ -1156,16 +1154,18 @@ namespace Ryujinx.Ava.Ui.ViewModels
public async void PurgePtcCache()
{
var selection = SelectedApplication;
ApplicationData selection = SelectedApplication;
if (selection != null)
{
DirectoryInfo mainDir = new(Path.Combine(AppDataManager.GamesDirPath, selection.TitleId, "cache", "cpu", "0"));
DirectoryInfo mainDir = new(Path.Combine(AppDataManager.GamesDirPath, selection.TitleId, "cache", "cpu", "0"));
DirectoryInfo backupDir = new(Path.Combine(AppDataManager.GamesDirPath, selection.TitleId, "cache", "cpu", "1"));
// FIXME: Found a way to reproduce the bold effect on the title name (fork?).
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(LocaleManager.Instance["DialogWarning"],
string.Format(LocaleManager.Instance["DialogPPTCDeletionMessage"], selection.TitleName), LocaleManager.Instance["InputDialogYes"], LocaleManager.Instance["InputDialogNo"], LocaleManager.Instance["RyujinxConfirm"]);
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(LocaleManager.Instance["DialogWarning"],
string.Format(LocaleManager.Instance["DialogPPTCDeletionMessage"], selection.TitleName),
LocaleManager.Instance["InputDialogYes"],
LocaleManager.Instance["InputDialogNo"],
LocaleManager.Instance["RyujinxConfirm"]);
List<FileInfo> cacheFiles = new();
@@ -1198,8 +1198,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
public void OpenShaderCacheDirectory()
{
var selection = SelectedApplication;
ApplicationData selection = SelectedApplication;
if (selection != null)
{
string shaderCacheDir = Path.Combine(AppDataManager.GamesDirPath, selection.TitleId, "cache", "shader");
@@ -1220,18 +1219,20 @@ namespace Ryujinx.Ava.Ui.ViewModels
public async void PurgeShaderCache()
{
var selection = SelectedApplication;
ApplicationData selection = SelectedApplication;
if (selection != null)
{
DirectoryInfo shaderCacheDir = new(Path.Combine(AppDataManager.GamesDirPath, selection.TitleId, "cache", "shader"));
// FIXME: Found a way to reproduce the bold effect on the title name (fork?).
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(LocaleManager.Instance["DialogWarning"],
string.Format(LocaleManager.Instance["DialogShaderDeletionMessage"], selection.TitleName), LocaleManager.Instance["InputDialogYes"], LocaleManager.Instance["InputDialogNo"], LocaleManager.Instance["RyujinxConfirm"]);
string.Format(LocaleManager.Instance["DialogShaderDeletionMessage"], selection.TitleName),
LocaleManager.Instance["InputDialogYes"],
LocaleManager.Instance["InputDialogNo"],
LocaleManager.Instance["RyujinxConfirm"]);
List<DirectoryInfo> oldCacheDirectories = new List<DirectoryInfo>();
List<FileInfo> newCacheFiles = new List<FileInfo>();
List<DirectoryInfo> oldCacheDirectories = new();
List<FileInfo> newCacheFiles = new();
if (shaderCacheDir.Exists)
{
@@ -1279,38 +1280,28 @@ namespace Ryujinx.Ava.Ui.ViewModels
public async void OpenTitleUpdateManager()
{
var selection = SelectedApplication;
ApplicationData selection = SelectedApplication;
if (selection != null)
{
TitleUpdateWindow titleUpdateManager =
new(_owner.VirtualFileSystem, selection.TitleId, selection.TitleName);
await titleUpdateManager.ShowDialog(_owner);
await new TitleUpdateWindow(_owner.VirtualFileSystem, selection.TitleId, selection.TitleName).ShowDialog(_owner);
}
}
public async void OpenDownloadableContentManager()
{
var selection = SelectedApplication;
ApplicationData selection = SelectedApplication;
if (selection != null)
{
DownloadableContentManagerWindow downloadableContentManager = new(_owner.VirtualFileSystem, ulong.Parse(selection.TitleId, NumberStyles.HexNumber), selection.TitleName);
await downloadableContentManager.ShowDialog(_owner);
await new DownloadableContentManagerWindow(_owner.VirtualFileSystem, ulong.Parse(selection.TitleId, NumberStyles.HexNumber), selection.TitleName).ShowDialog(_owner);
}
}
public async void OpenCheatManager()
{
var selection = SelectedApplication;
ApplicationData selection = SelectedApplication;
if (selection != null)
{
CheatWindow cheatManager = new(_owner.VirtualFileSystem, selection.TitleId, selection.TitleName);
await cheatManager.ShowDialog(_owner);
await new CheatWindow(_owner.VirtualFileSystem, selection.TitleId, selection.TitleName).ShowDialog(_owner);
}
}
@@ -1321,13 +1312,10 @@ namespace Ryujinx.Ava.Ui.ViewModels
return;
}
var application = _owner.AppHost.Device.Application;
ApplicationLoader application = _owner.AppHost.Device.Application;
if (application != null)
{
CheatWindow cheatManager = new(_owner.VirtualFileSystem, application.TitleIdText, application.TitleName);
await cheatManager.ShowDialog(_owner);
await new CheatWindow(_owner.VirtualFileSystem, application.TitleIdText, application.TitleName).ShowDialog(_owner);
_owner.AppHost.Device.EnableCheats();
}
@@ -1335,14 +1323,12 @@ namespace Ryujinx.Ava.Ui.ViewModels
public void OpenDeviceSaveDirectory()
{
var selection = SelectedApplication;
ApplicationData selection = SelectedApplication;
if (selection != null)
{
Task.Run(() =>
{
if (!ulong.TryParse(selection.TitleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture,
out ulong titleIdNumber))
if (!ulong.TryParse(selection.TitleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdNumber))
{
Dispatcher.UIThread.Post(async () =>
{
@@ -1360,14 +1346,12 @@ namespace Ryujinx.Ava.Ui.ViewModels
public void OpenBcatSaveDirectory()
{
var selection = SelectedApplication;
ApplicationData selection = SelectedApplication;
if (selection != null)
{
Task.Run(() =>
{
if (!ulong.TryParse(selection.TitleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture,
out ulong titleIdNumber))
if (!ulong.TryParse(selection.TitleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdNumber))
{
Dispatcher.UIThread.Post(async () =>
{
@@ -1420,12 +1404,10 @@ namespace Ryujinx.Ava.Ui.ViewModels
_owner.Close();
}
private async Task HandleFirmwareInstallation(string path)
private async Task HandleFirmwareInstallation(string filename)
{
try
{
string filename = path;
SystemVersion firmwareVersion = _owner.ContentManager.VerifyFirmwarePackage(filename);
if (firmwareVersion == null)
@@ -1437,7 +1419,6 @@ namespace Ryujinx.Ava.Ui.ViewModels
string dialogTitle = string.Format(LocaleManager.Instance["DialogFirmwareInstallerFirmwareInstallTitle"], firmwareVersion.VersionString);
SystemVersion currentVersion = _owner.ContentManager.GetCurrentFirmwareVersion();
string dialogMessage = string.Format(LocaleManager.Instance["DialogFirmwareInstallerFirmwareInstallMessage"], firmwareVersion.VersionString);
@@ -1480,11 +1461,12 @@ namespace Ryujinx.Ava.Ui.ViewModels
string message = string.Format(LocaleManager.Instance["DialogFirmwareInstallerFirmwareInstallSuccessMessage"], firmwareVersion.VersionString);
await ContentDialogHelper.CreateInfoDialog(dialogTitle, message, LocaleManager.Instance["InputDialogOk"], "", LocaleManager.Instance["RyujinxInfo"]);
Logger.Info?.Print(LogClass.Application, message);
// Purge Applet Cache.
DirectoryInfo miiEditorCacheFolder = new DirectoryInfo(System.IO.Path.Combine(AppDataManager.GamesDirPath, "0100000000001009", "cache"));
DirectoryInfo miiEditorCacheFolder = new DirectoryInfo(Path.Combine(AppDataManager.GamesDirPath, "0100000000001009", "cache"));
if (miiEditorCacheFolder.Exists)
{
@@ -1514,8 +1496,8 @@ namespace Ryujinx.Ava.Ui.ViewModels
catch (LibHac.Common.Keys.MissingKeyException ex)
{
Logger.Error?.Print(LogClass.Application, ex.ToString());
Dispatcher.UIThread.Post(async () => await
UserErrorDialog.ShowUserErrorDialog(UserError.NoKeys, _owner));
Dispatcher.UIThread.Post(async () => await UserErrorDialog.ShowUserErrorDialog(UserError.NoKeys, _owner));
}
catch (Exception ex)
{
@@ -1527,8 +1509,8 @@ namespace Ryujinx.Ava.Ui.ViewModels
{
OpenFileDialog dialog = new() { AllowMultiple = false };
dialog.Filters.Add(new FileDialogFilter { Name = LocaleManager.Instance["FileDialogAllTypes"], Extensions = { "xci", "zip" } });
dialog.Filters.Add(new FileDialogFilter { Name = "XCI", Extensions = { "xci" } });
dialog.Filters.Add(new FileDialogFilter { Name = "ZIP", Extensions = { "zip" } });
dialog.Filters.Add(new FileDialogFilter { Name = "XCI", Extensions = { "xci" } });
dialog.Filters.Add(new FileDialogFilter { Name = "ZIP", Extensions = { "zip" } });
string[] file = await dialog.ShowAsync(_owner);

View File

@@ -3,89 +3,126 @@
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows"
SizeToContent="Height"
Width="600" MinHeight="500" Height="500"
WindowStartupLocation="CenterOwner"
Width="800"
Height="500"
MinWidth="600"
MinHeight="500"
SizeToContent="Height"
WindowStartupLocation="CenterOwner"
mc:Ignorable="d">
<Grid Name="DownloadableContentGrid" Margin="15">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock
Name="Heading"
Grid.Row="1"
MaxWidth="500"
Margin="20,15,20,20"
HorizontalAlignment="Center"
VerticalAlignment="Center"
MaxWidth="500"
LineHeight="18"
TextWrapping="Wrap"
Text="{Binding Heading}"
TextAlignment="Center" />
<Border
TextAlignment="Center"
TextWrapping="Wrap" />
<DockPanel
Grid.Row="2"
Margin="0"
HorizontalAlignment="Left">
<Button
Name="EnableAllButton"
MinWidth="90"
Margin="5"
Command="{Binding EnableAll}">
<TextBlock Text="{locale:Locale DlcManagerEnableAllButton}" />
</Button>
<Button
Name="DisableAllButton"
MinWidth="90"
Margin="5"
Command="{Binding DisableAll}">
<TextBlock Text="{locale:Locale DlcManagerDisableAllButton}" />
</Button>
</DockPanel>
<Border
Grid.Row="3"
Margin="5"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
BorderBrush="Gray"
BorderThickness="1">
<DataGrid
MinHeight="200"
HorizontalAlignment="Stretch"
<ScrollViewer
VerticalAlignment="Stretch"
HorizontalScrollBarVisibility="Auto"
Items="{Binding DownloadableContents}"
VerticalScrollBarVisibility="Auto">
<DataGrid.Columns>
<DataGridTemplateColumn Width="90">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox
Width="50"
MinWidth="40"
HorizontalAlignment="Right"
IsChecked="{Binding Enabled}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.Header>
<TextBlock Text="{locale:Locale DlcManagerTableHeadingEnabledLabel}" />
</DataGridTemplateColumn.Header>
</DataGridTemplateColumn>
<DataGridTextColumn
Width="190"
Binding="{Binding TitleId}"
CanUserResize="True">
<DataGridTextColumn.Header>
<TextBlock Text="{locale:Locale DlcManagerTableHeadingTitleIdLabel}" />
</DataGridTextColumn.Header>
</DataGridTextColumn>
<DataGridTextColumn
Width="*"
Binding="{Binding ContainerPath}"
CanUserResize="True">
<DataGridTextColumn.Header>
<TextBlock Text="{locale:Locale DlcManagerTableHeadingContainerPathLabel}" />
</DataGridTextColumn.Header>
</DataGridTextColumn>
<DataGridTextColumn
Width="*"
Binding="{Binding FullPath}"
CanUserResize="True">
<DataGridTextColumn.Header>
<TextBlock Text="{locale:Locale DlcManagerTableHeadingFullPathLabel}" />
</DataGridTextColumn.Header>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
<DataGrid
Name="DlcDataGrid"
MinHeight="200"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
CanUserReorderColumns="False"
CanUserResizeColumns="True"
CanUserSortColumns="True"
HorizontalScrollBarVisibility="Auto"
Items="{Binding _downloadableContents}"
SelectionMode="Extended"
VerticalScrollBarVisibility="Auto">
<DataGrid.Styles>
<Styles>
<Style Selector="DataGridCell:nth-child(3), DataGridCell:nth-child(4)">
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="HorizontalContentAlignment" Value="Left" />
</Style>
</Styles>
<Styles>
<Style Selector="DataGridCell:nth-child(1)">
<Setter Property="HorizontalAlignment" Value="Right" />
<Setter Property="HorizontalContentAlignment" Value="Right" />
</Style>
</Styles>
</DataGrid.Styles>
<DataGrid.Columns>
<DataGridTemplateColumn Width="90">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox
Width="50"
MinWidth="40"
HorizontalAlignment="Center"
IsChecked="{Binding Enabled}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.Header>
<TextBlock Text="{locale:Locale DlcManagerTableHeadingEnabledLabel}" />
</DataGridTemplateColumn.Header>
</DataGridTemplateColumn>
<DataGridTextColumn Width="140" Binding="{Binding TitleId}">
<DataGridTextColumn.Header>
<TextBlock Text="{locale:Locale DlcManagerTableHeadingTitleIdLabel}" />
</DataGridTextColumn.Header>
</DataGridTextColumn>
<DataGridTextColumn Width="280" Binding="{Binding FullPath}">
<DataGridTextColumn.Header>
<TextBlock Text="{locale:Locale DlcManagerTableHeadingFullPathLabel}" />
</DataGridTextColumn.Header>
</DataGridTextColumn>
<DataGridTextColumn Binding="{Binding ContainerPath}">
<DataGridTextColumn.Header>
<TextBlock Text="{locale:Locale DlcManagerTableHeadingContainerPathLabel}" />
</DataGridTextColumn.Header>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
</ScrollViewer>
</Border>
<DockPanel
Grid.Row="3"
Grid.Row="4"
Margin="0"
HorizontalAlignment="Stretch">
<DockPanel Margin="0" HorizontalAlignment="Left">

View File

@@ -8,6 +8,7 @@ using LibHac.FsSystem;
using LibHac.Tools.Fs;
using LibHac.Tools.FsSystem;
using LibHac.Tools.FsSystem.NcaUtils;
using LibHac.Tools.FsSystem.Save;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Ui.Controls;
using Ryujinx.Ava.Ui.Models;
@@ -16,8 +17,11 @@ using Ryujinx.Common.Utilities;
using Ryujinx.HLE.FileSystem;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Reactive.Linq;
using System.Text;
using System.Threading.Tasks;
using Path = System.IO.Path;
@@ -27,14 +31,13 @@ namespace Ryujinx.Ava.Ui.Windows
public partial class DownloadableContentManagerWindow : StyleableWindow
{
private readonly List<DownloadableContentContainer> _downloadableContentContainerList;
private readonly string _downloadableContentJsonPath;
private readonly string _downloadableContentJsonPath;
public VirtualFileSystem VirtualFileSystem { get; }
public AvaloniaList<DownloadableContentModel> DownloadableContents { get; set; } = new AvaloniaList<DownloadableContentModel>();
public ulong TitleId { get; }
public string TitleName { get; }
private VirtualFileSystem _virtualFileSystem { get; }
private AvaloniaList<DownloadableContentModel> _downloadableContents { get; set; }
public string Heading => string.Format(LocaleManager.Instance["DlcWindowHeading"], TitleName, TitleId.ToString("X16"));
private ulong TitleId { get; }
private string TitleName { get; }
public DownloadableContentManagerWindow()
{
@@ -42,14 +45,15 @@ namespace Ryujinx.Ava.Ui.Windows
InitializeComponent();
Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["DlcWindowTitle"];
Title = $"Ryujinx {Program.Version} - {LocaleManager.Instance["DlcWindowTitle"]} - {TitleName} ({TitleId:X16})";
}
public DownloadableContentManagerWindow(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName)
{
VirtualFileSystem = virtualFileSystem;
TitleId = titleId;
TitleName = titleName;
_virtualFileSystem = virtualFileSystem;
_downloadableContents = new AvaloniaList<DownloadableContentModel>();
TitleId = titleId;
TitleName = titleName;
_downloadableContentJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId.ToString("x16"), "dlc.json");
@@ -66,9 +70,24 @@ namespace Ryujinx.Ava.Ui.Windows
InitializeComponent();
Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["DlcWindowTitle"];
RemoveButton.IsEnabled = false;
DlcDataGrid.SelectionChanged += DlcDataGrid_SelectionChanged;
Title = $"Ryujinx {Program.Version} - {LocaleManager.Instance["DlcWindowTitle"]} - {TitleName} ({TitleId:X16})";
LoadDownloadableContents();
PrintHeading();
}
private void DlcDataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
RemoveButton.IsEnabled = (DlcDataGrid.SelectedItems.Count > 0);
}
private void PrintHeading()
{
Heading.Text = string.Format(LocaleManager.Instance["DlcWindowHeading"], _downloadableContents.Count, TitleName, TitleId.ToString("X16"));
}
private void LoadDownloadableContents()
@@ -79,23 +98,23 @@ namespace Ryujinx.Ava.Ui.Windows
{
using FileStream containerFile = File.OpenRead(downloadableContentContainer.ContainerPath);
PartitionFileSystem pfs = new PartitionFileSystem(containerFile.AsStorage());
PartitionFileSystem pfs = new(containerFile.AsStorage());
VirtualFileSystem.ImportTickets(pfs);
_virtualFileSystem.ImportTickets(pfs);
foreach (DownloadableContentNca downloadableContentNca in downloadableContentContainer.DownloadableContentNcaList)
{
using var ncaFile = new UniqueRef<IFile>();
using UniqueRef<IFile> ncaFile = new();
pfs.OpenFile(ref ncaFile.Ref(), downloadableContentNca.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
Nca nca = TryCreateNca(ncaFile.Get.AsStorage(), downloadableContentContainer.ContainerPath);
Nca nca = TryOpenNca(ncaFile.Get.AsStorage(), downloadableContentContainer.ContainerPath);
if (nca != null)
{
DownloadableContents.Add(new DownloadableContentModel(nca.Header.TitleId.ToString("X16"),
downloadableContentContainer.ContainerPath,
downloadableContentNca.FullPath,
downloadableContentNca.Enabled));
_downloadableContents.Add(new DownloadableContentModel(nca.Header.TitleId.ToString("X16"),
downloadableContentContainer.ContainerPath,
downloadableContentNca.FullPath,
downloadableContentNca.Enabled));
}
}
}
@@ -105,11 +124,11 @@ namespace Ryujinx.Ava.Ui.Windows
Save();
}
private Nca TryCreateNca(IStorage ncaStorage, string containerPath)
private Nca TryOpenNca(IStorage ncaStorage, string containerPath)
{
try
{
return new Nca(VirtualFileSystem.KeySet, ncaStorage);
return new Nca(_virtualFileSystem.KeySet, ncaStorage);
}
catch (Exception ex)
{
@@ -124,61 +143,73 @@ namespace Ryujinx.Ava.Ui.Windows
private async Task AddDownloadableContent(string path)
{
if (!File.Exists(path) || DownloadableContents.FirstOrDefault(x => x.ContainerPath == path) != null)
if (!File.Exists(path) || _downloadableContents.FirstOrDefault(x => x.ContainerPath == path) != null)
{
return;
}
using (FileStream containerFile = File.OpenRead(path))
using FileStream containerFile = File.OpenRead(path);
PartitionFileSystem partitionFileSystem = new(containerFile.AsStorage());
bool containsDownloadableContent = false;
_virtualFileSystem.ImportTickets(partitionFileSystem);
foreach (DirectoryEntryEx fileEntry in partitionFileSystem.EnumerateEntries("/", "*.nca"))
{
PartitionFileSystem pfs = new PartitionFileSystem(containerFile.AsStorage());
bool containsDownloadableContent = false;
using var ncaFile = new UniqueRef<IFile>();
VirtualFileSystem.ImportTickets(pfs);
partitionFileSystem.OpenFile(ref ncaFile.Ref(), fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))
Nca nca = TryOpenNca(ncaFile.Get.AsStorage(), path);
if (nca == null)
{
using var ncaFile = new UniqueRef<IFile>();
pfs.OpenFile(ref ncaFile.Ref(), fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
Nca nca = TryCreateNca(ncaFile.Get.AsStorage(), path);
if (nca == null)
{
continue;
}
if (nca.Header.ContentType == NcaContentType.PublicData)
{
if ((nca.Header.TitleId & 0xFFFFFFFFFFFFE000) != TitleId)
{
break;
}
DownloadableContents.Add(new DownloadableContentModel(nca.Header.TitleId.ToString("X16"), path, fileEntry.FullPath, true));
containsDownloadableContent = true;
}
continue;
}
if (!containsDownloadableContent)
if (nca.Header.ContentType == NcaContentType.PublicData)
{
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance["DialogDlcNoDlcErrorMessage"]);
if ((nca.Header.TitleId & 0xFFFFFFFFFFFFE000) != TitleId)
{
break;
}
_downloadableContents.Add(new DownloadableContentModel(nca.Header.TitleId.ToString("X16"), path, fileEntry.FullPath, true));
containsDownloadableContent = true;
}
}
if (!containsDownloadableContent)
{
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance["DialogDlcNoDlcErrorMessage"]);
}
}
private void RemoveDownloadableContents(bool removeSelectedOnly = false)
{
if (removeSelectedOnly)
{
DownloadableContents.RemoveAll(DownloadableContents.Where(x => x.Enabled).ToList());
AvaloniaList<DownloadableContentModel> removedItems = new();
foreach (var item in DlcDataGrid.SelectedItems)
{
removedItems.Add(item as DownloadableContentModel);
}
DlcDataGrid.SelectedItems.Clear();
foreach (var item in removedItems)
{
_downloadableContents.RemoveAll(_downloadableContents.Where(x => x.TitleId == item.TitleId).ToList());
}
}
else
{
DownloadableContents.Clear();
_downloadableContents.Clear();
}
PrintHeading();
}
public void RemoveSelected()
@@ -191,6 +222,22 @@ namespace Ryujinx.Ava.Ui.Windows
RemoveDownloadableContents();
}
public void EnableAll()
{
foreach(var item in _downloadableContents)
{
item.Enabled = true;
}
}
public void DisableAll()
{
foreach (var item in _downloadableContents)
{
item.Enabled = false;
}
}
public async void Add()
{
OpenFileDialog dialog = new OpenFileDialog()
@@ -214,6 +261,8 @@ namespace Ryujinx.Ava.Ui.Windows
await AddDownloadableContent(file);
}
}
PrintHeading();
}
public void Save()
@@ -222,7 +271,7 @@ namespace Ryujinx.Ava.Ui.Windows
DownloadableContentContainer container = default;
foreach (DownloadableContentModel downloadableContent in DownloadableContents)
foreach (DownloadableContentModel downloadableContent in _downloadableContents)
{
if (container.ContainerPath != downloadableContent.ContainerPath)
{

View File

@@ -90,8 +90,8 @@ namespace Ryujinx.Ava.Ui.Windows
Title = $"Ryujinx {Program.Version}";
Height = Height / Program.WindowScaleFactor;
Width = Width / Program.WindowScaleFactor;
Height /= Program.WindowScaleFactor;
Width /= Program.WindowScaleFactor;
if (Program.PreviewerDetached)
{
@@ -251,24 +251,29 @@ namespace Ryujinx.Ava.Ui.Windows
AppHost = new AppHost(RendererControl, InputManager, path, VirtualFileSystem, ContentManager, AccountManager, _userChannelPersistence, this);
if (!AppHost.LoadGuestApplication().Result)
Dispatcher.UIThread.Post(async () =>
{
AppHost.DisposeContext();
if (!await AppHost.LoadGuestApplication())
{
AppHost.DisposeContext();
AppHost = null;
return;
}
return;
}
ViewModel.LoadHeading = string.IsNullOrWhiteSpace(titleName) ? string.Format(LocaleManager.Instance["LoadingHeading"], AppHost.Device.Application.TitleName) : titleName;
ViewModel.TitleName = string.IsNullOrWhiteSpace(titleName) ? AppHost.Device.Application.TitleName : titleName;
ViewModel.LoadHeading = string.IsNullOrWhiteSpace(titleName) ? string.Format(LocaleManager.Instance["LoadingHeading"], AppHost.Device.Application.TitleName) : titleName;
ViewModel.TitleName = string.IsNullOrWhiteSpace(titleName) ? AppHost.Device.Application.TitleName : titleName;
SwitchToGameControl(startFullscreen);
SwitchToGameControl(startFullscreen);
_currentEmulatedGamePath = path;
Thread gameThread = new Thread(InitializeGame)
{
Name = "GUI.WindowThread"
};
gameThread.Start();
_currentEmulatedGamePath = path;
Thread gameThread = new(InitializeGame)
{
Name = "GUI.WindowThread"
};
gameThread.Start();
});
}
private void InitializeGame()
@@ -518,23 +523,20 @@ namespace Ryujinx.Ava.Ui.Windows
public static void UpdateGraphicsConfig()
{
int resScale = ConfigurationState.Instance.Graphics.ResScale;
float resScaleCustom = ConfigurationState.Instance.Graphics.ResScaleCustom;
GraphicsConfig.ResScale = resScale == -1 ? resScaleCustom : resScale;
GraphicsConfig.MaxAnisotropy = ConfigurationState.Instance.Graphics.MaxAnisotropy;
GraphicsConfig.ShadersDumpPath = ConfigurationState.Instance.Graphics.ShadersDumpPath;
GraphicsConfig.EnableShaderCache = ConfigurationState.Instance.Graphics.EnableShaderCache;
GraphicsConfig.ResScale = ConfigurationState.Instance.Graphics.ResScale == -1 ? ConfigurationState.Instance.Graphics.ResScaleCustom : ConfigurationState.Instance.Graphics.ResScale;
GraphicsConfig.MaxAnisotropy = ConfigurationState.Instance.Graphics.MaxAnisotropy;
GraphicsConfig.ShadersDumpPath = ConfigurationState.Instance.Graphics.ShadersDumpPath;
GraphicsConfig.EnableShaderCache = ConfigurationState.Instance.Graphics.EnableShaderCache;
GraphicsConfig.EnableTextureRecompression = ConfigurationState.Instance.Graphics.EnableTextureRecompression;
GraphicsConfig.EnableMacroHLE = ConfigurationState.Instance.Graphics.EnableMacroHLE;
GraphicsConfig.EnableMacroHLE = ConfigurationState.Instance.Graphics.EnableMacroHLE;
}
public void LoadHotKeys()
{
HotKeyManager.SetHotKey(FullscreenHotKey, new KeyGesture(Key.Enter, KeyModifiers.Alt));
HotKeyManager.SetHotKey(FullscreenHotKey, new KeyGesture(Key.Enter, KeyModifiers.Alt));
HotKeyManager.SetHotKey(FullscreenHotKey2, new KeyGesture(Key.F11));
HotKeyManager.SetHotKey(DockToggleHotKey, new KeyGesture(Key.F9));
HotKeyManager.SetHotKey(ExitHotKey, new KeyGesture(Key.Escape));
HotKeyManager.SetHotKey(DockToggleHotKey, new KeyGesture(Key.F9));
HotKeyManager.SetHotKey(ExitHotKey, new KeyGesture(Key.Escape));
}
public static void SaveConfig()
@@ -546,10 +548,12 @@ namespace Ryujinx.Ava.Ui.Windows
{
ApplicationLibrary.LoadAndSaveMetaData(titleId, appMetadata =>
{
DateTime lastPlayedDateTime = DateTime.Parse(appMetadata.LastPlayed);
double sessionTimePlayed = DateTime.UtcNow.Subtract(lastPlayedDateTime).TotalSeconds;
if (DateTime.TryParse(appMetadata.LastPlayed, out DateTime lastPlayedDateTime))
{
double sessionTimePlayed = DateTime.UtcNow.Subtract(lastPlayedDateTime).TotalSeconds;
appMetadata.TimePlayed += Math.Round(sessionTimePlayed, MidpointRounding.AwayFromZero);
appMetadata.TimePlayed += Math.Round(sessionTimePlayed, MidpointRounding.AwayFromZero);
}
});
}

View File

@@ -75,7 +75,7 @@
Spacing="10">
<ListBox
Name="GameList"
MinHeight="150"
MinHeight="250"
Items="{Binding GameDirectories}" />
<Grid HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>

View File

@@ -16,7 +16,6 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using TimeZone = Ryujinx.Ava.Ui.Models.TimeZone;
namespace Ryujinx.Ava.Ui.Windows
@@ -31,7 +30,7 @@ namespace Ryujinx.Ava.Ui.Windows
{
Title = $"Ryujinx {Program.Version} - {LocaleManager.Instance["Settings"]}";
ViewModel = new SettingsViewModel(virtualFileSystem, contentManager, this);
ViewModel = new SettingsViewModel(virtualFileSystem, contentManager, this);
DataContext = ViewModel;
InitializeComponent();
@@ -48,7 +47,7 @@ namespace Ryujinx.Ava.Ui.Windows
public SettingsWindow()
{
ViewModel = new SettingsViewModel();
ViewModel = new SettingsViewModel();
DataContext = ViewModel;
InitializeComponent();
@@ -79,7 +78,7 @@ namespace Ryujinx.Ava.Ui.Windows
PointerPressed += MouseClick;
IKeyboard keyboard = (IKeyboard)ViewModel.AvaloniaKeyboardDriver.GetGamepad(ViewModel.AvaloniaKeyboardDriver.GamepadsIds[0]);
IKeyboard keyboard = (IKeyboard)ViewModel.AvaloniaKeyboardDriver.GetGamepad(ViewModel.AvaloniaKeyboardDriver.GamepadsIds[0]);
IButtonAssigner assigner = new KeyboardKeyAssigner(keyboard);
_currentAssigner.GetInputAndAssign(assigner);
@@ -92,6 +91,7 @@ namespace Ryujinx.Ava.Ui.Windows
_currentAssigner.Cancel();
_currentAssigner = null;
button.IsChecked = false;
}
}
@@ -122,36 +122,19 @@ namespace Ryujinx.Ava.Ui.Windows
{
if (e.SelectedItem is NavigationViewItem navitem)
{
switch (navitem.Tag.ToString())
NavPanel.Content = navitem.Tag.ToString() switch
{
case "UiPage":
NavPanel.Content = UiPage;
break;
case "InputPage":
NavPanel.Content = InputPage;
break;
case "HotkeysPage":
NavPanel.Content = HotkeysPage;
break;
case "SystemPage":
NavPanel.Content = SystemPage;
break;
case "CpuPage":
NavPanel.Content = CpuPage;
break;
case "GraphicsPage":
NavPanel.Content = GraphicsPage;
break;
case "AudioPage":
NavPanel.Content = AudioPage;
break;
case "NetworkPage":
NavPanel.Content = NetworkPage;
break;
case "LoggingPage":
NavPanel.Content = LoggingPage;
break;
}
"UiPage" => UiPage,
"InputPage" => InputPage,
"HotkeysPage" => HotkeysPage,
"SystemPage" => SystemPage,
"CpuPage" => CpuPage,
"GraphicsPage" => GraphicsPage,
"AudioPage" => AudioPage,
"NetworkPage" => NetworkPage,
"LoggingPage" => LoggingPage,
_ => throw new NotImplementedException()
};
}
}
@@ -178,13 +161,18 @@ namespace Ryujinx.Ava.Ui.Windows
private void RemoveButton_OnClick(object sender, RoutedEventArgs e)
{
List<string> selected = new(GameList.SelectedItems.Cast<string>());
int oldIndex = GameList.SelectedIndex;
foreach (string path in selected)
foreach (string path in new List<string>(GameList.SelectedItems.Cast<string>()))
{
ViewModel.GameDirectories.Remove(path);
ViewModel.DirectoryChanged = true;
}
if (GameList.ItemCount > 0)
{
GameList.SelectedIndex = oldIndex < GameList.ItemCount ? oldIndex : 0;
}
}
private void TimeZoneBox_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
@@ -214,7 +202,6 @@ namespace Ryujinx.Ava.Ui.Windows
private void SaveButton_Clicked(object sender, RoutedEventArgs e)
{
SaveSettings();
Close();
}
@@ -232,7 +219,6 @@ namespace Ryujinx.Ava.Ui.Windows
private void SaveSettings()
{
ViewModel.SaveSettings();
ControllerSettings?.SaveCurrentProfile();
if (Owner is MainWindow window && ViewModel.DirectoryChanged)
@@ -246,8 +232,10 @@ namespace Ryujinx.Ava.Ui.Windows
protected override void OnClosed(EventArgs e)
{
ControllerSettings.Dispose();
_currentAssigner?.Cancel();
_currentAssigner = null;
base.OnClosed(e);
}
}

View File

@@ -131,6 +131,13 @@ namespace Ryujinx.Ava.Ui.Windows
nacpFile.Get.Read(out _, 0, SpanHelpers.AsByteSpan(ref controlData), ReadOption.None).ThrowIfFailure();
TitleUpdates.Add(new TitleUpdateModel(controlData, path));
foreach (var update in TitleUpdates)
{
update.IsEnabled = false;
}
TitleUpdates.Last().IsEnabled = true;
}
else
{

View File

@@ -1,5 +1,6 @@
using System;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Ryujinx.Common
@@ -7,49 +8,15 @@ namespace Ryujinx.Common
public static class BinaryReaderExtensions
{
public unsafe static T ReadStruct<T>(this BinaryReader reader)
where T : struct
where T : unmanaged
{
int size = Marshal.SizeOf<T>();
byte[] data = reader.ReadBytes(size);
fixed (byte* ptr = data)
{
return Marshal.PtrToStructure<T>((IntPtr)ptr);
}
}
public unsafe static T[] ReadStructArray<T>(this BinaryReader reader, int count)
where T : struct
{
int size = Marshal.SizeOf<T>();
T[] result = new T[count];
for (int i = 0; i < count; i++)
{
byte[] data = reader.ReadBytes(size);
fixed (byte* ptr = data)
{
result[i] = Marshal.PtrToStructure<T>((IntPtr)ptr);
}
}
return result;
return MemoryMarshal.Cast<byte, T>(reader.ReadBytes(Unsafe.SizeOf<T>()))[0];
}
public unsafe static void WriteStruct<T>(this BinaryWriter writer, T value)
where T : struct
where T : unmanaged
{
long size = Marshal.SizeOf<T>();
byte[] data = new byte[size];
fixed (byte* ptr = data)
{
Marshal.StructureToPtr<T>(value, (IntPtr)ptr, false);
}
ReadOnlySpan<byte> data = MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateReadOnlySpan(ref value, 1));
writer.Write(data);
}

View File

@@ -180,6 +180,37 @@ namespace Ryujinx.Cpu.Jit
WriteImpl(va, data);
}
/// <inheritdoc/>
public bool WriteWithRedundancyCheck(ulong va, ReadOnlySpan<byte> data)
{
if (data.Length == 0)
{
return false;
}
SignalMemoryTracking(va, (ulong)data.Length, false);
if (IsContiguousAndMapped(va, data.Length))
{
var target = _backingMemory.GetSpan(GetPhysicalAddressInternal(va), data.Length);
bool changed = !data.SequenceEqual(target);
if (changed)
{
data.CopyTo(target);
}
return changed;
}
else
{
WriteImpl(va, data);
return true;
}
}
/// <summary>
/// Writes data to CPU mapped memory.
/// </summary>

View File

@@ -307,6 +307,34 @@ namespace Ryujinx.Cpu.Jit
}
}
/// <inheritdoc/>
public bool WriteWithRedundancyCheck(ulong va, ReadOnlySpan<byte> data)
{
try
{
SignalMemoryTracking(va, (ulong)data.Length, false);
Span<byte> target = _addressSpaceMirror.GetSpan(va, data.Length);
bool changed = !data.SequenceEqual(target);
if (changed)
{
data.CopyTo(target);
}
return changed;
}
catch (InvalidMemoryRegionException)
{
if (_invalidAccessHandler == null || !_invalidAccessHandler(va))
{
throw;
}
return true;
}
}
/// <inheritdoc/>
public ReadOnlySpan<byte> GetSpan(ulong va, int size, bool tracked = false)
{

View File

@@ -1,6 +1,7 @@
using Ryujinx.Memory;
using System;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
@@ -23,34 +24,18 @@ namespace Ryujinx.Cpu
}
}
public unsafe static T Read<T>(IVirtualMemoryManager memory, ulong position) where T : struct
public unsafe static T Read<T>(IVirtualMemoryManager memory, ulong position) where T : unmanaged
{
long size = Marshal.SizeOf<T>();
byte[] data = new byte[size];
memory.Read(position, data);
fixed (byte* ptr = data)
{
return Marshal.PtrToStructure<T>((IntPtr)ptr);
}
return MemoryMarshal.Cast<byte, T>(memory.GetSpan(position, Unsafe.SizeOf<T>()))[0];
}
public unsafe static ulong Write<T>(IVirtualMemoryManager memory, ulong position, T value) where T : struct
public unsafe static ulong Write<T>(IVirtualMemoryManager memory, ulong position, T value) where T : unmanaged
{
long size = Marshal.SizeOf<T>();
byte[] data = new byte[size];
fixed (byte* ptr = data)
{
Marshal.StructureToPtr<T>(value, (IntPtr)ptr, false);
}
ReadOnlySpan<byte> data = MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateReadOnlySpan(ref value, 1));
memory.Write(position, data);
return (ulong)size;
return (ulong)data.Length;
}
public static string ReadAsciiString(IVirtualMemoryManager memory, ulong position, long maxSize = -1)

View File

@@ -0,0 +1,14 @@
namespace Ryujinx.Graphics.GAL
{
public struct BufferAssignment
{
public readonly int Binding;
public readonly BufferRange Range;
public BufferAssignment(int binding, BufferRange range)
{
Binding = binding;
Range = range;
}
}
}

View File

@@ -86,12 +86,12 @@ namespace Ryujinx.Graphics.GAL
void SetStencilTest(StencilTestDescriptor stencilTest);
void SetStorageBuffers(int first, ReadOnlySpan<BufferRange> buffers);
void SetStorageBuffers(ReadOnlySpan<BufferAssignment> buffers);
void SetTextureAndSampler(ShaderStage stage, int binding, ITexture texture, ISampler sampler);
void SetTransformFeedbackBuffers(ReadOnlySpan<BufferRange> buffers);
void SetUniformBuffers(int first, ReadOnlySpan<BufferRange> buffers);
void SetUniformBuffers(ReadOnlySpan<BufferAssignment> buffers);
void SetUserClipDistance(int index, bool enableClip);

View File

@@ -142,6 +142,30 @@ namespace Ryujinx.Graphics.GAL.Multithreading
return ranges;
}
internal Span<BufferAssignment> MapBufferRanges(Span<BufferAssignment> ranges)
{
// Rewrite the buffer ranges to point to the mapped handles.
lock (_bufferMap)
{
for (int i = 0; i < ranges.Length; i++)
{
ref BufferAssignment assignment = ref ranges[i];
BufferRange range = assignment.Range;
BufferHandle result;
if (!_bufferMap.TryGetValue(range.Handle, out result))
{
result = BufferHandle.Null;
}
assignment = new BufferAssignment(ranges[i].Binding, new BufferRange(result, range.Offset, range.Size));
}
}
return ranges;
}
internal Span<VertexBufferDescriptor> MapBufferRanges(Span<VertexBufferDescriptor> ranges)
{
// Rewrite the buffer ranges to point to the mapped handles.

View File

@@ -6,19 +6,17 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands
struct SetStorageBuffersCommand : IGALCommand
{
public CommandType CommandType => CommandType.SetStorageBuffers;
private int _first;
private SpanRef<BufferRange> _buffers;
private SpanRef<BufferAssignment> _buffers;
public void Set(int first, SpanRef<BufferRange> buffers)
public void Set(SpanRef<BufferAssignment> buffers)
{
_first = first;
_buffers = buffers;
}
public static void Run(ref SetStorageBuffersCommand command, ThreadedRenderer threaded, IRenderer renderer)
{
Span<BufferRange> buffers = command._buffers.Get(threaded);
renderer.Pipeline.SetStorageBuffers(command._first, threaded.Buffers.MapBufferRanges(buffers));
Span<BufferAssignment> buffers = command._buffers.Get(threaded);
renderer.Pipeline.SetStorageBuffers(threaded.Buffers.MapBufferRanges(buffers));
command._buffers.Dispose(threaded);
}
}

View File

@@ -6,19 +6,17 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands
struct SetUniformBuffersCommand : IGALCommand
{
public CommandType CommandType => CommandType.SetUniformBuffers;
private int _first;
private SpanRef<BufferRange> _buffers;
private SpanRef<BufferAssignment> _buffers;
public void Set(int first, SpanRef<BufferRange> buffers)
public void Set(SpanRef<BufferAssignment> buffers)
{
_first = first;
_buffers = buffers;
}
public static void Run(ref SetUniformBuffersCommand command, ThreadedRenderer threaded, IRenderer renderer)
{
Span<BufferRange> buffers = command._buffers.Get(threaded);
renderer.Pipeline.SetUniformBuffers(command._first, threaded.Buffers.MapBufferRanges(buffers));
Span<BufferAssignment> buffers = command._buffers.Get(threaded);
renderer.Pipeline.SetUniformBuffers(threaded.Buffers.MapBufferRanges(buffers));
command._buffers.Dispose(threaded);
}
}

View File

@@ -275,9 +275,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading
_renderer.QueueCommand();
}
public void SetStorageBuffers(int first, ReadOnlySpan<BufferRange> buffers)
public void SetStorageBuffers(ReadOnlySpan<BufferAssignment> buffers)
{
_renderer.New<SetStorageBuffersCommand>().Set(first, _renderer.CopySpan(buffers));
_renderer.New<SetStorageBuffersCommand>().Set(_renderer.CopySpan(buffers));
_renderer.QueueCommand();
}
@@ -293,9 +293,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading
_renderer.QueueCommand();
}
public void SetUniformBuffers(int first, ReadOnlySpan<BufferRange> buffers)
public void SetUniformBuffers(ReadOnlySpan<BufferAssignment> buffers)
{
_renderer.New<SetUniformBuffersCommand>().Set(first, _renderer.CopySpan(buffers));
_renderer.New<SetUniformBuffersCommand>().Set(_renderer.CopySpan(buffers));
_renderer.QueueCommand();
}

View File

@@ -422,7 +422,11 @@ namespace Ryujinx.Graphics.GAL.Multithreading
// Stop the GPU thread.
_disposed = true;
_gpuThread.Join();
if (_gpuThread != null && _gpuThread.IsAlive)
{
_gpuThread.Join();
}
// Dispose the renderer.
_baseRenderer.Dispose();
@@ -435,4 +439,4 @@ namespace Ryujinx.Graphics.GAL.Multithreading
Sync.Dispose();
}
}
}
}

View File

@@ -51,16 +51,35 @@ namespace Ryujinx.Graphics.Gpu.Engine.GPFifo
/// </summary>
public uint EntryCount;
/// <summary>
/// Get the entries for the command buffer from memory.
/// </summary>
/// <param name="memoryManager">The memory manager used to fetch the data</param>
/// <param name="flush">If true, flushes potential GPU written data before reading the command buffer</param>
/// <returns>The fetched data</returns>
private ReadOnlySpan<int> GetWords(MemoryManager memoryManager, bool flush)
{
return MemoryMarshal.Cast<byte, int>(memoryManager.GetSpan(EntryAddress, (int)EntryCount * 4, flush));
}
/// <summary>
/// Prefetch the command buffer.
/// </summary>
/// <param name="memoryManager">The memory manager used to fetch the data</param>
public void Prefetch(MemoryManager memoryManager)
{
Words = GetWords(memoryManager, true).ToArray();
}
/// <summary>
/// Fetch the command buffer.
/// </summary>
/// <param name="memoryManager">The memory manager used to fetch the data</param>
/// <param name="flush">If true, flushes potential GPU written data before reading the command buffer</param>
public void Fetch(MemoryManager memoryManager, bool flush = true)
/// <returns>The command buffer words</returns>
public ReadOnlySpan<int> Fetch(MemoryManager memoryManager, bool flush)
{
if (Words == null)
{
Words = MemoryMarshal.Cast<byte, int>(memoryManager.GetSpan(EntryAddress, (int)EntryCount * 4, flush)).ToArray();
}
return Words ?? GetWords(memoryManager, flush);
}
}
@@ -158,7 +177,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.GPFifo
if (beforeBarrier && commandBuffer.Type == CommandBufferType.Prefetch)
{
commandBuffer.Fetch(processor.MemoryManager);
commandBuffer.Prefetch(processor.MemoryManager);
}
if (commandBuffer.Type == CommandBufferType.NoPrefetch)
@@ -199,7 +218,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.GPFifo
}
_currentCommandBuffer = entry;
_currentCommandBuffer.Fetch(entry.Processor.MemoryManager, flushCommandBuffer);
ReadOnlySpan<int> words = entry.Fetch(entry.Processor.MemoryManager, flushCommandBuffer);
// If we are changing the current channel,
// we need to force all the host state to be updated.
@@ -209,7 +228,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.GPFifo
entry.Processor.ForceAllDirty();
}
entry.Processor.Process(entry.EntryAddress, _currentCommandBuffer.Words);
entry.Processor.Process(entry.EntryAddress, words);
}
_interrupt = false;

View File

@@ -8,6 +8,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
/// </summary>
class ConstantBufferUpdater
{
private const int UniformDataCacheSize = 512;
private readonly GpuChannel _channel;
private readonly DeviceStateWithShadow<ThreedClassState> _state;
@@ -16,6 +18,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
private ulong _ubBeginCpuAddress = 0;
private ulong _ubFollowUpAddress = 0;
private ulong _ubByteCount = 0;
private int _ubIndex = 0;
private int[] _ubData = new int[UniformDataCacheSize];
/// <summary>
/// Creates a new instance of the constant buffer updater.
@@ -108,9 +112,16 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
if (_ubFollowUpAddress != 0)
{
var memoryManager = _channel.MemoryManager;
memoryManager.Physical.BufferCache.ForceDirty(memoryManager, _ubFollowUpAddress - _ubByteCount, _ubByteCount);
Span<byte> data = MemoryMarshal.Cast<int, byte>(_ubData.AsSpan(0, (int)(_ubByteCount / 4)));
if (memoryManager.Physical.WriteWithRedundancyCheck(_ubBeginCpuAddress, data))
{
memoryManager.Physical.BufferCache.ForceDirty(memoryManager, _ubFollowUpAddress - _ubByteCount, _ubByteCount);
}
_ubFollowUpAddress = 0;
_ubIndex = 0;
}
}
@@ -124,7 +135,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
ulong address = uniformBuffer.Address.Pack() + (uint)uniformBuffer.Offset;
if (_ubFollowUpAddress != address)
if (_ubFollowUpAddress != address || _ubIndex == _ubData.Length)
{
FlushUboDirty();
@@ -132,8 +143,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
_ubBeginCpuAddress = _channel.MemoryManager.Translate(address);
}
var byteData = MemoryMarshal.Cast<int, byte>(MemoryMarshal.CreateSpan(ref argument, 1));
_channel.MemoryManager.Physical.WriteUntracked(_ubBeginCpuAddress + _ubByteCount, byteData);
_ubData[_ubIndex++] = argument;
_ubFollowUpAddress = address + 4;
_ubByteCount += 4;
@@ -153,7 +163,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
ulong size = (ulong)data.Length * 4;
if (_ubFollowUpAddress != address)
if (_ubFollowUpAddress != address || _ubIndex + data.Length > _ubData.Length)
{
FlushUboDirty();
@@ -161,8 +171,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
_ubBeginCpuAddress = _channel.MemoryManager.Translate(address);
}
var byteData = MemoryMarshal.Cast<int, byte>(data);
_channel.MemoryManager.Physical.WriteUntracked(_ubBeginCpuAddress + _ubByteCount, byteData);
data.CopyTo(_ubData.AsSpan(_ubIndex));
_ubIndex += data.Length;
_ubFollowUpAddress = address + size;
_ubByteCount += size;

View File

@@ -372,7 +372,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
float srcX1 = ((float)_state.State.DrawTextureDuDx / (1UL << 32)) * dstWidth + srcX0;
float srcY1 = ((float)_state.State.DrawTextureDvDy / (1UL << 32)) * dstHeight + srcY0;
engine.UpdateState();
engine.UpdateState(ulong.MaxValue & ~(1UL << StateUpdater.ShaderStateIndex));
_channel.TextureManager.UpdateRenderTargets();
int textureId = _state.State.DrawTextureTextureId;
int samplerId = _state.State.DrawTextureSamplerId;

View File

@@ -22,6 +22,10 @@ namespace Ryujinx.Graphics.Gpu.Memory
private readonly GpuContext _context;
private readonly PhysicalMemory _physicalMemory;
/// <remarks>
/// Only modified from the GPU thread. Must lock for add/remove.
/// Must lock for any access from other threads.
/// </remarks>
private readonly RangeList<Buffer> _buffers;
private Buffer[] _bufferOverlaps;
@@ -200,12 +204,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="size">Size in bytes of the buffer</param>
private void CreateBufferAligned(ulong address, ulong size)
{
int overlapsCount;
lock (_buffers)
{
overlapsCount = _buffers.FindOverlapsNonOverlapping(address, size, ref _bufferOverlaps);
}
int overlapsCount = _buffers.FindOverlapsNonOverlapping(address, size, ref _bufferOverlaps);
if (overlapsCount != 0)
{
@@ -410,10 +409,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
if (size != 0)
{
lock (_buffers)
{
buffer = _buffers.FindFirstOverlap(address, size);
}
buffer = _buffers.FindFirstOverlap(address, size);
buffer.SynchronizeMemory(address, size);
@@ -424,10 +420,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
}
else
{
lock (_buffers)
{
buffer = _buffers.FindFirstOverlap(address, 1);
}
buffer = _buffers.FindFirstOverlap(address, 1);
}
return buffer;
@@ -442,12 +435,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
{
if (size != 0)
{
Buffer buffer;
lock (_buffers)
{
buffer = _buffers.FindFirstOverlap(address, size);
}
Buffer buffer = _buffers.FindFirstOverlap(address, size);
buffer.SynchronizeMemory(address, size);
}

View File

@@ -24,7 +24,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
private readonly VertexBuffer[] _vertexBuffers;
private readonly BufferBounds[] _transformFeedbackBuffers;
private readonly List<BufferTextureBinding> _bufferTextures;
private readonly BufferRange[] _ranges;
private readonly BufferAssignment[] _ranges;
/// <summary>
/// Holds shader stage buffer state and binding information.
@@ -134,7 +134,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
_bufferTextures = new List<BufferTextureBinding>();
_ranges = new BufferRange[Constants.TotalGpUniformBuffers * Constants.ShaderStages];
_ranges = new BufferAssignment[Constants.TotalGpUniformBuffers * Constants.ShaderStages];
}
@@ -618,10 +618,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void BindBuffers(BufferCache bufferCache, BuffersPerStage[] bindings, bool isStorage)
{
int rangesFirst = 0;
int rangesCount = 0;
Span<BufferRange> ranges = _ranges;
Span<BufferAssignment> ranges = _ranges;
for (ShaderStage stage = ShaderStage.Vertex; stage <= ShaderStage.Fragment; stage++)
{
@@ -640,25 +639,14 @@ namespace Ryujinx.Graphics.Gpu.Memory
? bufferCache.GetBufferRangeTillEnd(bounds.Address, bounds.Size, isWrite)
: bufferCache.GetBufferRange(bounds.Address, bounds.Size);
if (rangesCount == 0)
{
rangesFirst = bindingInfo.Binding;
}
else if (bindingInfo.Binding != rangesFirst + rangesCount)
{
SetHostBuffers(ranges, rangesFirst, rangesCount, isStorage);
rangesFirst = bindingInfo.Binding;
rangesCount = 0;
}
ranges[rangesCount++] = range;
ranges[rangesCount++] = new BufferAssignment(bindingInfo.Binding, range);
}
}
}
if (rangesCount != 0)
{
SetHostBuffers(ranges, rangesFirst, rangesCount, isStorage);
SetHostBuffers(ranges, rangesCount, isStorage);
}
}
@@ -671,10 +659,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void BindBuffers(BufferCache bufferCache, BuffersPerStage buffers, bool isStorage)
{
int rangesFirst = 0;
int rangesCount = 0;
Span<BufferRange> ranges = _ranges;
Span<BufferAssignment> ranges = _ranges;
for (int index = 0; index < buffers.Count; index++)
{
@@ -689,24 +676,13 @@ namespace Ryujinx.Graphics.Gpu.Memory
? bufferCache.GetBufferRangeTillEnd(bounds.Address, bounds.Size, isWrite)
: bufferCache.GetBufferRange(bounds.Address, bounds.Size);
if (rangesCount == 0)
{
rangesFirst = bindingInfo.Binding;
}
else if (bindingInfo.Binding != rangesFirst + rangesCount)
{
SetHostBuffers(ranges, rangesFirst, rangesCount, isStorage);
rangesFirst = bindingInfo.Binding;
rangesCount = 0;
}
ranges[rangesCount++] = range;
ranges[rangesCount++] = new BufferAssignment(bindingInfo.Binding, range);
}
}
if (rangesCount != 0)
{
SetHostBuffers(ranges, rangesFirst, rangesCount, isStorage);
SetHostBuffers(ranges, rangesCount, isStorage);
}
}
@@ -718,15 +694,15 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="count">Number of bindings</param>
/// <param name="isStorage">Indicates if the buffers are storage or uniform buffers</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void SetHostBuffers(ReadOnlySpan<BufferRange> ranges, int first, int count, bool isStorage)
private void SetHostBuffers(ReadOnlySpan<BufferAssignment> ranges, int count, bool isStorage)
{
if (isStorage)
{
_context.Renderer.Pipeline.SetStorageBuffers(first, ranges.Slice(0, count));
_context.Renderer.Pipeline.SetStorageBuffers(ranges.Slice(0, count));
}
else
{
_context.Renderer.Pipeline.SetUniformBuffers(first, ranges.Slice(0, count));
_context.Renderer.Pipeline.SetUniformBuffers(ranges.Slice(0, count));
}
}

View File

@@ -242,6 +242,19 @@ namespace Ryujinx.Graphics.Gpu.Memory
WriteImpl(range, data, _cpuMemory.WriteUntracked);
}
/// <summary>
/// Writes data to the application process, returning false if the data was not changed.
/// This triggers read memory tracking, as a redundancy check would be useless if the data is not up to date.
/// </summary>
/// <remarks>The memory manager can return that memory has changed when it hasn't to avoid expensive data copies.</remarks>
/// <param name="address">Address to write into</param>
/// <param name="data">Data to be written</param>
/// <returns>True if the data was changed, false otherwise</returns>
public bool WriteWithRedundancyCheck(ulong address, ReadOnlySpan<byte> data)
{
return _cpuMemory.WriteWithRedundancyCheck(address, data);
}
private delegate void WriteCallback(ulong address, ReadOnlySpan<byte> data);
/// <summary>

View File

@@ -1296,9 +1296,9 @@ namespace Ryujinx.Graphics.OpenGL
_stencilFrontMask = stencilTest.FrontMask;
}
public void SetStorageBuffers(int first, ReadOnlySpan<BufferRange> buffers)
public void SetStorageBuffers(ReadOnlySpan<BufferAssignment> buffers)
{
SetBuffers(first, buffers, isStorage: true);
SetBuffers(buffers, isStorage: true);
}
public void SetTextureAndSampler(ShaderStage stage, int binding, ITexture texture, ISampler sampler)
@@ -1366,9 +1366,9 @@ namespace Ryujinx.Graphics.OpenGL
}
}
public void SetUniformBuffers(int first, ReadOnlySpan<BufferRange> buffers)
public void SetUniformBuffers(ReadOnlySpan<BufferAssignment> buffers)
{
SetBuffers(first, buffers, isStorage: false);
SetBuffers(buffers, isStorage: false);
}
public void SetUserClipDistance(int index, bool enableClip)
@@ -1460,21 +1460,22 @@ namespace Ryujinx.Graphics.OpenGL
GL.MemoryBarrier(MemoryBarrierFlags.TextureFetchBarrierBit);
}
private void SetBuffers(int first, ReadOnlySpan<BufferRange> buffers, bool isStorage)
private void SetBuffers(ReadOnlySpan<BufferAssignment> buffers, bool isStorage)
{
BufferRangeTarget target = isStorage ? BufferRangeTarget.ShaderStorageBuffer : BufferRangeTarget.UniformBuffer;
for (int index = 0; index < buffers.Length; index++)
{
BufferRange buffer = buffers[index];
BufferAssignment assignment = buffers[index];
BufferRange buffer = assignment.Range;
if (buffer.Handle == BufferHandle.Null)
{
GL.BindBufferRange(target, first + index, 0, IntPtr.Zero, 0);
GL.BindBufferRange(target, assignment.Binding, 0, IntPtr.Zero, 0);
continue;
}
GL.BindBufferRange(target, first + index, buffer.Handle.ToInt32(), (IntPtr)buffer.Offset, buffer.Size);
GL.BindBufferRange(target, assignment.Binding, buffer.Handle.ToInt32(), (IntPtr)buffer.Offset, buffer.Size);
}
}

View File

@@ -54,4 +54,4 @@ namespace Ryujinx.Graphics.OpenGL.Queries
}
}
}
}
}

View File

@@ -10,6 +10,8 @@ namespace Ryujinx.Graphics.OpenGL
private const int TextureCount = 3;
private readonly OpenGLRenderer _renderer;
private bool _initialized;
private int _width;
private int _height;
private int _copyFramebufferHandle;
@@ -179,6 +181,7 @@ namespace Ryujinx.Graphics.OpenGL
public void InitializeBackgroundContext(IOpenGLContext baseContext)
{
BackgroundContext = new BackgroundContextWorker(baseContext);
_initialized = true;
}
public void CaptureFrame(int x, int y, int width, int height, bool isBgra, bool flipX, bool flipY)
@@ -193,6 +196,11 @@ namespace Ryujinx.Graphics.OpenGL
public void Dispose()
{
if (!_initialized)
{
return;
}
BackgroundContext.Dispose();
if (_copyFramebufferHandle != 0)
@@ -203,4 +211,4 @@ namespace Ryujinx.Graphics.OpenGL
}
}
}
}
}

View File

@@ -163,12 +163,13 @@ namespace Ryujinx.Graphics.Vulkan
SignalDirty(DirtyFlags.Image);
}
public void SetStorageBuffers(CommandBuffer commandBuffer, int first, ReadOnlySpan<BufferRange> buffers)
public void SetStorageBuffers(CommandBuffer commandBuffer, ReadOnlySpan<BufferAssignment> buffers)
{
for (int i = 0; i < buffers.Length; i++)
{
var buffer = buffers[i];
int index = first + i;
var assignment = buffers[i];
var buffer = assignment.Range;
int index = assignment.Binding;
Auto<DisposableBuffer> vkBuffer = _gd.BufferManager.GetBuffer(commandBuffer, buffer.Handle, false);
ref Auto<DisposableBuffer> currentVkBuffer = ref _storageBufferRefs[index];
@@ -243,12 +244,13 @@ namespace Ryujinx.Graphics.Vulkan
SignalDirty(DirtyFlags.Texture);
}
public void SetUniformBuffers(CommandBuffer commandBuffer, int first, ReadOnlySpan<BufferRange> buffers)
public void SetUniformBuffers(CommandBuffer commandBuffer, ReadOnlySpan<BufferAssignment> buffers)
{
for (int i = 0; i < buffers.Length; i++)
{
var buffer = buffers[i];
int index = first + i;
var assignment = buffers[i];
var buffer = assignment.Range;
int index = assignment.Binding;
Auto<DisposableBuffer> vkBuffer = _gd.BufferManager.GetBuffer(commandBuffer, buffer.Handle, false);
ref Auto<DisposableBuffer> currentVkBuffer = ref _uniformBufferRefs[index];

View File

@@ -177,7 +177,7 @@ namespace Ryujinx.Graphics.Vulkan
gd.BufferManager.SetData<float>(bufferHandle, 0, region);
_pipeline.SetUniformBuffers(1, stackalloc[] { new BufferRange(bufferHandle, 0, RegionBufferSize) });
_pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(1, new BufferRange(bufferHandle, 0, RegionBufferSize)) });
Span<GAL.Viewport> viewports = stackalloc GAL.Viewport[1];
@@ -240,7 +240,7 @@ namespace Ryujinx.Graphics.Vulkan
gd.BufferManager.SetData<float>(bufferHandle, 0, clearColor);
_pipeline.SetUniformBuffers(1, stackalloc[] { new BufferRange(bufferHandle, 0, ClearColorBufferSize) });
_pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(1, new BufferRange(bufferHandle, 0, ClearColorBufferSize)) });
Span<GAL.Viewport> viewports = stackalloc GAL.Viewport[1];
@@ -302,7 +302,7 @@ namespace Ryujinx.Graphics.Vulkan
gd.BufferManager.SetData<float>(bufferHandle, 0, region);
pipeline.SetUniformBuffers(1, stackalloc[] { new BufferRange(bufferHandle, 0, RegionBufferSize) });
pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(1, new BufferRange(bufferHandle, 0, RegionBufferSize)) });
Span<GAL.Viewport> viewports = stackalloc GAL.Viewport[1];
@@ -380,7 +380,7 @@ namespace Ryujinx.Graphics.Vulkan
_pipeline.SetCommandBuffer(cbs);
_pipeline.SetUniformBuffers(0, stackalloc[] { new BufferRange(bufferHandle, 0, ParamsBufferSize) });
_pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(0, new BufferRange(bufferHandle, 0, ParamsBufferSize)) });
Span<Auto<DisposableBuffer>> sbRanges = new Auto<DisposableBuffer>[2];
@@ -571,7 +571,7 @@ namespace Ryujinx.Graphics.Vulkan
int conversionType = srcIsMs ? src.Info.BytesPerPixel : -src.Info.BytesPerPixel;
_pipeline.Specialize(conversionType);
_pipeline.SetUniformBuffers(0, stackalloc[] { new BufferRange(bufferHandle, 0, ParamsBufferSize) });
_pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(0, new BufferRange(bufferHandle, 0, ParamsBufferSize)) });
if (src.Info.Target == Target.Texture2DMultisampleArray ||
dst.Info.Target == Target.Texture2DMultisampleArray)
@@ -776,7 +776,7 @@ namespace Ryujinx.Graphics.Vulkan
srcIndirectBufferOffset,
indirectDataSize);
_pipeline.SetUniformBuffers(0, stackalloc[] { drawCountBufferAligned });
_pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(0, drawCountBufferAligned) });
_pipeline.SetStorageBuffers(1, new[] { srcIndirectBuffer.GetBuffer(), dstIndirectBuffer.GetBuffer(), patternBuffer.GetBuffer() });
_pipeline.SetProgram(_programConvertIndirectData);
@@ -804,7 +804,7 @@ namespace Ryujinx.Graphics.Vulkan
0,
convertedCount * outputIndexSize);
_pipeline.SetUniformBuffers(0, stackalloc[] { new BufferRange(patternBufferHandle, 0, ParamsBufferSize) });
_pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(0, new BufferRange(patternBufferHandle, 0, ParamsBufferSize)) });
_pipeline.SetStorageBuffers(1, new[] { srcIndexBuffer.GetBuffer(), dstIndexBuffer.GetBuffer() });
_pipeline.SetProgram(_programConvertIndexBuffer);

View File

@@ -973,9 +973,9 @@ namespace Ryujinx.Graphics.Vulkan
SignalStateChange();
}
public void SetStorageBuffers(int first, ReadOnlySpan<BufferRange> buffers)
public void SetStorageBuffers(ReadOnlySpan<BufferAssignment> buffers)
{
_descriptorSetUpdater.SetStorageBuffers(CommandBuffer, first, buffers);
_descriptorSetUpdater.SetStorageBuffers(CommandBuffer, buffers);
}
public void SetStorageBuffers(int first, ReadOnlySpan<Auto<DisposableBuffer>> buffers)
@@ -1013,9 +1013,9 @@ namespace Ryujinx.Graphics.Vulkan
}
}
public void SetUniformBuffers(int first, ReadOnlySpan<BufferRange> buffers)
public void SetUniformBuffers(ReadOnlySpan<BufferAssignment> buffers)
{
_descriptorSetUpdater.SetUniformBuffers(CommandBuffer, first, buffers);
_descriptorSetUpdater.SetUniformBuffers(CommandBuffer, buffers);
}
public void SetUserClipDistance(int index, bool enableClip)

View File

@@ -22,6 +22,8 @@ namespace Ryujinx.Graphics.Vulkan
private Device _device;
private WindowBase _window;
private bool _initialized;
internal FormatCapabilities FormatCapabilities { get; private set; }
internal HardwareCapabilities Capabilities;
@@ -266,6 +268,8 @@ namespace Ryujinx.Graphics.Vulkan
LoadFeatures(supportedExtensions, maxQueueCount, queueFamilyIndex);
_window = new Window(this, _surface, _physicalDevice, _device);
_initialized = true;
}
public BufferHandle CreateBuffer(int size)
@@ -573,6 +577,11 @@ namespace Ryujinx.Graphics.Vulkan
public unsafe void Dispose()
{
if (!_initialized)
{
return;
}
CommandBufferPool.Dispose();
BackgroundResources.Dispose();
_counters.Dispose();
@@ -613,4 +622,4 @@ namespace Ryujinx.Graphics.Vulkan
Api.DestroyInstance(_instance, null);
}
}
}
}

View File

@@ -172,9 +172,11 @@ namespace Ryujinx.HLE.FileSystem
fsServerClient = horizon.CreatePrivilegedHorizonClient();
var fsServer = new FileSystemServer(fsServerClient);
DefaultFsServerObjects fsServerObjects = DefaultFsServerObjects.GetDefaultEmulatedCreators(serverBaseFs, KeySet, fsServer);
RandomDataGenerator randomGenerator = buffer => Random.Shared.NextBytes(buffer);
// Use our own encrypted fs creator that always uses all-zero keys
DefaultFsServerObjects fsServerObjects = DefaultFsServerObjects.GetDefaultEmulatedCreators(serverBaseFs, KeySet, fsServer, randomGenerator);
// Use our own encrypted fs creator that doesn't actually do any encryption
fsServerObjects.FsCreators.EncryptedFileSystemCreator = new EncryptedFileSystemCreator();
GameCard = fsServerObjects.GameCard;
@@ -186,7 +188,8 @@ namespace Ryujinx.HLE.FileSystem
{
DeviceOperator = fsServerObjects.DeviceOperator,
ExternalKeySet = KeySet.ExternalKeySet,
FsCreators = fsServerObjects.FsCreators
FsCreators = fsServerObjects.FsCreators,
RandomGenerator = randomGenerator
};
FileSystemServerInitializer.InitializeWithConfig(fsServerClient, fsServer, fsServerConfig);

View File

@@ -1,4 +1,5 @@
using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Common;
using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Common.Logging;
using Ryujinx.HLE.HOS.Applets.SoftwareKeyboard;
using Ryujinx.HLE.HOS.Services.Am.AppletAE;
@@ -9,6 +10,7 @@ using Ryujinx.Memory;
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
@@ -78,13 +80,13 @@ namespace Ryujinx.HLE.HOS.Applets
var launchParams = _normalSession.Pop();
var keyboardConfig = _normalSession.Pop();
_isBackground = keyboardConfig.Length == Marshal.SizeOf<SoftwareKeyboardInitialize>();
_isBackground = keyboardConfig.Length == Unsafe.SizeOf<SoftwareKeyboardInitialize>();
if (_isBackground)
{
// Initialize the keyboard applet in background mode.
_keyboardBackgroundInitialize = ReadStruct<SoftwareKeyboardInitialize>(keyboardConfig);
_keyboardBackgroundInitialize = MemoryMarshal.Read<SoftwareKeyboardInitialize>(keyboardConfig);
_backgroundState = InlineKeyboardState.Uninitialized;
if (_device.UiHandler == null)
@@ -342,7 +344,7 @@ namespace Ryujinx.HLE.HOS.Applets
else
{
int wordsCount = reader.ReadInt32();
int wordSize = Marshal.SizeOf<SoftwareKeyboardUserWord>();
int wordSize = Unsafe.SizeOf<SoftwareKeyboardUserWord>();
remaining = stream.Length - stream.Position;
if (wordsCount > MaxUserWords)
@@ -359,8 +361,7 @@ namespace Ryujinx.HLE.HOS.Applets
for (int word = 0; word < wordsCount; word++)
{
byte[] wordData = reader.ReadBytes(wordSize);
_keyboardBackgroundUserWords[word] = ReadStruct<SoftwareKeyboardUserWord>(wordData);
_keyboardBackgroundUserWords[word] = reader.ReadStruct<SoftwareKeyboardUserWord>();
}
}
}
@@ -369,27 +370,25 @@ namespace Ryujinx.HLE.HOS.Applets
case InlineKeyboardRequest.SetCustomizeDic:
// Read the custom dic data.
remaining = stream.Length - stream.Position;
if (remaining != Marshal.SizeOf<SoftwareKeyboardCustomizeDic>())
if (remaining != Unsafe.SizeOf<SoftwareKeyboardCustomizeDic>())
{
Logger.Warning?.Print(LogClass.ServiceAm, $"Received invalid Software Keyboard Customize Dic of {remaining} bytes");
}
else
{
var keyboardDicData = reader.ReadBytes((int)remaining);
_keyboardBackgroundDic = ReadStruct<SoftwareKeyboardCustomizeDic>(keyboardDicData);
_keyboardBackgroundDic = reader.ReadStruct<SoftwareKeyboardCustomizeDic>();
}
break;
case InlineKeyboardRequest.SetCustomizedDictionaries:
// Read the custom dictionaries data.
remaining = stream.Length - stream.Position;
if (remaining != Marshal.SizeOf<SoftwareKeyboardDictSet>())
if (remaining != Unsafe.SizeOf<SoftwareKeyboardDictSet>())
{
Logger.Warning?.Print(LogClass.ServiceAm, $"Received invalid Software Keyboard DictSet of {remaining} bytes");
}
else
{
var keyboardDictData = reader.ReadBytes((int)remaining);
_keyboardBackgroundDictSet = ReadStruct<SoftwareKeyboardDictSet>(keyboardDictData);
_keyboardBackgroundDictSet = reader.ReadStruct<SoftwareKeyboardDictSet>();
}
break;
case InlineKeyboardRequest.Calc:

View File

@@ -5,10 +5,9 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
/// <summary>
/// A structure used by SetCustomizeDic request to software keyboard.
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 4)]
[StructLayout(LayoutKind.Sequential, Size = 0x70)]
struct SoftwareKeyboardCustomizeDic
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 112)]
public byte[] Unknown;
// Unknown
}
}

View File

@@ -1,4 +1,5 @@
using System.Runtime.InteropServices;
using Ryujinx.Common.Memory;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
{
@@ -21,8 +22,7 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
/// <summary>
/// Array of word entries in the buffer.
/// </summary>
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 24)]
public ulong[] Entries;
public Array24<ulong> Entries;
/// <summary>
/// Number of used entries in the Entries field.

View File

@@ -5,10 +5,9 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
/// <summary>
/// A structure used by SetUserWordInfo request to the software keyboard.
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 4)]
[StructLayout(LayoutKind.Sequential, Size = 0x64)]
struct SoftwareKeyboardUserWord
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 100)]
public byte[] Unknown;
// Unknown
}
}

View File

@@ -850,7 +850,7 @@ namespace Ryujinx.HLE.HOS
for (int i = 0; i < programCount; i++)
{
mapInfo[i].ProgramId = new ProgramId(applicationId + (uint)i);
mapInfo[i].MainProgramId = new ProgramId(applicationId);
mapInfo[i].MainProgramId = new ApplicationId(applicationId);
mapInfo[i].ProgramIndex = (byte)i;
}

View File

@@ -479,7 +479,10 @@ namespace Ryujinx.HLE.HOS
AudioRendererManager.Dispose();
LibHacHorizonManager.PmClient.Fs.UnregisterProgram(LibHacHorizonManager.ApplicationClient.Os.GetCurrentProcessId().Value).ThrowIfFailure();
if (LibHacHorizonManager.ApplicationClient != null)
{
LibHacHorizonManager.PmClient.Fs.UnregisterProgram(LibHacHorizonManager.ApplicationClient.Os.GetCurrentProcessId().Value).ThrowIfFailure();
}
KernelContext.Dispose();
}

View File

@@ -60,8 +60,6 @@ namespace Ryujinx.HLE.HOS
virtualFileSystem.InitializeFsServer(Server, out var fsClient);
FsClient = fsClient;
CleanSdCardDirectory();
}
public void InitializeSystemClients()
@@ -78,27 +76,6 @@ namespace Ryujinx.HLE.HOS
ApplicationClient = Server.CreateHorizonClient(new ProgramLocation(programId, StorageId.BuiltInUser), npdm.FsAccessControlData, npdm.FsAccessControlDescriptor);
}
// This function was added to avoid errors that come from a user's keys or SD encryption seed changing.
// Catching these errors and recreating the file ended up not working because of the different ways
// applications respond to a file suddenly containing all zeros or having a length of zero.
// Clearing the SD card save directory was determined to be the best option for the moment since
// the saves on the SD card are meant as caches that can be deleted at any time.
private void CleanSdCardDirectory()
{
Result rc = RyujinxClient.Fs.MountSdCard("sdcard".ToU8Span());
if (rc.IsFailure()) return;
try
{
RyujinxClient.Fs.CleanDirectoryRecursively("sdcard:/Nintendo/save".ToU8Span()).IgnoreResult();
RyujinxClient.Fs.DeleteDirectoryRecursively("sdcard:/save".ToU8Span()).IgnoreResult();
}
finally
{
RyujinxClient.Fs.Unmount("sdcard".ToU8Span());
}
}
private static AccessControlBits.Bits AccountFsPermissions => AccessControlBits.Bits.SystemSaveData |
AccessControlBits.Bits.GameCard |
AccessControlBits.Bits.SaveDataMeta |

View File

@@ -1,9 +1,12 @@
using Ryujinx.HLE.HOS.Services.Account.Acc;
using Ryujinx.Common.Memory;
using Ryujinx.HLE.HOS.Services.Account.Acc;
using System;
using System.Runtime.InteropServices;
using System.Text;
namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator.FriendService
{
[StructLayout(LayoutKind.Sequential, Pack = 0x8, CharSet = CharSet.Ansi)]
[StructLayout(LayoutKind.Sequential, Pack = 0x8)]
struct UserPresence
{
public UserId UserId;
@@ -13,15 +16,20 @@ namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator.FriendService
[MarshalAs(UnmanagedType.I1)]
public bool SamePresenceGroupApplication;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x3)]
public char[] Unknown;
public Array3<byte> Unknown;
private AppKeyValueStorageHolder _appKeyValueStorage;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 0xC0)]
public char[] AppKeyValueStorage;
public Span<byte> AppKeyValueStorage => MemoryMarshal.Cast<AppKeyValueStorageHolder, byte>(MemoryMarshal.CreateSpan(ref _appKeyValueStorage, AppKeyValueStorageHolder.Size));
[StructLayout(LayoutKind.Sequential, Pack = 0x1, Size = Size)]
private struct AppKeyValueStorageHolder
{
public const int Size = 0xC0;
}
public override string ToString()
{
return $"UserPresence {{ UserId: {UserId}, LastTimeOnlineTimestamp: {LastTimeOnlineTimestamp}, Status: {Status}, AppKeyValueStorage: {AppKeyValueStorage} }}";
return $"UserPresence {{ UserId: {UserId}, LastTimeOnlineTimestamp: {LastTimeOnlineTimestamp}, Status: {Status}, AppKeyValueStorage: {Encoding.ASCII.GetString(AppKeyValueStorage)} }}";
}
}
}

View File

@@ -43,6 +43,16 @@ namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator
return ResultCode.Success;
}
[CommandHipc(1)]
// nn::friends::Cancel()
public ResultCode Cancel(ServiceCtx context)
{
// TODO: Original service sets an internal field to 1 here. Determine usage.
Logger.Stub?.PrintStub(LogClass.ServiceFriend);
return ResultCode.Success;
}
[CommandHipc(10100)]
// nn::friends::GetFriendListIds(int offset, nn::account::Uid userId, nn::friends::detail::ipc::SizedFriendFilter friendFilter, ulong pidPlaceHolder, pid)
// -> int outCount, array<nn::account::NetworkServiceAccountId, 0xa>
@@ -226,23 +236,14 @@ namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator
ulong position = context.Request.PtrBuff[0].Position;
ulong size = context.Request.PtrBuff[0].Size;
byte[] bufferContent = new byte[size];
context.Memory.Read(position, bufferContent);
ReadOnlySpan<UserPresence> userPresenceInputArray = MemoryMarshal.Cast<byte, UserPresence>(context.Memory.GetSpan(position, (int)size));
if (uuid.IsNull)
{
return ResultCode.InvalidArgument;
}
int elementCount = bufferContent.Length / Marshal.SizeOf<UserPresence>();
using (BinaryReader bufferReader = new BinaryReader(new MemoryStream(bufferContent)))
{
UserPresence[] userPresenceInputArray = bufferReader.ReadStructArray<UserPresence>(elementCount);
Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { UserId = uuid.ToString(), userPresenceInputArray });
}
Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { UserId = uuid.ToString(), userPresenceInputArray = userPresenceInputArray.ToArray() });
return ResultCode.Success;
}

View File

@@ -1,15 +1,13 @@
using System.Runtime.InteropServices;
using Ryujinx.Common.Memory;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator.NotificationService
{
[StructLayout(LayoutKind.Sequential, Pack = 0x8, Size = 0x10)]
[StructLayout(LayoutKind.Sequential, Size = 0x10)]
struct NotificationInfo
{
public NotificationEventType Type;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x4)]
public char[] Padding;
private Array4<byte> _padding;
public long NetworkUserIdPlaceholder;
}
}

View File

@@ -1,6 +1,7 @@
using LibHac;
using LibHac.Common;
using LibHac.Fs;
using GameCardHandle = System.UInt32;
namespace Ryujinx.HLE.HOS.Services.Fs
{
@@ -41,7 +42,7 @@ namespace Ryujinx.HLE.HOS.Services.Fs
{
Result result = _baseOperator.Get.GetGameCardHandle(out GameCardHandle handle);
context.ResponseData.Write(handle.Value);
context.ResponseData.Write(handle);
return (ResultCode)result.Value;
}

View File

@@ -19,6 +19,7 @@ using static Ryujinx.HLE.Utilities.StringUtils;
using IFileSystem = LibHac.FsSrv.Sf.IFileSystem;
using IStorage = LibHac.FsSrv.Sf.IStorage;
using RightsId = LibHac.Fs.RightsId;
using GameCardHandle = System.UInt32;
namespace Ryujinx.HLE.HOS.Services.Fs
{
@@ -239,7 +240,7 @@ namespace Ryujinx.HLE.HOS.Services.Fs
// OpenGameCardStorage(u32 handle, u32 partitionId) -> object<nn::fssrv::sf::IStorage>
public ResultCode OpenGameCardStorage(ServiceCtx context)
{
GameCardHandle handle = new GameCardHandle(context.RequestData.ReadInt32());
GameCardHandle handle = context.RequestData.ReadUInt32();
GameCardPartitionRaw partitionId = (GameCardPartitionRaw)context.RequestData.ReadInt32();
using var storage = new SharedRef<IStorage>();
@@ -255,7 +256,7 @@ namespace Ryujinx.HLE.HOS.Services.Fs
// OpenGameCardFileSystem(u32 handle, u32 partitionId) -> object<nn::fssrv::sf::IFileSystem>
public ResultCode OpenGameCardFileSystem(ServiceCtx context)
{
GameCardHandle handle = new GameCardHandle(context.RequestData.ReadInt32());
GameCardHandle handle = context.RequestData.ReadUInt32();
GameCardPartition partitionId = (GameCardPartition)context.RequestData.ReadInt32();
using var fileSystem = new SharedRef<IFileSystem>();
@@ -315,6 +316,17 @@ namespace Ryujinx.HLE.HOS.Services.Fs
return (ResultCode)_baseFileSystemProxy.Get.CreateSaveDataFileSystemWithHashSalt(in attribute, in creationInfo, in metaCreateInfo, in hashSalt).Value;
}
[CommandHipc(37)] // 14.0.0+
// CreateSaveDataFileSystemWithCreationInfo2(buffer<nn::fs::SaveDataCreationInfo2, 25> creationInfo) -> ()
public ResultCode CreateSaveDataFileSystemWithCreationInfo2(ServiceCtx context)
{
byte[] creationInfoBuffer = new byte[context.Request.SendBuff[0].Size];
context.Memory.Read(context.Request.SendBuff[0].Position, creationInfoBuffer);
ref readonly SaveDataCreationInfo2 creationInfo = ref SpanHelpers.AsReadOnlyStruct<SaveDataCreationInfo2>(creationInfoBuffer);
return (ResultCode)_baseFileSystemProxy.Get.CreateSaveDataFileSystemWithCreationInfo2(in creationInfo).Value;
}
[CommandHipc(51)]
// OpenSaveDataFileSystem(u8 spaceId, nn::fs::SaveDataAttribute attribute) -> object<nn::fssrv::sf::IFileSystem> saveDataFs
public ResultCode OpenSaveDataFileSystem(ServiceCtx context)

View File

@@ -5,6 +5,7 @@ using Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService.Types;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService
@@ -75,7 +76,7 @@ namespace Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService
for (int i = 0; i < filteredApplicationPlayStatistics.Count(); i++)
{
MemoryHelper.Write(context.Memory, outputPosition + (ulong)(i * Marshal.SizeOf<ApplicationPlayStatistics>()), filteredApplicationPlayStatistics.ElementAt(i).Value);
MemoryHelper.Write(context.Memory, outputPosition + (ulong)(i * Unsafe.SizeOf<ApplicationPlayStatistics>()), filteredApplicationPlayStatistics.ElementAt(i).Value);
}
context.ResponseData.Write(filteredApplicationPlayStatistics.Count());

View File

@@ -1,4 +1,5 @@
using Ryujinx.HLE.HOS.Services.Time.TimeZone;
using System;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Time.Clock
@@ -16,14 +17,22 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock
public CalendarAdditionalInfo NetworkCalendarAdditionalTime;
public SteadyClockTimePoint SteadyClockTimePoint;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x24)]
public char[] LocationName;
private LocationNameStorageHolder _locationName;
public Span<byte> LocationName => MemoryMarshal.Cast<LocationNameStorageHolder, byte>(MemoryMarshal.CreateSpan(ref _locationName, LocationNameStorageHolder.Size));
[MarshalAs(UnmanagedType.I1)]
public bool IsAutomaticCorrectionEnabled;
public byte Type;
public ushort Unknown;
[StructLayout(LayoutKind.Sequential, Pack = 1, Size = Size)]
private struct LocationNameStorageHolder
{
public const int Size = 0x24;
}
public static ResultCode GetCurrentTime(out long currentTime, SteadyClockTimePoint steadyClockTimePoint, SystemClockContext context)
{
currentTime = 0;

View File

@@ -8,7 +8,9 @@ using Ryujinx.HLE.HOS.Services.Time.TimeZone;
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
namespace Ryujinx.HLE.HOS.Services.Time
{
@@ -281,7 +283,7 @@ namespace Ryujinx.HLE.HOS.Services.Time
{
byte type = context.RequestData.ReadByte();
context.Response.PtrBuff[0] = context.Response.PtrBuff[0].WithSize((uint)Marshal.SizeOf<ClockSnapshot>());
context.Response.PtrBuff[0] = context.Response.PtrBuff[0].WithSize((uint)Unsafe.SizeOf<ClockSnapshot>());
context.RequestData.BaseStream.Position += 7;
@@ -372,12 +374,9 @@ namespace Ryujinx.HLE.HOS.Services.Time
return result;
}
char[] tzName = deviceLocationName.ToCharArray();
char[] locationName = new char[0x24];
ReadOnlySpan<byte> tzName = Encoding.ASCII.GetBytes(deviceLocationName);
Array.Copy(tzName, locationName, tzName.Length);
clockSnapshot.LocationName = locationName;
tzName.CopyTo(clockSnapshot.LocationName);
result = ClockSnapshot.GetCurrentTime(out clockSnapshot.UserTime, currentTimePoint, clockSnapshot.UserContext);
@@ -414,7 +413,7 @@ namespace Ryujinx.HLE.HOS.Services.Time
private ClockSnapshot ReadClockSnapshotFromBuffer(ServiceCtx context, IpcPtrBuffDesc ipcDesc)
{
Debug.Assert(ipcDesc.Size == (ulong)Marshal.SizeOf<ClockSnapshot>());
Debug.Assert(ipcDesc.Size == (ulong)Unsafe.SizeOf<ClockSnapshot>());
byte[] temp = new byte[ipcDesc.Size];

View File

@@ -5,6 +5,7 @@ using Ryujinx.HLE.Utilities;
using System;
using System.Buffers.Binary;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
@@ -890,14 +891,14 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
long streamLength = reader.BaseStream.Length;
if (streamLength < Marshal.SizeOf<TzifHeader>())
if (streamLength < Unsafe.SizeOf<TzifHeader>())
{
return false;
}
TzifHeader header = reader.ReadStruct<TzifHeader>();
streamLength -= Marshal.SizeOf<TzifHeader>();
streamLength -= Unsafe.SizeOf<TzifHeader>();
int ttisGMTCount = Detzcode32(header.TtisGMTCount);
int ttisSTDCount = Detzcode32(header.TtisSTDCount);

View File

@@ -22,7 +22,7 @@
<ItemGroup>
<PackageReference Include="Concentus" Version="1.1.7" />
<PackageReference Include="LibHac" Version="0.16.1" />
<PackageReference Include="LibHac" Version="0.17.0" />
<PackageReference Include="MsgPack.Cli" Version="1.0.1" />
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.4" />
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta11" />

View File

@@ -40,20 +40,16 @@ namespace Ryujinx.Headless.SDL2.Vulkan
return (IntPtr)surfaceHandle;
}
// TODO: Fix this in SDL2-CS.
[DllImport("SDL2", EntryPoint = "SDL_Vulkan_GetInstanceExtensions", CallingConvention = CallingConvention.Cdecl)]
public static extern SDL_bool SDL_Vulkan_GetInstanceExtensions_Workaround(IntPtr window, out uint count, IntPtr names);
public unsafe string[] GetRequiredInstanceExtensions()
{
if (SDL_Vulkan_GetInstanceExtensions_Workaround(WindowHandle, out uint extensionsCount, IntPtr.Zero) == SDL_bool.SDL_TRUE)
if (SDL_Vulkan_GetInstanceExtensions(WindowHandle, out uint extensionsCount, IntPtr.Zero) == SDL_bool.SDL_TRUE)
{
IntPtr[] rawExtensions = new IntPtr[(int)extensionsCount];
string[] extensions = new string[(int)extensionsCount];
fixed (IntPtr* rawExtensionsPtr = rawExtensions)
{
if (SDL_Vulkan_GetInstanceExtensions_Workaround(WindowHandle, out extensionsCount, (IntPtr)rawExtensionsPtr) == SDL_bool.SDL_TRUE)
if (SDL_Vulkan_GetInstanceExtensions(WindowHandle, out extensionsCount, (IntPtr)rawExtensionsPtr) == SDL_bool.SDL_TRUE)
{
for (int i = 0; i < extensions.Length; i++)
{

View File

@@ -1,16 +1,15 @@
using System.Runtime.InteropServices;
using Ryujinx.Common.Memory;
using System.Runtime.InteropServices;
namespace Ryujinx.Input.Motion.CemuHook.Protocol
{
[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct ControllerDataRequest
{
public MessageType Type;
public MessageType Type;
public SubscriberType SubscriberType;
public byte Slot;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
public byte[] MacAddress;
public byte Slot;
public Array6<byte> MacAddress;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
@@ -27,11 +26,8 @@ namespace Ryujinx.Input.Motion.CemuHook.Protocol
public uint DPadAnalog;
public ulong MainButtonsAnalog;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
public byte[] Touch1;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
public byte[] Touch2;
public Array6<byte> Touch1;
public Array6<byte> Touch2;
public ulong MotionTimestamp;
public float AccelerometerX;

View File

@@ -1,21 +1,20 @@
using System.Runtime.InteropServices;
using Ryujinx.Common.Memory;
using System.Runtime.InteropServices;
namespace Ryujinx.Input.Motion.CemuHook.Protocol
{
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct ControllerInfoResponse
{
public SharedResponse Shared;
private byte _zero;
public SharedResponse Shared;
private byte _zero;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct ControllerInfoRequest
{
public MessageType Type;
public int PortsCount;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
public byte[] PortIndices;
public int PortsCount;
public Array4<byte> PortIndices;
}
}

View File

@@ -1,4 +1,5 @@
using System.Runtime.InteropServices;
using Ryujinx.Common.Memory;
using System.Runtime.InteropServices;
namespace Ryujinx.Input.Motion.CemuHook.Protocol
{
@@ -11,8 +12,7 @@ namespace Ryujinx.Input.Motion.CemuHook.Protocol
public DeviceModelType ModelType;
public ConnectionType ConnectionType;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
public byte[] MacAddress;
public Array6<byte> MacAddress;
public BatteryStatus BatteryStatus;
}

View File

@@ -44,6 +44,11 @@ namespace Ryujinx.Memory.Tests
throw new NotImplementedException();
}
public bool WriteWithRedundancyCheck(ulong va, ReadOnlySpan<byte> data)
{
throw new NotImplementedException();
}
public ReadOnlySpan<byte> GetSpan(ulong va, int size, bool tracked = false)
{
throw new NotImplementedException();

View File

@@ -136,6 +136,14 @@ namespace Ryujinx.Memory
}
}
/// <inheritdoc/>
public bool WriteWithRedundancyCheck(ulong va, ReadOnlySpan<byte> data)
{
Write(va, data);
return true;
}
/// <inheritdoc/>
public ReadOnlySpan<byte> GetSpan(ulong va, int size, bool tracked = false)
{

View File

@@ -58,6 +58,17 @@ namespace Ryujinx.Memory
/// <exception cref="InvalidMemoryRegionException">Throw for unhandled invalid or unmapped memory accesses</exception>
void Write(ulong va, ReadOnlySpan<byte> data);
/// <summary>
/// Writes data to the application process, returning false if the data was not changed.
/// This triggers read memory tracking, as a redundancy check would be useless if the data is not up to date.
/// </summary>
/// <remarks>The memory manager can return that memory has changed when it hasn't to avoid expensive data copies.</remarks>
/// <param name="va">Virtual address to write the data into</param>
/// <param name="data">Data to be written</param>
/// <exception cref="InvalidMemoryRegionException">Throw for unhandled invalid or unmapped memory accesses</exception>
/// <returns>True if the data was changed, false otherwise</returns>
bool WriteWithRedundancyCheck(ulong va, ReadOnlySpan<byte> data);
void Fill(ulong va, ulong size, byte value)
{
const int MaxChunkSize = 1 << 24;

View File

@@ -5,7 +5,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Ryujinx.SDL2-CS" Version="2.0.22-build20" />
<PackageReference Include="Ryujinx.SDL2-CS" Version="2.24.2-build21" />
</ItemGroup>
<ItemGroup>

View File

@@ -43,6 +43,8 @@ namespace Ryujinx.SDL2.Common
private SDL2Driver() {}
private const string SDL_HINT_JOYSTICK_HIDAPI_COMBINE_JOY_CONS = "SDL_JOYSTICK_HIDAPI_COMBINE_JOY_CONS";
public void Initialize()
{
lock (_lock)
@@ -60,6 +62,11 @@ namespace Ryujinx.SDL2.Common
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_SWITCH_HOME_LED, "0");
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, "1");
// NOTE: As of SDL2 2.24.0, joycons are combined by default but the motion source only come from one of them.
// We disable this behavior for now.
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_COMBINE_JOY_CONS, "0");
if (SDL_Init(SdlInitFlags) != 0)
{
string errorMessage = $"SDL2 initlaization failed with error \"{SDL_GetError()}\"";

View File

@@ -1,135 +0,0 @@
namespace Ryujinx.Tests.Unicorn.Native
{
public enum Arm32Register
{
INVALID = 0,
APSR,
APSR_NZCV,
CPSR,
FPEXC,
FPINST,
FPSCR,
FPSCR_NZCV,
FPSID,
ITSTATE,
LR,
PC,
SP,
SPSR,
D0,
D1,
D2,
D3,
D4,
D5,
D6,
D7,
D8,
D9,
D10,
D11,
D12,
D13,
D14,
D15,
D16,
D17,
D18,
D19,
D20,
D21,
D22,
D23,
D24,
D25,
D26,
D27,
D28,
D29,
D30,
D31,
FPINST2,
MVFR0,
MVFR1,
MVFR2,
Q0,
Q1,
Q2,
Q3,
Q4,
Q5,
Q6,
Q7,
Q8,
Q9,
Q10,
Q11,
Q12,
Q13,
Q14,
Q15,
R0,
R1,
R2,
R3,
R4,
R5,
R6,
R7,
R8,
R9,
R10,
R11,
R12,
S0,
S1,
S2,
S3,
S4,
S5,
S6,
S7,
S8,
S9,
S10,
S11,
S12,
S13,
S14,
S15,
S16,
S17,
S18,
S19,
S20,
S21,
S22,
S23,
S24,
S25,
S26,
S27,
S28,
S29,
S30,
S31,
C1_C0_2,
C13_C0_2,
C13_C0_3,
IPSR,
MSP,
PSP,
CONTROL,
ENDING,
// Alias registers.
R13 = SP,
R14 = LR,
R15 = PC,
SB = R9,
SL = R10,
FP = R11,
IP = R12,
}
}

View File

@@ -1,295 +0,0 @@
// ReSharper disable InconsistentNaming
namespace Ryujinx.Tests.Unicorn.Native
{
public enum ArmRegister
{
INVALID = 0,
X29,
X30,
NZCV,
SP,
WSP,
WZR,
XZR,
B0,
B1,
B2,
B3,
B4,
B5,
B6,
B7,
B8,
B9,
B10,
B11,
B12,
B13,
B14,
B15,
B16,
B17,
B18,
B19,
B20,
B21,
B22,
B23,
B24,
B25,
B26,
B27,
B28,
B29,
B30,
B31,
D0,
D1,
D2,
D3,
D4,
D5,
D6,
D7,
D8,
D9,
D10,
D11,
D12,
D13,
D14,
D15,
D16,
D17,
D18,
D19,
D20,
D21,
D22,
D23,
D24,
D25,
D26,
D27,
D28,
D29,
D30,
D31,
H0,
H1,
H2,
H3,
H4,
H5,
H6,
H7,
H8,
H9,
H10,
H11,
H12,
H13,
H14,
H15,
H16,
H17,
H18,
H19,
H20,
H21,
H22,
H23,
H24,
H25,
H26,
H27,
H28,
H29,
H30,
H31,
Q0,
Q1,
Q2,
Q3,
Q4,
Q5,
Q6,
Q7,
Q8,
Q9,
Q10,
Q11,
Q12,
Q13,
Q14,
Q15,
Q16,
Q17,
Q18,
Q19,
Q20,
Q21,
Q22,
Q23,
Q24,
Q25,
Q26,
Q27,
Q28,
Q29,
Q30,
Q31,
S0,
S1,
S2,
S3,
S4,
S5,
S6,
S7,
S8,
S9,
S10,
S11,
S12,
S13,
S14,
S15,
S16,
S17,
S18,
S19,
S20,
S21,
S22,
S23,
S24,
S25,
S26,
S27,
S28,
S29,
S30,
S31,
W0,
W1,
W2,
W3,
W4,
W5,
W6,
W7,
W8,
W9,
W10,
W11,
W12,
W13,
W14,
W15,
W16,
W17,
W18,
W19,
W20,
W21,
W22,
W23,
W24,
W25,
W26,
W27,
W28,
W29,
W30,
X0,
X1,
X2,
X3,
X4,
X5,
X6,
X7,
X8,
X9,
X10,
X11,
X12,
X13,
X14,
X15,
X16,
X17,
X18,
X19,
X20,
X21,
X22,
X23,
X24,
X25,
X26,
X27,
X28,
V0,
V1,
V2,
V3,
V4,
V5,
V6,
V7,
V8,
V9,
V10,
V11,
V12,
V13,
V14,
V15,
V16,
V17,
V18,
V19,
V20,
V21,
V22,
V23,
V24,
V25,
V26,
V27,
V28,
V29,
V30,
V31,
// > pseudo registers
PC, // program counter register
CPACR_EL1,
ESR,
// > thread registers
TPIDR_EL0,
TPIDRRO_EL0,
TPIDR_EL1,
PSTATE, // PSTATE pseudoregister
// > floating point control and status registers
FPCR,
FPSR,
ENDING, // <-- mark the end of the list of registers
// > alias registers
IP0 = X16,
IP1 = X17,
FP = X29,
LR = X30,
}
}

View File

@@ -0,0 +1,20 @@
// Constants for Unicorn Engine. AUTO-GENERATED FILE, DO NOT EDIT
// ReSharper disable InconsistentNaming
namespace Ryujinx.Tests.Unicorn.Native.Const
{
public enum Arch
{
ARM = 1,
ARM64 = 2,
MIPS = 3,
X86 = 4,
PPC = 5,
SPARC = 6,
M68K = 7,
RISCV = 8,
S390X = 9,
TRICORE = 10,
MAX = 11,
}
}

View File

@@ -0,0 +1,200 @@
// Constants for Unicorn Engine. AUTO-GENERATED FILE, DO NOT EDIT
// ReSharper disable InconsistentNaming
namespace Ryujinx.Tests.Unicorn.Native.Const
{
public enum Arm
{
// ARM CPU
CPU_ARM_926 = 0,
CPU_ARM_946 = 1,
CPU_ARM_1026 = 2,
CPU_ARM_1136_R2 = 3,
CPU_ARM_1136 = 4,
CPU_ARM_1176 = 5,
CPU_ARM_11MPCORE = 6,
CPU_ARM_CORTEX_M0 = 7,
CPU_ARM_CORTEX_M3 = 8,
CPU_ARM_CORTEX_M4 = 9,
CPU_ARM_CORTEX_M7 = 10,
CPU_ARM_CORTEX_M33 = 11,
CPU_ARM_CORTEX_R5 = 12,
CPU_ARM_CORTEX_R5F = 13,
CPU_ARM_CORTEX_A7 = 14,
CPU_ARM_CORTEX_A8 = 15,
CPU_ARM_CORTEX_A9 = 16,
CPU_ARM_CORTEX_A15 = 17,
CPU_ARM_TI925T = 18,
CPU_ARM_SA1100 = 19,
CPU_ARM_SA1110 = 20,
CPU_ARM_PXA250 = 21,
CPU_ARM_PXA255 = 22,
CPU_ARM_PXA260 = 23,
CPU_ARM_PXA261 = 24,
CPU_ARM_PXA262 = 25,
CPU_ARM_PXA270 = 26,
CPU_ARM_PXA270A0 = 27,
CPU_ARM_PXA270A1 = 28,
CPU_ARM_PXA270B0 = 29,
CPU_ARM_PXA270B1 = 30,
CPU_ARM_PXA270C0 = 31,
CPU_ARM_PXA270C5 = 32,
CPU_ARM_MAX = 33,
CPU_ARM_ENDING = 34,
// ARM registers
REG_INVALID = 0,
REG_APSR = 1,
REG_APSR_NZCV = 2,
REG_CPSR = 3,
REG_FPEXC = 4,
REG_FPINST = 5,
REG_FPSCR = 6,
REG_FPSCR_NZCV = 7,
REG_FPSID = 8,
REG_ITSTATE = 9,
REG_LR = 10,
REG_PC = 11,
REG_SP = 12,
REG_SPSR = 13,
REG_D0 = 14,
REG_D1 = 15,
REG_D2 = 16,
REG_D3 = 17,
REG_D4 = 18,
REG_D5 = 19,
REG_D6 = 20,
REG_D7 = 21,
REG_D8 = 22,
REG_D9 = 23,
REG_D10 = 24,
REG_D11 = 25,
REG_D12 = 26,
REG_D13 = 27,
REG_D14 = 28,
REG_D15 = 29,
REG_D16 = 30,
REG_D17 = 31,
REG_D18 = 32,
REG_D19 = 33,
REG_D20 = 34,
REG_D21 = 35,
REG_D22 = 36,
REG_D23 = 37,
REG_D24 = 38,
REG_D25 = 39,
REG_D26 = 40,
REG_D27 = 41,
REG_D28 = 42,
REG_D29 = 43,
REG_D30 = 44,
REG_D31 = 45,
REG_FPINST2 = 46,
REG_MVFR0 = 47,
REG_MVFR1 = 48,
REG_MVFR2 = 49,
REG_Q0 = 50,
REG_Q1 = 51,
REG_Q2 = 52,
REG_Q3 = 53,
REG_Q4 = 54,
REG_Q5 = 55,
REG_Q6 = 56,
REG_Q7 = 57,
REG_Q8 = 58,
REG_Q9 = 59,
REG_Q10 = 60,
REG_Q11 = 61,
REG_Q12 = 62,
REG_Q13 = 63,
REG_Q14 = 64,
REG_Q15 = 65,
REG_R0 = 66,
REG_R1 = 67,
REG_R2 = 68,
REG_R3 = 69,
REG_R4 = 70,
REG_R5 = 71,
REG_R6 = 72,
REG_R7 = 73,
REG_R8 = 74,
REG_R9 = 75,
REG_R10 = 76,
REG_R11 = 77,
REG_R12 = 78,
REG_S0 = 79,
REG_S1 = 80,
REG_S2 = 81,
REG_S3 = 82,
REG_S4 = 83,
REG_S5 = 84,
REG_S6 = 85,
REG_S7 = 86,
REG_S8 = 87,
REG_S9 = 88,
REG_S10 = 89,
REG_S11 = 90,
REG_S12 = 91,
REG_S13 = 92,
REG_S14 = 93,
REG_S15 = 94,
REG_S16 = 95,
REG_S17 = 96,
REG_S18 = 97,
REG_S19 = 98,
REG_S20 = 99,
REG_S21 = 100,
REG_S22 = 101,
REG_S23 = 102,
REG_S24 = 103,
REG_S25 = 104,
REG_S26 = 105,
REG_S27 = 106,
REG_S28 = 107,
REG_S29 = 108,
REG_S30 = 109,
REG_S31 = 110,
REG_C1_C0_2 = 111,
REG_C13_C0_2 = 112,
REG_C13_C0_3 = 113,
REG_IPSR = 114,
REG_MSP = 115,
REG_PSP = 116,
REG_CONTROL = 117,
REG_IAPSR = 118,
REG_EAPSR = 119,
REG_XPSR = 120,
REG_EPSR = 121,
REG_IEPSR = 122,
REG_PRIMASK = 123,
REG_BASEPRI = 124,
REG_BASEPRI_MAX = 125,
REG_FAULTMASK = 126,
REG_APSR_NZCVQ = 127,
REG_APSR_G = 128,
REG_APSR_NZCVQG = 129,
REG_IAPSR_NZCVQ = 130,
REG_IAPSR_G = 131,
REG_IAPSR_NZCVQG = 132,
REG_EAPSR_NZCVQ = 133,
REG_EAPSR_G = 134,
REG_EAPSR_NZCVQG = 135,
REG_XPSR_NZCVQ = 136,
REG_XPSR_G = 137,
REG_XPSR_NZCVQG = 138,
REG_CP_REG = 139,
REG_ENDING = 140,
// alias registers
REG_R13 = 12,
REG_R14 = 10,
REG_R15 = 11,
REG_SB = 75,
REG_SL = 76,
REG_FP = 77,
REG_IP = 78,
}
}

View File

@@ -0,0 +1,341 @@
// Constants for Unicorn Engine. AUTO-GENERATED FILE, DO NOT EDIT
// ReSharper disable InconsistentNaming
namespace Ryujinx.Tests.Unicorn.Native.Const
{
public enum Arm64
{
// ARM64 CPU
CPU_ARM64_A57 = 0,
CPU_ARM64_A53 = 1,
CPU_ARM64_A72 = 2,
CPU_ARM64_MAX = 3,
CPU_ARM64_ENDING = 4,
// ARM64 registers
REG_INVALID = 0,
REG_X29 = 1,
REG_X30 = 2,
REG_NZCV = 3,
REG_SP = 4,
REG_WSP = 5,
REG_WZR = 6,
REG_XZR = 7,
REG_B0 = 8,
REG_B1 = 9,
REG_B2 = 10,
REG_B3 = 11,
REG_B4 = 12,
REG_B5 = 13,
REG_B6 = 14,
REG_B7 = 15,
REG_B8 = 16,
REG_B9 = 17,
REG_B10 = 18,
REG_B11 = 19,
REG_B12 = 20,
REG_B13 = 21,
REG_B14 = 22,
REG_B15 = 23,
REG_B16 = 24,
REG_B17 = 25,
REG_B18 = 26,
REG_B19 = 27,
REG_B20 = 28,
REG_B21 = 29,
REG_B22 = 30,
REG_B23 = 31,
REG_B24 = 32,
REG_B25 = 33,
REG_B26 = 34,
REG_B27 = 35,
REG_B28 = 36,
REG_B29 = 37,
REG_B30 = 38,
REG_B31 = 39,
REG_D0 = 40,
REG_D1 = 41,
REG_D2 = 42,
REG_D3 = 43,
REG_D4 = 44,
REG_D5 = 45,
REG_D6 = 46,
REG_D7 = 47,
REG_D8 = 48,
REG_D9 = 49,
REG_D10 = 50,
REG_D11 = 51,
REG_D12 = 52,
REG_D13 = 53,
REG_D14 = 54,
REG_D15 = 55,
REG_D16 = 56,
REG_D17 = 57,
REG_D18 = 58,
REG_D19 = 59,
REG_D20 = 60,
REG_D21 = 61,
REG_D22 = 62,
REG_D23 = 63,
REG_D24 = 64,
REG_D25 = 65,
REG_D26 = 66,
REG_D27 = 67,
REG_D28 = 68,
REG_D29 = 69,
REG_D30 = 70,
REG_D31 = 71,
REG_H0 = 72,
REG_H1 = 73,
REG_H2 = 74,
REG_H3 = 75,
REG_H4 = 76,
REG_H5 = 77,
REG_H6 = 78,
REG_H7 = 79,
REG_H8 = 80,
REG_H9 = 81,
REG_H10 = 82,
REG_H11 = 83,
REG_H12 = 84,
REG_H13 = 85,
REG_H14 = 86,
REG_H15 = 87,
REG_H16 = 88,
REG_H17 = 89,
REG_H18 = 90,
REG_H19 = 91,
REG_H20 = 92,
REG_H21 = 93,
REG_H22 = 94,
REG_H23 = 95,
REG_H24 = 96,
REG_H25 = 97,
REG_H26 = 98,
REG_H27 = 99,
REG_H28 = 100,
REG_H29 = 101,
REG_H30 = 102,
REG_H31 = 103,
REG_Q0 = 104,
REG_Q1 = 105,
REG_Q2 = 106,
REG_Q3 = 107,
REG_Q4 = 108,
REG_Q5 = 109,
REG_Q6 = 110,
REG_Q7 = 111,
REG_Q8 = 112,
REG_Q9 = 113,
REG_Q10 = 114,
REG_Q11 = 115,
REG_Q12 = 116,
REG_Q13 = 117,
REG_Q14 = 118,
REG_Q15 = 119,
REG_Q16 = 120,
REG_Q17 = 121,
REG_Q18 = 122,
REG_Q19 = 123,
REG_Q20 = 124,
REG_Q21 = 125,
REG_Q22 = 126,
REG_Q23 = 127,
REG_Q24 = 128,
REG_Q25 = 129,
REG_Q26 = 130,
REG_Q27 = 131,
REG_Q28 = 132,
REG_Q29 = 133,
REG_Q30 = 134,
REG_Q31 = 135,
REG_S0 = 136,
REG_S1 = 137,
REG_S2 = 138,
REG_S3 = 139,
REG_S4 = 140,
REG_S5 = 141,
REG_S6 = 142,
REG_S7 = 143,
REG_S8 = 144,
REG_S9 = 145,
REG_S10 = 146,
REG_S11 = 147,
REG_S12 = 148,
REG_S13 = 149,
REG_S14 = 150,
REG_S15 = 151,
REG_S16 = 152,
REG_S17 = 153,
REG_S18 = 154,
REG_S19 = 155,
REG_S20 = 156,
REG_S21 = 157,
REG_S22 = 158,
REG_S23 = 159,
REG_S24 = 160,
REG_S25 = 161,
REG_S26 = 162,
REG_S27 = 163,
REG_S28 = 164,
REG_S29 = 165,
REG_S30 = 166,
REG_S31 = 167,
REG_W0 = 168,
REG_W1 = 169,
REG_W2 = 170,
REG_W3 = 171,
REG_W4 = 172,
REG_W5 = 173,
REG_W6 = 174,
REG_W7 = 175,
REG_W8 = 176,
REG_W9 = 177,
REG_W10 = 178,
REG_W11 = 179,
REG_W12 = 180,
REG_W13 = 181,
REG_W14 = 182,
REG_W15 = 183,
REG_W16 = 184,
REG_W17 = 185,
REG_W18 = 186,
REG_W19 = 187,
REG_W20 = 188,
REG_W21 = 189,
REG_W22 = 190,
REG_W23 = 191,
REG_W24 = 192,
REG_W25 = 193,
REG_W26 = 194,
REG_W27 = 195,
REG_W28 = 196,
REG_W29 = 197,
REG_W30 = 198,
REG_X0 = 199,
REG_X1 = 200,
REG_X2 = 201,
REG_X3 = 202,
REG_X4 = 203,
REG_X5 = 204,
REG_X6 = 205,
REG_X7 = 206,
REG_X8 = 207,
REG_X9 = 208,
REG_X10 = 209,
REG_X11 = 210,
REG_X12 = 211,
REG_X13 = 212,
REG_X14 = 213,
REG_X15 = 214,
REG_X16 = 215,
REG_X17 = 216,
REG_X18 = 217,
REG_X19 = 218,
REG_X20 = 219,
REG_X21 = 220,
REG_X22 = 221,
REG_X23 = 222,
REG_X24 = 223,
REG_X25 = 224,
REG_X26 = 225,
REG_X27 = 226,
REG_X28 = 227,
REG_V0 = 228,
REG_V1 = 229,
REG_V2 = 230,
REG_V3 = 231,
REG_V4 = 232,
REG_V5 = 233,
REG_V6 = 234,
REG_V7 = 235,
REG_V8 = 236,
REG_V9 = 237,
REG_V10 = 238,
REG_V11 = 239,
REG_V12 = 240,
REG_V13 = 241,
REG_V14 = 242,
REG_V15 = 243,
REG_V16 = 244,
REG_V17 = 245,
REG_V18 = 246,
REG_V19 = 247,
REG_V20 = 248,
REG_V21 = 249,
REG_V22 = 250,
REG_V23 = 251,
REG_V24 = 252,
REG_V25 = 253,
REG_V26 = 254,
REG_V27 = 255,
REG_V28 = 256,
REG_V29 = 257,
REG_V30 = 258,
REG_V31 = 259,
// pseudo registers
REG_PC = 260,
REG_CPACR_EL1 = 261,
// thread registers, depreciated, use UC_ARM64_REG_CP_REG instead
REG_TPIDR_EL0 = 262,
REG_TPIDRRO_EL0 = 263,
REG_TPIDR_EL1 = 264,
REG_PSTATE = 265,
// exception link registers, depreciated, use UC_ARM64_REG_CP_REG instead
REG_ELR_EL0 = 266,
REG_ELR_EL1 = 267,
REG_ELR_EL2 = 268,
REG_ELR_EL3 = 269,
// stack pointers registers, depreciated, use UC_ARM64_REG_CP_REG instead
REG_SP_EL0 = 270,
REG_SP_EL1 = 271,
REG_SP_EL2 = 272,
REG_SP_EL3 = 273,
// other CP15 registers, depreciated, use UC_ARM64_REG_CP_REG instead
REG_TTBR0_EL1 = 274,
REG_TTBR1_EL1 = 275,
REG_ESR_EL0 = 276,
REG_ESR_EL1 = 277,
REG_ESR_EL2 = 278,
REG_ESR_EL3 = 279,
REG_FAR_EL0 = 280,
REG_FAR_EL1 = 281,
REG_FAR_EL2 = 282,
REG_FAR_EL3 = 283,
REG_PAR_EL1 = 284,
REG_MAIR_EL1 = 285,
REG_VBAR_EL0 = 286,
REG_VBAR_EL1 = 287,
REG_VBAR_EL2 = 288,
REG_VBAR_EL3 = 289,
REG_CP_REG = 290,
// floating point control and status registers
REG_FPCR = 291,
REG_FPSR = 292,
REG_ENDING = 293,
// alias registers
REG_IP0 = 215,
REG_IP1 = 216,
REG_FP = 1,
REG_LR = 2,
// ARM64 instructions
INS_INVALID = 0,
INS_MRS = 1,
INS_MSR = 2,
INS_SYS = 3,
INS_SYSL = 4,
INS_ENDING = 5,
}
}

View File

@@ -0,0 +1,44 @@
// Constants for Unicorn Engine. AUTO-GENERATED FILE, DO NOT EDIT
// ReSharper disable InconsistentNaming
namespace Ryujinx.Tests.Unicorn.Native.Const
{
public enum Common
{
API_MAJOR = 2,
API_MINOR = 0,
API_PATCH = 0,
API_EXTRA = 255,
VERSION_MAJOR = 2,
VERSION_MINOR = 0,
VERSION_PATCH = 0,
VERSION_EXTRA = 255,
SECOND_SCALE = 1000000,
MILISECOND_SCALE = 1000,
QUERY_MODE = 1,
QUERY_PAGE_SIZE = 2,
QUERY_ARCH = 3,
QUERY_TIMEOUT = 4,
CTL_IO_NONE = 0,
CTL_IO_WRITE = 1,
CTL_IO_READ = 2,
CTL_IO_READ_WRITE = 3,
CTL_UC_MODE = 0,
CTL_UC_PAGE_SIZE = 1,
CTL_UC_ARCH = 2,
CTL_UC_TIMEOUT = 3,
CTL_UC_USE_EXITS = 4,
CTL_UC_EXITS_CNT = 5,
CTL_UC_EXITS = 6,
CTL_CPU_MODEL = 7,
CTL_TB_REQUEST_CACHE = 8,
CTL_TB_REMOVE_CACHE = 9,
CTL_TB_FLUSH = 10,
}
}

View File

@@ -0,0 +1,31 @@
// Constants for Unicorn Engine. AUTO-GENERATED FILE, DO NOT EDIT
// ReSharper disable InconsistentNaming
namespace Ryujinx.Tests.Unicorn.Native.Const
{
public enum Error
{
OK = 0,
NOMEM = 1,
ARCH = 2,
HANDLE = 3,
MODE = 4,
VERSION = 5,
READ_UNMAPPED = 6,
WRITE_UNMAPPED = 7,
FETCH_UNMAPPED = 8,
HOOK = 9,
INSN_INVALID = 10,
MAP = 11,
WRITE_PROT = 12,
READ_PROT = 13,
FETCH_PROT = 14,
ARG = 15,
READ_UNALIGNED = 16,
WRITE_UNALIGNED = 17,
FETCH_UNALIGNED = 18,
HOOK_EXIST = 19,
RESOURCE = 20,
EXCEPTION = 21,
}
}

View File

@@ -0,0 +1,33 @@
// Constants for Unicorn Engine. AUTO-GENERATED FILE, DO NOT EDIT
// ReSharper disable InconsistentNaming
namespace Ryujinx.Tests.Unicorn.Native.Const
{
public enum Hook
{
INTR = 1,
INSN = 2,
CODE = 4,
BLOCK = 8,
MEM_READ_UNMAPPED = 16,
MEM_WRITE_UNMAPPED = 32,
MEM_FETCH_UNMAPPED = 64,
MEM_READ_PROT = 128,
MEM_WRITE_PROT = 256,
MEM_FETCH_PROT = 512,
MEM_READ = 1024,
MEM_WRITE = 2048,
MEM_FETCH = 4096,
MEM_READ_AFTER = 8192,
INSN_INVALID = 16384,
EDGE_GENERATED = 32768,
TCG_OPCODE = 65536,
MEM_UNMAPPED = 112,
MEM_PROT = 896,
MEM_READ_INVALID = 144,
MEM_WRITE_INVALID = 288,
MEM_FETCH_INVALID = 576,
MEM_INVALID = 1008,
MEM_VALID = 7168,
}
}

View File

@@ -0,0 +1,19 @@
// Constants for Unicorn Engine. AUTO-GENERATED FILE, DO NOT EDIT
// ReSharper disable InconsistentNaming
namespace Ryujinx.Tests.Unicorn.Native.Const
{
public enum Memory
{
READ = 16,
WRITE = 17,
FETCH = 18,
READ_UNMAPPED = 19,
WRITE_UNMAPPED = 20,
FETCH_UNMAPPED = 21,
WRITE_PROT = 22,
READ_PROT = 23,
FETCH_PROT = 24,
READ_AFTER = 25,
}
}

View File

@@ -0,0 +1,35 @@
// Constants for Unicorn Engine. AUTO-GENERATED FILE, DO NOT EDIT
// ReSharper disable InconsistentNaming
namespace Ryujinx.Tests.Unicorn.Native.Const
{
public enum Mode
{
LITTLE_ENDIAN = 0,
BIG_ENDIAN = 1073741824,
ARM = 0,
THUMB = 16,
MCLASS = 32,
V8 = 64,
ARMBE8 = 1024,
ARM926 = 128,
ARM946 = 256,
ARM1176 = 512,
MICRO = 16,
MIPS3 = 32,
MIPS32R6 = 64,
MIPS32 = 4,
MIPS64 = 8,
MODE_16 = 2,
MODE_32 = 4,
MODE_64 = 8,
PPC32 = 4,
PPC64 = 8,
QPX = 16,
SPARC32 = 4,
SPARC64 = 8,
V9 = 16,
RISCV32 = 4,
RISCV64 = 8,
}
}

View File

@@ -0,0 +1,14 @@
// Constants for Unicorn Engine. AUTO-GENERATED FILE, DO NOT EDIT
// ReSharper disable InconsistentNaming
namespace Ryujinx.Tests.Unicorn.Native.Const
{
public enum Permission
{
NONE = 0,
READ = 1,
WRITE = 2,
EXEC = 4,
ALL = 7,
}
}

View File

@@ -0,0 +1,12 @@
// Constants for Unicorn Engine. AUTO-GENERATED FILE, DO NOT EDIT
// ReSharper disable InconsistentNaming
namespace Ryujinx.Tests.Unicorn.Native.Const
{
public enum TCG
{
OP_SUB = 0,
OP_FLAG_CMP = 1,
OP_FLAG_DIRECT = 2,
}
}

View File

@@ -1,13 +1,43 @@
using Ryujinx.Tests.Unicorn.Native.Const;
using System;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
namespace Ryujinx.Tests.Unicorn.Native
{
public class Interface
public static class Interface
{
public static void Checked(UnicornError error)
public static bool IsUnicornAvailable { get; private set; } = true;
private static IntPtr ImportResolver(string libraryName, Assembly assembly, DllImportSearchPath? searchPath)
{
if (error != UnicornError.UC_ERR_OK)
if (libraryName == "unicorn")
{
string loadPath = $"{Path.GetDirectoryName(assembly.Location)}/";
loadPath += OperatingSystem.IsWindows() ? $"{libraryName}.dll" : $"lib{libraryName}.so";
if (!NativeLibrary.TryLoad(loadPath, out IntPtr libraryPtr))
{
IsUnicornAvailable = false;
Console.Error.WriteLine($"ERROR: Could not find unicorn at: {loadPath}");
}
return libraryPtr;
}
// Otherwise, fallback to default import resolver.
return IntPtr.Zero;
}
static Interface()
{
NativeLibrary.SetDllImportResolver(Assembly.GetExecutingAssembly(), ImportResolver);
}
public static void Checked(Error error)
{
if (error != Error.OK)
{
throw new UnicornException(error);
}
@@ -31,39 +61,39 @@ namespace Ryujinx.Tests.Unicorn.Native
public static extern uint uc_version(out uint major, out uint minor);
[DllImport("unicorn", CallingConvention = CallingConvention.Cdecl)]
public static extern UnicornError uc_open(UnicornArch arch, UnicornMode mode, out IntPtr uc);
public static extern Error uc_open(Arch arch, Mode mode, out IntPtr uc);
[DllImport("unicorn", CallingConvention = CallingConvention.Cdecl)]
public static extern UnicornError uc_close(IntPtr uc);
public static extern Error uc_close(IntPtr uc);
[DllImport("unicorn", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr uc_strerror(UnicornError err);
public static extern IntPtr uc_strerror(Error err);
[DllImport("unicorn", CallingConvention = CallingConvention.Cdecl)]
public static extern UnicornError uc_reg_write(IntPtr uc, int regid, byte[] value);
public static extern Error uc_reg_write(IntPtr uc, int regid, byte[] value);
[DllImport("unicorn", CallingConvention = CallingConvention.Cdecl)]
public static extern UnicornError uc_reg_read(IntPtr uc, int regid, byte[] value);
public static extern Error uc_reg_read(IntPtr uc, int regid, byte[] value);
[DllImport("unicorn", CallingConvention = CallingConvention.Cdecl)]
public static extern UnicornError uc_mem_write(IntPtr uc, ulong address, byte[] bytes, ulong size);
public static extern Error uc_mem_write(IntPtr uc, ulong address, byte[] bytes, ulong size);
[DllImport("unicorn", CallingConvention = CallingConvention.Cdecl)]
public static extern UnicornError uc_mem_read(IntPtr uc, ulong address, byte[] bytes, ulong size);
public static extern Error uc_mem_read(IntPtr uc, ulong address, byte[] bytes, ulong size);
[DllImport("unicorn", CallingConvention = CallingConvention.Cdecl)]
public static extern UnicornError uc_emu_start(IntPtr uc, ulong begin, ulong until, ulong timeout, ulong count);
public static extern Error uc_emu_start(IntPtr uc, ulong begin, ulong until, ulong timeout, ulong count);
[DllImport("unicorn", CallingConvention = CallingConvention.Cdecl)]
public static extern UnicornError uc_mem_map(IntPtr uc, ulong address, ulong size, uint perms);
public static extern Error uc_mem_map(IntPtr uc, ulong address, ulong size, uint perms);
[DllImport("unicorn", CallingConvention = CallingConvention.Cdecl)]
public static extern UnicornError uc_mem_unmap(IntPtr uc, ulong address, ulong size);
public static extern Error uc_mem_unmap(IntPtr uc, ulong address, ulong size);
[DllImport("unicorn", CallingConvention = CallingConvention.Cdecl)]
public static extern UnicornError uc_mem_protect(IntPtr uc, ulong address, ulong size, uint perms);
public static extern Error uc_mem_protect(IntPtr uc, ulong address, ulong size, uint perms);
[DllImport("unicorn", CallingConvention = CallingConvention.Cdecl)]
public static extern UnicornError uc_mem_regions(IntPtr uc, out IntPtr regions, out uint count);
public static extern Error uc_mem_regions(IntPtr uc, out IntPtr regions, out uint count);
}
}
}

View File

@@ -1,14 +0,0 @@
namespace Ryujinx.Tests.Unicorn.Native
{
public enum UnicornArch : uint
{
UC_ARCH_ARM = 1, // ARM architecture (including Thumb, Thumb-2)
UC_ARCH_ARM64, // ARM-64, also called AArch64
UC_ARCH_MIPS, // Mips architecture
UC_ARCH_X86, // X86 architecture (including x86 & x86-64)
UC_ARCH_PPC, // PowerPC architecture (currently unsupported)
UC_ARCH_SPARC, // Sparc architecture
UC_ARCH_M68K, // M68K architecture
UC_ARCH_MAX,
}
}

View File

@@ -1,33 +0,0 @@
// ReSharper disable InconsistentNaming
namespace Ryujinx.Tests.Unicorn.Native
{
public enum UnicornMode : uint
{
UC_MODE_LITTLE_ENDIAN = 0, // little-endian mode (default mode)
UC_MODE_BIG_ENDIAN = 1 << 30, // big-endian mode
// arm / arm64
UC_MODE_ARM = 0, // ARM mode
UC_MODE_THUMB = 1 << 4, // THUMB mode (including Thumb-2)
UC_MODE_MCLASS = 1 << 5, // ARM's Cortex-M series (currently unsupported)
UC_MODE_V8 = 1 << 6, // ARMv8 A32 encodings for ARM (currently unsupported)
// mips
UC_MODE_MICRO = 1 << 4, // MicroMips mode (currently unsupported)
UC_MODE_MIPS3 = 1 << 5, // Mips III ISA (currently unsupported)
UC_MODE_MIPS32R6 = 1 << 6, // Mips32r6 ISA (currently unsupported)
UC_MODE_MIPS32 = 1 << 2, // Mips32 ISA
UC_MODE_MIPS64 = 1 << 3, // Mips64 ISA
// x86 / x64
UC_MODE_16 = 1 << 1, // 16-bit mode
UC_MODE_32 = 1 << 2, // 32-bit mode
UC_MODE_64 = 1 << 3, // 64-bit mode
// ppc
UC_MODE_PPC32 = 1 << 2, // 32-bit mode (currently unsupported)
UC_MODE_PPC64 = 1 << 3, // 64-bit mode (currently unsupported)
UC_MODE_QPX = 1 << 4, // Quad Processing eXtensions mode (currently unsupported)
// sparc
UC_MODE_SPARC32 = 1 << 2, // 32-bit mode
UC_MODE_SPARC64 = 1 << 3, // 64-bit mode
UC_MODE_V9 = 1 << 4, // SparcV9 mode (currently unsupported)
// m68k
}
}

View File

@@ -1,4 +1,5 @@
using Ryujinx.Tests.Unicorn.Native;
using Ryujinx.Tests.Unicorn.Native.Const;
using System;
namespace Ryujinx.Tests.Unicorn
@@ -30,32 +31,32 @@ namespace Ryujinx.Tests.Unicorn
public uint LR
{
get => GetRegister(Arm32Register.LR);
set => SetRegister(Arm32Register.LR, value);
get => GetRegister(Arm.REG_LR);
set => SetRegister(Arm.REG_LR, value);
}
public uint SP
{
get => GetRegister(Arm32Register.SP);
set => SetRegister(Arm32Register.SP, value);
get => GetRegister(Arm.REG_SP);
set => SetRegister(Arm.REG_SP, value);
}
public uint PC
{
get => GetRegister(Arm32Register.PC) & 0xfffffffeu;
set => SetRegister(Arm32Register.PC, (value & 0xfffffffeu) | (ThumbFlag ? 1u : 0u));
get => GetRegister(Arm.REG_PC) & 0xfffffffeu;
set => SetRegister(Arm.REG_PC, (value & 0xfffffffeu) | (ThumbFlag ? 1u : 0u));
}
public uint CPSR
{
get => (uint)GetRegister(Arm32Register.CPSR);
set => SetRegister(Arm32Register.CPSR, (uint)value);
get => GetRegister(Arm.REG_CPSR);
set => SetRegister(Arm.REG_CPSR, value);
}
public int Fpscr
{
get => (int)GetRegister(Arm32Register.FPSCR) | ((int)GetRegister(Arm32Register.FPSCR_NZCV));
set => SetRegister(Arm32Register.FPSCR, (uint)value);
get => (int)GetRegister(Arm.REG_FPSCR) | ((int)GetRegister(Arm.REG_FPSCR_NZCV));
set => SetRegister(Arm.REG_FPSCR, (uint)value);
}
public bool QFlag
@@ -94,16 +95,16 @@ namespace Ryujinx.Tests.Unicorn
set
{
CPSR = (CPSR & ~0x00000020u) | (value ? 0x00000020u : 0u);
SetRegister(Arm32Register.PC, (GetRegister(Arm32Register.PC) & 0xfffffffeu) | (value ? 1u : 0u));
SetRegister(Arm.REG_PC, (GetRegister(Arm.REG_PC) & 0xfffffffeu) | (value ? 1u : 0u));
}
}
public UnicornAArch32()
{
Interface.Checked(Interface.uc_open(UnicornArch.UC_ARCH_ARM, UnicornMode.UC_MODE_LITTLE_ENDIAN, out uc));
Interface.Checked(Interface.uc_open(Arch.ARM, Mode.LITTLE_ENDIAN, out uc));
SetRegister(Arm32Register.C1_C0_2, GetRegister(Arm32Register.C1_C0_2) | 0xf00000);
SetRegister(Arm32Register.FPEXC, 0x40000000);
SetRegister(Arm.REG_C1_C0_2, GetRegister(Arm.REG_C1_C0_2) | 0xf00000);
SetRegister(Arm.REG_FPEXC, 0x40000000);
}
~UnicornAArch32()
@@ -136,44 +137,44 @@ namespace Ryujinx.Tests.Unicorn
RunForCount(1);
}
private static Arm32Register[] XRegisters = new Arm32Register[16]
private static Arm[] XRegisters = new Arm[16]
{
Arm32Register.R0,
Arm32Register.R1,
Arm32Register.R2,
Arm32Register.R3,
Arm32Register.R4,
Arm32Register.R5,
Arm32Register.R6,
Arm32Register.R7,
Arm32Register.R8,
Arm32Register.R9,
Arm32Register.R10,
Arm32Register.R11,
Arm32Register.R12,
Arm32Register.R13,
Arm32Register.R14,
Arm32Register.R15,
Arm.REG_R0,
Arm.REG_R1,
Arm.REG_R2,
Arm.REG_R3,
Arm.REG_R4,
Arm.REG_R5,
Arm.REG_R6,
Arm.REG_R7,
Arm.REG_R8,
Arm.REG_R9,
Arm.REG_R10,
Arm.REG_R11,
Arm.REG_R12,
Arm.REG_R13,
Arm.REG_R14,
Arm.REG_R15,
};
private static Arm32Register[] QRegisters = new Arm32Register[16]
private static Arm[] QRegisters = new Arm[16]
{
Arm32Register.Q0,
Arm32Register.Q1,
Arm32Register.Q2,
Arm32Register.Q3,
Arm32Register.Q4,
Arm32Register.Q5,
Arm32Register.Q6,
Arm32Register.Q7,
Arm32Register.Q8,
Arm32Register.Q9,
Arm32Register.Q10,
Arm32Register.Q11,
Arm32Register.Q12,
Arm32Register.Q13,
Arm32Register.Q14,
Arm32Register.Q15
Arm.REG_Q0,
Arm.REG_Q1,
Arm.REG_Q2,
Arm.REG_Q3,
Arm.REG_Q4,
Arm.REG_Q5,
Arm.REG_Q6,
Arm.REG_Q7,
Arm.REG_Q8,
Arm.REG_Q9,
Arm.REG_Q10,
Arm.REG_Q11,
Arm.REG_Q12,
Arm.REG_Q13,
Arm.REG_Q14,
Arm.REG_Q15
};
public uint GetX(int index)
@@ -204,7 +205,7 @@ namespace Ryujinx.Tests.Unicorn
}
// Getting quadword registers from Unicorn A32 seems to be broken, so we combine its 2 doubleword registers instead.
return GetVector((Arm32Register)((int)Arm32Register.D0 + index * 2));
return GetVector((Arm)((int)Arm.REG_D0 + index * 2));
}
public void SetQ(int index, SimdValue value)
@@ -214,10 +215,10 @@ namespace Ryujinx.Tests.Unicorn
throw new ArgumentOutOfRangeException(nameof(index));
}
SetVector((Arm32Register)((int)Arm32Register.D0 + index * 2), value);
SetVector((Arm)((int)Arm.REG_D0 + index * 2), value);
}
public uint GetRegister(Arm32Register register)
public uint GetRegister(Arm register)
{
byte[] data = new byte[4];
@@ -226,14 +227,14 @@ namespace Ryujinx.Tests.Unicorn
return (uint)BitConverter.ToInt32(data, 0);
}
public void SetRegister(Arm32Register register, uint value)
public void SetRegister(Arm register, uint value)
{
byte[] data = BitConverter.GetBytes(value);
Interface.Checked(Interface.uc_reg_write(uc, (int)register, data));
}
public SimdValue GetVector(Arm32Register register)
public SimdValue GetVector(Arm register)
{
byte[] data = new byte[8];
@@ -245,7 +246,7 @@ namespace Ryujinx.Tests.Unicorn
return new SimdValue(lo, hi);
}
private void SetVector(Arm32Register register, SimdValue value)
private void SetVector(Arm register, SimdValue value)
{
byte[] data = BitConverter.GetBytes(value.GetUInt64(0));
Interface.Checked(Interface.uc_reg_write(uc, (int)register, data));
@@ -300,13 +301,10 @@ namespace Ryujinx.Tests.Unicorn
try
{
Interface.uc_version(out _, out _);
}
catch (DllNotFoundException) { }
return true;
}
catch (DllNotFoundException)
{
return false;
}
return Interface.IsUnicornAvailable;
}
}
}
}

View File

@@ -1,4 +1,5 @@
using Ryujinx.Tests.Unicorn.Native;
using Ryujinx.Tests.Unicorn.Native.Const;
using System;
namespace Ryujinx.Tests.Unicorn
@@ -30,38 +31,38 @@ namespace Ryujinx.Tests.Unicorn
public ulong LR
{
get => GetRegister(ArmRegister.LR);
set => SetRegister(ArmRegister.LR, value);
get => GetRegister(Arm64.REG_LR);
set => SetRegister(Arm64.REG_LR, value);
}
public ulong SP
{
get => GetRegister(ArmRegister.SP);
set => SetRegister(ArmRegister.SP, value);
get => GetRegister(Arm64.REG_SP);
set => SetRegister(Arm64.REG_SP, value);
}
public ulong PC
{
get => GetRegister(ArmRegister.PC);
set => SetRegister(ArmRegister.PC, value);
get => GetRegister(Arm64.REG_PC);
set => SetRegister(Arm64.REG_PC, value);
}
public uint Pstate
{
get => (uint)GetRegister(ArmRegister.PSTATE);
set => SetRegister(ArmRegister.PSTATE, (uint)value);
get => (uint)GetRegister(Arm64.REG_PSTATE);
set => SetRegister(Arm64.REG_PSTATE, (uint)value);
}
public int Fpcr
{
get => (int)GetRegister(ArmRegister.FPCR);
set => SetRegister(ArmRegister.FPCR, (uint)value);
get => (int)GetRegister(Arm64.REG_FPCR);
set => SetRegister(Arm64.REG_FPCR, (uint)value);
}
public int Fpsr
{
get => (int)GetRegister(ArmRegister.FPSR);
set => SetRegister(ArmRegister.FPSR, (uint)value);
get => (int)GetRegister(Arm64.REG_FPSR);
set => SetRegister(Arm64.REG_FPSR, (uint)value);
}
public bool OverflowFlag
@@ -90,9 +91,9 @@ namespace Ryujinx.Tests.Unicorn
public UnicornAArch64()
{
Interface.Checked(Interface.uc_open(UnicornArch.UC_ARCH_ARM64, UnicornMode.UC_MODE_LITTLE_ENDIAN, out uc));
Interface.Checked(Interface.uc_open(Arch.ARM64, Mode.LITTLE_ENDIAN, out uc));
SetRegister(ArmRegister.CPACR_EL1, 0x00300000);
SetRegister(Arm64.REG_CPACR_EL1, 0x00300000);
}
~UnicornAArch64()
@@ -125,75 +126,75 @@ namespace Ryujinx.Tests.Unicorn
RunForCount(1);
}
private static ArmRegister[] XRegisters = new ArmRegister[31]
private static Arm64[] XRegisters = new Arm64[31]
{
ArmRegister.X0,
ArmRegister.X1,
ArmRegister.X2,
ArmRegister.X3,
ArmRegister.X4,
ArmRegister.X5,
ArmRegister.X6,
ArmRegister.X7,
ArmRegister.X8,
ArmRegister.X9,
ArmRegister.X10,
ArmRegister.X11,
ArmRegister.X12,
ArmRegister.X13,
ArmRegister.X14,
ArmRegister.X15,
ArmRegister.X16,
ArmRegister.X17,
ArmRegister.X18,
ArmRegister.X19,
ArmRegister.X20,
ArmRegister.X21,
ArmRegister.X22,
ArmRegister.X23,
ArmRegister.X24,
ArmRegister.X25,
ArmRegister.X26,
ArmRegister.X27,
ArmRegister.X28,
ArmRegister.X29,
ArmRegister.X30,
Arm64.REG_X0,
Arm64.REG_X1,
Arm64.REG_X2,
Arm64.REG_X3,
Arm64.REG_X4,
Arm64.REG_X5,
Arm64.REG_X6,
Arm64.REG_X7,
Arm64.REG_X8,
Arm64.REG_X9,
Arm64.REG_X10,
Arm64.REG_X11,
Arm64.REG_X12,
Arm64.REG_X13,
Arm64.REG_X14,
Arm64.REG_X15,
Arm64.REG_X16,
Arm64.REG_X17,
Arm64.REG_X18,
Arm64.REG_X19,
Arm64.REG_X20,
Arm64.REG_X21,
Arm64.REG_X22,
Arm64.REG_X23,
Arm64.REG_X24,
Arm64.REG_X25,
Arm64.REG_X26,
Arm64.REG_X27,
Arm64.REG_X28,
Arm64.REG_X29,
Arm64.REG_X30,
};
private static ArmRegister[] QRegisters = new ArmRegister[32]
private static Arm64[] QRegisters = new Arm64[32]
{
ArmRegister.Q0,
ArmRegister.Q1,
ArmRegister.Q2,
ArmRegister.Q3,
ArmRegister.Q4,
ArmRegister.Q5,
ArmRegister.Q6,
ArmRegister.Q7,
ArmRegister.Q8,
ArmRegister.Q9,
ArmRegister.Q10,
ArmRegister.Q11,
ArmRegister.Q12,
ArmRegister.Q13,
ArmRegister.Q14,
ArmRegister.Q15,
ArmRegister.Q16,
ArmRegister.Q17,
ArmRegister.Q18,
ArmRegister.Q19,
ArmRegister.Q20,
ArmRegister.Q21,
ArmRegister.Q22,
ArmRegister.Q23,
ArmRegister.Q24,
ArmRegister.Q25,
ArmRegister.Q26,
ArmRegister.Q27,
ArmRegister.Q28,
ArmRegister.Q29,
ArmRegister.Q30,
ArmRegister.Q31,
Arm64.REG_Q0,
Arm64.REG_Q1,
Arm64.REG_Q2,
Arm64.REG_Q3,
Arm64.REG_Q4,
Arm64.REG_Q5,
Arm64.REG_Q6,
Arm64.REG_Q7,
Arm64.REG_Q8,
Arm64.REG_Q9,
Arm64.REG_Q10,
Arm64.REG_Q11,
Arm64.REG_Q12,
Arm64.REG_Q13,
Arm64.REG_Q14,
Arm64.REG_Q15,
Arm64.REG_Q16,
Arm64.REG_Q17,
Arm64.REG_Q18,
Arm64.REG_Q19,
Arm64.REG_Q20,
Arm64.REG_Q21,
Arm64.REG_Q22,
Arm64.REG_Q23,
Arm64.REG_Q24,
Arm64.REG_Q25,
Arm64.REG_Q26,
Arm64.REG_Q27,
Arm64.REG_Q28,
Arm64.REG_Q29,
Arm64.REG_Q30,
Arm64.REG_Q31,
};
public ulong GetX(int index)
@@ -236,7 +237,7 @@ namespace Ryujinx.Tests.Unicorn
SetVector(QRegisters[index], value);
}
private ulong GetRegister(ArmRegister register)
private ulong GetRegister(Arm64 register)
{
byte[] data = new byte[8];
@@ -245,14 +246,14 @@ namespace Ryujinx.Tests.Unicorn
return (ulong)BitConverter.ToInt64(data, 0);
}
private void SetRegister(ArmRegister register, ulong value)
private void SetRegister(Arm64 register, ulong value)
{
byte[] data = BitConverter.GetBytes(value);
Interface.Checked(Interface.uc_reg_write(uc, (int)register, data));
}
private SimdValue GetVector(ArmRegister register)
private SimdValue GetVector(Arm64 register)
{
byte[] data = new byte[16];
@@ -261,7 +262,7 @@ namespace Ryujinx.Tests.Unicorn
return new SimdValue(data);
}
private void SetVector(ArmRegister register, SimdValue value)
private void SetVector(Arm64 register, SimdValue value)
{
byte[] data = value.ToArray();
@@ -315,13 +316,10 @@ namespace Ryujinx.Tests.Unicorn
try
{
Interface.uc_version(out _, out _);
}
catch (DllNotFoundException) { }
return true;
}
catch (DllNotFoundException)
{
return false;
}
return Interface.IsUnicornAvailable;
}
}
}

View File

@@ -1,29 +0,0 @@
// ReSharper disable InconsistentNaming
namespace Ryujinx.Tests.Unicorn
{
public enum UnicornError
{
UC_ERR_OK = 0, // No error: everything was fine
UC_ERR_NOMEM, // Out-Of-Memory error: uc_open(), uc_emulate()
UC_ERR_ARCH, // Unsupported architecture: uc_open()
UC_ERR_HANDLE, // Invalid handle
UC_ERR_MODE, // Invalid/unsupported mode: uc_open()
UC_ERR_VERSION, // Unsupported version (bindings)
UC_ERR_READ_UNMAPPED, // Quit emulation due to READ on unmapped memory: uc_emu_start()
UC_ERR_WRITE_UNMAPPED, // Quit emulation due to WRITE on unmapped memory: uc_emu_start()
UC_ERR_FETCH_UNMAPPED, // Quit emulation due to FETCH on unmapped memory: uc_emu_start()
UC_ERR_HOOK, // Invalid hook type: uc_hook_add()
UC_ERR_INSN_INVALID, // Quit emulation due to invalid instruction: uc_emu_start()
UC_ERR_MAP, // Invalid memory mapping: uc_mem_map()
UC_ERR_WRITE_PROT, // Quit emulation due to UC_MEM_WRITE_PROT violation: uc_emu_start()
UC_ERR_READ_PROT, // Quit emulation due to UC_MEM_READ_PROT violation: uc_emu_start()
UC_ERR_FETCH_PROT, // Quit emulation due to UC_MEM_FETCH_PROT violation: uc_emu_start()
UC_ERR_ARG, // Invalid argument provided to uc_xxx function (See specific function API)
UC_ERR_READ_UNALIGNED, // Unaligned read
UC_ERR_WRITE_UNALIGNED, // Unaligned write
UC_ERR_FETCH_UNALIGNED, // Unaligned fetch
UC_ERR_HOOK_EXIST, // hook for this event already existed
UC_ERR_RESOURCE, // Insufficient resource: uc_emu_start()
UC_ERR_EXCEPTION // Unhandled CPU exception
}
}

View File

@@ -1,3 +1,4 @@
using Ryujinx.Tests.Unicorn.Native.Const;
using System;
using System.Runtime.InteropServices;
@@ -5,9 +6,9 @@ namespace Ryujinx.Tests.Unicorn
{
public class UnicornException : Exception
{
public readonly UnicornError Error;
public readonly Error Error;
internal UnicornException(UnicornError error)
internal UnicornException(Error error)
{
Error = error;
}
@@ -20,4 +21,4 @@ namespace Ryujinx.Tests.Unicorn
}
}
}
}
}

View File

@@ -9,14 +9,12 @@ CPU simulator, Armeilleure.
On Windows, Unicorn is shipped as a pre-compiled dynamic library (`.dll`), licenced under the GPLv2.
The source code for `windows/unicorn.dll` is available at: https://github.com/MerryMage/UnicornDotNet/tree/299451c02d9c810d2feca51f5e9cb6d8b2f38960
The source code for `windows/unicorn.dll` is available at: https://github.com/unicorn-engine/unicorn/tree/df3aa0fccbce9e1420e82110cbae5951755a0698
## Linux
On Linux, you will first need to download Unicorn from https://github.com/unicorn-engine/unicorn.
On Windows, Unicorn is shipped as a pre-compiled shared object (`.so`), licenced under the GPLv2.
Then you need to patch it to expose the FSPCR register by applying `linux/unicorn_fspcr.patch`
Then, compile Unicorn from source with its `make.sh` script.
The source code for `linux/unicorn.so` is available at: https://github.com/unicorn-engine/unicorn/tree/df3aa0fccbce9e1420e82110cbae5951755a0698
See https://github.com/Ryujinx/Ryujinx/pull/1433 for details.

Binary file not shown.

View File

@@ -1,24 +0,0 @@
diff --git a/qemu/target-arm/unicorn_arm.c b/qemu/target-arm/unicorn_arm.c
index 5ff9ebb..d4953f4 100644
--- a/qemu/target-arm/unicorn_arm.c
+++ b/qemu/target-arm/unicorn_arm.c
@@ -101,6 +101,9 @@ int arm_reg_read(struct uc_struct *uc, unsigned int *regs, void **vals, int coun
case UC_ARM_REG_FPEXC:
*(int32_t *)value = ARM_CPU(uc, mycpu)->env.vfp.xregs[ARM_VFP_FPEXC];
break;
+ case UC_ARM_REG_FPSCR:
+ *(int32_t *)value = vfp_get_fpscr(&ARM_CPU(uc, mycpu)->env);
+ break;
case UC_ARM_REG_IPSR:
*(uint32_t *)value = xpsr_read(&ARM_CPU(uc, mycpu)->env) & 0x1ff;
break;
@@ -175,6 +178,9 @@ int arm_reg_write(struct uc_struct *uc, unsigned int *regs, void* const* vals, i
case UC_ARM_REG_FPEXC:
ARM_CPU(uc, mycpu)->env.vfp.xregs[ARM_VFP_FPEXC] = *(int32_t *)value;
break;
+ case UC_ARM_REG_FPSCR:
+ vfp_set_fpscr(&ARM_CPU(uc, mycpu)->env, *(uint32_t *)value);
+ break;
case UC_ARM_REG_IPSR:
xpsr_write(&ARM_CPU(uc, mycpu)->env, *(uint32_t *)value, 0x1ff);
break;

View File

@@ -0,0 +1,199 @@
#!/usr/bin/env python3
# Unicorn Engine
# By Dang Hoang Vu, 2013
# Modified for Ryujinx from: https://github.com/unicorn-engine/unicorn/blob/6c1cbef6ac505d355033aef1176b684d02e1eb3a/bindings/const_generator.py
from __future__ import print_function
import sys, re, os
include = [ 'arm.h', 'arm64.h', 'unicorn.h' ]
split_common = [ 'ARCH', 'MODE', 'ERR', 'MEM', 'TCG', 'HOOK', 'PROT' ]
template = {
'dotnet': {
'header': "// Constants for Unicorn Engine. AUTO-GENERATED FILE, DO NOT EDIT\n\n// ReSharper disable InconsistentNaming\nnamespace Ryujinx.Tests.Unicorn.Native.Const\n{\n public enum %s\n {\n",
'footer': " }\n}\n",
'line_format': ' %s = %s,\n',
'out_file': os.path.join(os.path.dirname(__file__), 'Native', 'Const', '%s.cs'),
# prefixes for constant filenames of all archs - case sensitive
'arm.h': 'Arm',
'arm64.h': 'Arm64',
'unicorn.h': 'Common',
# prefixes for filenames of split_common values - case sensitive
'ARCH': 'Arch',
'MODE': 'Mode',
'ERR': 'Error',
'MEM': 'Memory',
'TCG': 'TCG',
'HOOK': 'Hook',
'PROT': 'Permission',
'comment_open': ' //',
'comment_close': '',
}
}
# markup for comments to be added to autogen files
MARKUP = '//>'
def gen(unicorn_repo_path):
global include
include_dir = os.path.join(unicorn_repo_path, 'include', 'unicorn')
templ = template["dotnet"]
for target in include:
prefix = templ[target]
outfile = open(templ['out_file'] %(prefix), 'wb') # open as binary prevents windows newlines
outfile.write((templ['header'] % (prefix)).encode("utf-8"))
if target == 'unicorn.h':
prefix = ''
for cat in split_common:
with open(templ['out_file'] %(templ[cat]), 'wb') as file:
file.write((templ['header'] %(templ[cat])).encode("utf-8"))
with open(os.path.join(include_dir, target)) as f:
lines = f.readlines()
previous = {}
count = 0
skip = 0
in_comment = False
for lno, line in enumerate(lines):
if "/*" in line:
in_comment = True
if "*/" in line:
in_comment = False
if in_comment:
continue
if skip > 0:
# Due to clang-format, values may come up in the next line
skip -= 1
continue
line = line.strip()
if line.startswith(MARKUP): # markup for comments
outfile.write(("\n%s%s%s\n" %(templ['comment_open'], \
line.replace(MARKUP, ''), templ['comment_close'])).encode("utf-8"))
continue
if line == '' or line.startswith('//'):
continue
tmp = line.strip().split(',')
if len(tmp) >= 2 and tmp[0] != "#define" and not tmp[0].startswith("UC_"):
continue
for t in tmp:
t = t.strip()
if not t or t.startswith('//'): continue
f = re.split('\s+', t)
# parse #define UC_TARGET (num)
define = False
if f[0] == '#define' and len(f) >= 3:
define = True
f.pop(0)
f.insert(1, '=')
if f[0].startswith("UC_" + prefix.upper()) or f[0].startswith("UC_CPU"):
if len(f) > 1 and f[1] not in ('//', '='):
print("WARNING: Unable to convert %s" % f)
print(" Line =", line)
continue
elif len(f) > 1 and f[1] == '=':
# Like:
# UC_A =
# (1 << 2)
# #define UC_B \
# (UC_A | UC_C)
# Let's search the next line
if len(f) == 2:
if lno == len(lines) - 1:
print("WARNING: Unable to convert %s" % f)
print(" Line =", line)
continue
skip += 1
next_line = lines[lno + 1]
next_line_tmp = next_line.strip().split(",")
rhs = next_line_tmp[0]
elif f[-1] == "\\":
idx = 0
rhs = ""
while True:
idx += 1
if lno + idx == len(lines):
print("WARNING: Unable to convert %s" % f)
print(" Line =", line)
continue
skip += 1
next_line = lines[lno + idx]
next_line_f = re.split('\s+', next_line.strip())
if next_line_f[-1] == "\\":
rhs += "".join(next_line_f[:-1])
else:
rhs += next_line.strip()
break
else:
rhs = ''.join(f[2:])
else:
rhs = str(count)
lhs = f[0].strip()
#print(f'lhs: {lhs} rhs: {rhs} f:{f}')
# evaluate bitshifts in constants e.g. "UC_X86 = 1 << 1"
match = re.match(r'(?P<rhs>\s*\d+\s*<<\s*\d+\s*)', rhs)
if match:
rhs = str(eval(match.group(1)))
else:
# evaluate references to other constants e.g. "UC_ARM_REG_X = UC_ARM_REG_SP"
match = re.match(r'^([^\d]\w+)$', rhs)
if match:
rhs = previous[match.group(1)]
if not rhs.isdigit():
for k, v in previous.items():
rhs = re.sub(r'\b%s\b' % k, v, rhs)
rhs = str(eval(rhs))
lhs_strip = re.sub(r'^UC_', '', lhs)
count = int(rhs) + 1
if target == "unicorn.h":
matched_cat = False
for cat in split_common:
if lhs_strip.startswith(f"{cat}_"):
with open(templ['out_file'] %(templ[cat]), 'ab') as cat_file:
cat_lhs_strip = lhs_strip
if not lhs_strip.lstrip(f"{cat}_").isnumeric():
cat_lhs_strip = lhs_strip.replace(f"{cat}_", "", 1)
cat_file.write(
(templ['line_format'] % (cat_lhs_strip, rhs)).encode("utf-8"))
matched_cat = True
break
if matched_cat:
previous[lhs] = str(rhs)
continue
if (count == 1):
outfile.write(("\n").encode("utf-8"))
if lhs_strip.startswith(f"{prefix.upper()}_") and not lhs_strip.replace(f"{prefix.upper()}_", "", 1).isnumeric():
lhs_strip = lhs_strip.replace(f"{prefix.upper()}_", "", 1)
outfile.write((templ['line_format'] % (lhs_strip, rhs)).encode("utf-8"))
previous[lhs] = str(rhs)
outfile.write((templ['footer']).encode("utf-8"))
outfile.close()
if target == "unicorn.h":
for cat in split_common:
with open(templ['out_file'] %(templ[cat]), 'ab') as cat_file:
cat_file.write(templ['footer'].encode('utf-8'))
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage:", sys.argv[0], " <path to unicorn repo>")
sys.exit(1)
unicorn_repo_path = sys.argv[1]
if os.path.isdir(unicorn_repo_path):
print("Generating constants for dotnet")
gen(unicorn_repo_path)
else:
print("Couldn't find unicorn repo at:", unicorn_repo_path)

View File

@@ -38,14 +38,11 @@ namespace Ryujinx.Tests.Cpu
private bool _usingMemory;
static CpuTest()
[OneTimeSetUp]
public void OneTimeSetup()
{
_unicornAvailable = UnicornAArch64.IsAvailable();
if (!_unicornAvailable)
{
Console.WriteLine("WARNING: Could not find Unicorn.");
}
Assume.That(_unicornAvailable, "Unicorn is not available");
}
[SetUp]
@@ -610,4 +607,4 @@ namespace Ryujinx.Tests.Cpu
return rnd & 0x800FFFFFFFFFFFFFul;
}
}
}
}

View File

@@ -33,14 +33,11 @@ namespace Ryujinx.Tests.Cpu
private bool _usingMemory;
static CpuTest32()
[OneTimeSetUp]
public void OneTimeSetup()
{
_unicornAvailable = UnicornAArch32.IsAvailable();
if (!_unicornAvailable)
{
Console.WriteLine("WARNING: Could not find Unicorn.");
}
Assume.That(_unicornAvailable, "Unicorn is not available");
}
[SetUp]

View File

@@ -34,7 +34,17 @@
</ItemGroup>
<Target Name="CopyUnicorn" AfterTargets="Build">
<Copy SourceFiles="..\Ryujinx.Tests.Unicorn\libs\$(TargetOS)\unicorn.dll" DestinationFolder="$(OutputPath)" ContinueOnError="true" />
<ItemGroup>
<UnicornLib Include="..\Ryujinx.Tests.Unicorn\libs\$(TargetOS)\*unicorn.*"/>
</ItemGroup>
<Copy SourceFiles="@(UnicornLib)" DestinationFolder="$(OutputPath)" ContinueOnError="true" />
</Target>
<Target Name="CleanUnicorn" BeforeTargets="Clean">
<ItemGroup>
<UnicornLib Include="$(OutputPath)/unicorn.*"/>
</ItemGroup>
<Delete Files="@(UnicornLib)" />
</Target>
</Project>

View File

@@ -26,7 +26,7 @@
<PackageReference Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
<PackageReference Include="OpenTK.Graphics" Version="4.7.2" />
<PackageReference Include="SPB" Version="0.0.4-build28" />
<PackageReference Include="SharpZipLib" Version="1.3.3" />
<PackageReference Include="SharpZipLib" Version="1.4.1" />
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.4" />
</ItemGroup>

View File

@@ -735,7 +735,6 @@ namespace Ryujinx.Ui
_emulationContext.Dispose();
SwitchToGameTable();
RendererWidget.Dispose();
return;
}
@@ -747,7 +746,6 @@ namespace Ryujinx.Ui
_emulationContext.Dispose();
SwitchToGameTable();
RendererWidget.Dispose();
return;
}
@@ -770,7 +768,6 @@ namespace Ryujinx.Ui
_emulationContext.Dispose();
SwitchToGameTable();
RendererWidget.Dispose();
return;
}

View File

@@ -519,10 +519,14 @@ namespace Ryujinx.Ui
_gpuCancellationTokenSource.Cancel();
_isStopped = true;
_isActive = false;
if (_isActive)
{
_isActive = false;
_exitEvent.WaitOne();
_exitEvent.Dispose();
_exitEvent.WaitOne();
_exitEvent.Dispose();
}
}
private void NVStutterWorkaround()

View File

@@ -72,7 +72,8 @@ namespace Ryujinx.Ui
protected override void Dispose(bool disposing)
{
Device.DisposeGpu();
Device?.DisposeGpu();
NpadManager.Dispose();
}
}