Compare commits

..

7 Commits

Author SHA1 Message Date
merry
492056abf6 Ava: Make Avalonia use our logging system (#4231)
* Ava: Make Avalonia use our logging system

* LoggerAdapter: Address review comments

* Update Ryujinx.Common/Logging/LogClass.cs

Co-authored-by: Ac_K <Acoustik666@gmail.com>
2023-01-09 04:32:20 +01:00
Isaac Marovitz
ee6e682ab4 Fix selection bar (#4236) 2023-01-09 03:36:11 +01:00
gdkchan
6f60e102a2 HIPC: Fix reply possibly also receiving one request (#4232) 2023-01-08 15:34:49 -03:00
Isaac Marovitz
eeb2af9953 Ava GUI: MainWindow Refactor (#4178)
* Fix redundancies

* Add back elses

* `MainWindow` Refactor

* Switch commands to `ReflectionBinding`

Not required in Ava 11

* Update Ryujinx.Ava/AppHost.cs

Co-authored-by: Ac_K <Acoustik666@gmail.com>

* Update Ryujinx.Ava/AppHost.cs

Co-authored-by: Ac_K <Acoustik666@gmail.com>

* Update Ryujinx.Ava/UI/Windows/MainWindow.axaml.cs

Co-authored-by: Ac_K <Acoustik666@gmail.com>

* Update Ryujinx.Ava/UI/Windows/MainWindow.axaml.cs

Co-authored-by: Ac_K <Acoustik666@gmail.com>

* Update Ryujinx.Ava/UI/Windows/MainWindow.axaml.cs

Co-authored-by: Ac_K <Acoustik666@gmail.com>

* Update Ryujinx.Ava/AppHost.cs

Co-authored-by: Ac_K <Acoustik666@gmail.com>

* Update Ryujinx.Ava/UI/Views/Main/MainViewControls.axaml.cs

Co-authored-by: Ac_K <Acoustik666@gmail.com>

* Update Ryujinx.Ava/AppHost.cs

Co-authored-by: Ac_K <Acoustik666@gmail.com>

* Update Ryujinx.Ava/AppHost.cs

Co-authored-by: Ac_K <Acoustik666@gmail.com>

* Update Ryujinx.Ava/AppHost.cs

Co-authored-by: Ac_K <Acoustik666@gmail.com>

* Update Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs

Co-authored-by: Ac_K <Acoustik666@gmail.com>

* Update Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs

Co-authored-by: Ac_K <Acoustik666@gmail.com>

* Update Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs

Co-authored-by: Ac_K <Acoustik666@gmail.com>

* Update Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs

Co-authored-by: Ac_K <Acoustik666@gmail.com>

* Update Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs

Co-authored-by: Ac_K <Acoustik666@gmail.com>

* Update Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs

Co-authored-by: Ac_K <Acoustik666@gmail.com>

* Update Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs

Co-authored-by: Ac_K <Acoustik666@gmail.com>

* Update Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs

Co-authored-by: Ac_K <Acoustik666@gmail.com>

* Update Ryujinx.Ava/UI/Views/Main/MainMenuBarView.axaml.cs

Co-authored-by: Ac_K <Acoustik666@gmail.com>

* Update Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs

Co-authored-by: Ac_K <Acoustik666@gmail.com>

* Update Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs

Co-authored-by: Ac_K <Acoustik666@gmail.com>

* Update Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs

Co-authored-by: Ac_K <Acoustik666@gmail.com>

* Update Ryujinx.Ava/UI/Views/Main/MainViewControls.axaml.cs

Co-authored-by: Ac_K <Acoustik666@gmail.com>

* Resolve issues

* Remove Ava 11 Fix

* Update Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs

Co-authored-by: gdkchan <gab.dark.100@gmail.com>

* Update Ryujinx.Ava/UI/Windows/MainWindow.axaml.cs

Co-authored-by: gdkchan <gab.dark.100@gmail.com>

* Fix whitespace + other suggestions

* Move Vsync colours to `Styles.xaml`

* Remove catch all

* Use `switch` instead of `if`

* Update locale keys

* Use block-scoped namespaces

* Fix improper Ava api usage then

* Static PTC

* Fix `GridItemSelectorSize` with `ShowNames`

* Update for new About Window

* Add back search fix

Co-authored-by: Ac_K <Acoustik666@gmail.com>
Co-authored-by: gdkchan <gab.dark.100@gmail.com>
2023-01-08 18:46:25 +01:00
Ac_K
550747eac6 Horizon: Impl Prepo, Fixes bugs, Clean things (#4220)
* Horizon: Impl Prepo, Fixes bugs, Clean things

* remove ToArray()

* resultCode > status

* Remove old services

* Addresses gdkchan's comments and more cleanup

* Addresses Gdkchan's feedback 2

* Reorganize services, make sure service are loaded before guest

Co-Authored-By: gdkchan <5624669+gdkchan@users.noreply.github.com>

* Create interfaces for lm and sm

Co-authored-by: gdkchan <5624669+gdkchan@users.noreply.github.com>
2023-01-08 12:13:39 +00:00
merry
3ffceab1fb MainWindow: Vertically center SearchBox TextPresenter (#4223) 2023-01-07 16:01:53 +00:00
Mary-nyan
b9f2a96595 ava: Fix regression caused by #4013 (#4222)
Avalonia seems to not like when the artifact doesns't match the root namespace...

Address that by moving the binary to "Ryujinx" like we do on macOS build.
2023-01-07 12:24:21 +01:00
110 changed files with 3393 additions and 2598 deletions

View File

@@ -69,6 +69,12 @@ jobs:
- name: Publish Ryujinx.Ava - name: Publish Ryujinx.Ava
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish_ava -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER Ryujinx.Ava --self-contained true run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish_ava -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER Ryujinx.Ava --self-contained true
if: github.event_name == 'pull_request' if: github.event_name == 'pull_request'
- name: Rename Avalonia (Windows)
run: mv ./publish_ava/Ryujinx.Ava.exe ./publish_ava/Ryujinx.exe
if: runner.os == 'Windows' && github.event_name == 'pull_request'
- name: Rename Avalonia (Unix)
run: mv ./publish_ava/Ryujinx.Ava ./publish_ava/Ryujinx
if: runner.os != 'Windows' && github.event_name == 'pull_request'
- name: Upload Ryujinx artifact - name: Upload Ryujinx artifact
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:

View File

@@ -46,11 +46,13 @@ jobs:
shell: bash shell: bash
- name: Create output dir - name: Create output dir
run: "mkdir release_output" run: "mkdir release_output"
- name: Publish Windows - name: Publish Windows
run: | run: |
dotnet publish -c Release -r win10-x64 -o ./publish_windows/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded Ryujinx --self-contained true dotnet publish -c Release -r win10-x64 -o ./publish_windows/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded Ryujinx --self-contained true
dotnet publish -c Release -r win10-x64 -o ./publish_windows_sdl2_headless/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded Ryujinx.Headless.SDL2 --self-contained true dotnet publish -c Release -r win10-x64 -o ./publish_windows_sdl2_headless/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded Ryujinx.Headless.SDL2 --self-contained true
dotnet publish -c Release -r win10-x64 -o ./publish_windows_ava/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded Ryujinx.Ava --self-contained true dotnet publish -c Release -r win10-x64 -o ./publish_windows_ava/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded Ryujinx.Ava --self-contained true
mv ./publish_windows_ava/publish/Ryujinx.Ava.exe ./publish_windows_ava/publish/Ryujinx.exe
- name: Packing Windows builds - name: Packing Windows builds
run: | run: |
pushd publish_windows pushd publish_windows
@@ -71,7 +73,7 @@ jobs:
dotnet publish -c Release -r linux-x64 -o ./publish_linux/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded Ryujinx --self-contained true dotnet publish -c Release -r linux-x64 -o ./publish_linux/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded Ryujinx --self-contained true
dotnet publish -c Release -r linux-x64 -o ./publish_linux_sdl2_headless/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded Ryujinx.Headless.SDL2 --self-contained true dotnet publish -c Release -r linux-x64 -o ./publish_linux_sdl2_headless/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded Ryujinx.Headless.SDL2 --self-contained true
dotnet publish -c Release -r linux-x64 -o ./publish_linux_ava/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded Ryujinx.Ava --self-contained true dotnet publish -c Release -r linux-x64 -o ./publish_linux_ava/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded Ryujinx.Ava --self-contained true
mv ./publish_linux_ava/publish/Ryujinx.Ava ./publish_linux_ava/publish/Ryujinx
- name: Packing Linux builds - name: Packing Linux builds
run: | run: |
pushd publish_linux pushd publish_linux

View File

@@ -1,4 +1,6 @@
using ARMeilleure.Translation; using ARMeilleure.Translation;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Threading; using Avalonia.Threading;
using LibHac.Tools.FsSystem; using LibHac.Tools.FsSystem;
@@ -13,6 +15,7 @@ using Ryujinx.Ava.Input;
using Ryujinx.Ava.UI.Controls; using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.Helpers; using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Models; using Ryujinx.Ava.UI.Models;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ava.UI.Windows; using Ryujinx.Ava.UI.Windows;
using Ryujinx.Common; using Ryujinx.Common;
using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration;
@@ -42,7 +45,7 @@ using System.Diagnostics;
using System.IO; using System.IO;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Image = SixLabors.ImageSharp.Image;
using InputManager = Ryujinx.Input.HLE.InputManager; using InputManager = Ryujinx.Input.HLE.InputManager;
using Key = Ryujinx.Input.Key; using Key = Ryujinx.Input.Key;
using MouseButton = Ryujinx.Input.MouseButton; using MouseButton = Ryujinx.Input.MouseButton;
@@ -67,7 +70,7 @@ namespace Ryujinx.Ava
private readonly AccountManager _accountManager; private readonly AccountManager _accountManager;
private readonly UserChannelPersistence _userChannelPersistence; private readonly UserChannelPersistence _userChannelPersistence;
private readonly InputManager _inputManager; private readonly InputManager _inputManager;
private readonly MainWindow _parent; private readonly MainWindowViewModel _viewModel;
private readonly IKeyboard _keyboardInterface; private readonly IKeyboard _keyboardInterface;
private readonly GraphicsDebugLevel _glLogLevel; private readonly GraphicsDebugLevel _glLogLevel;
@@ -110,6 +113,7 @@ namespace Ryujinx.Ava
public bool ScreenshotRequested { get; set; } public bool ScreenshotRequested { get; set; }
private object _lockObject = new(); private object _lockObject = new();
private TopLevel _topLevel;
public AppHost( public AppHost(
RendererHost renderer, RendererHost renderer,
@@ -119,9 +123,10 @@ namespace Ryujinx.Ava
ContentManager contentManager, ContentManager contentManager,
AccountManager accountManager, AccountManager accountManager,
UserChannelPersistence userChannelPersistence, UserChannelPersistence userChannelPersistence,
MainWindow parent) MainWindowViewModel viewmodel,
TopLevel topLevel)
{ {
_parent = parent; _viewModel = viewmodel;
_inputManager = inputManager; _inputManager = inputManager;
_accountManager = accountManager; _accountManager = accountManager;
_userChannelPersistence = userChannelPersistence; _userChannelPersistence = userChannelPersistence;
@@ -129,7 +134,8 @@ namespace Ryujinx.Ava
_hideCursorOnIdle = ConfigurationState.Instance.HideCursorOnIdle; _hideCursorOnIdle = ConfigurationState.Instance.HideCursorOnIdle;
_lastCursorMoveTime = Stopwatch.GetTimestamp(); _lastCursorMoveTime = Stopwatch.GetTimestamp();
_glLogLevel = ConfigurationState.Instance.Logger.GraphicsDebugLevel; _glLogLevel = ConfigurationState.Instance.Logger.GraphicsDebugLevel;
_inputManager.SetMouseDriver(new AvaloniaMouseDriver(_parent, renderer)); _topLevel = topLevel;
_inputManager.SetMouseDriver(new AvaloniaMouseDriver(_topLevel, renderer));
_keyboardInterface = (IKeyboard)_inputManager.KeyboardDriver.GetGamepad("0"); _keyboardInterface = (IKeyboard)_inputManager.KeyboardDriver.GetGamepad("0");
NpadManager = _inputManager.CreateNpadManager(); NpadManager = _inputManager.CreateNpadManager();
@@ -144,15 +150,15 @@ namespace Ryujinx.Ava
if (ApplicationPath.StartsWith("@SystemContent")) if (ApplicationPath.StartsWith("@SystemContent"))
{ {
ApplicationPath = _parent.VirtualFileSystem.SwitchPathToSystemPath(ApplicationPath); ApplicationPath = _viewModel.VirtualFileSystem.SwitchPathToSystemPath(ApplicationPath);
_isFirmwareTitle = true; _isFirmwareTitle = true;
} }
ConfigurationState.Instance.HideCursorOnIdle.Event += HideCursorState_Changed; ConfigurationState.Instance.HideCursorOnIdle.Event += HideCursorState_Changed;
_parent.PointerLeave += Parent_PointerLeft; _topLevel.PointerLeave += TopLevel_PointerLeave;
_parent.PointerMoved += Parent_PointerMoved; _topLevel.PointerMoved += TopLevel_PointerMoved;
ConfigurationState.Instance.System.IgnoreMissingServices.Event += UpdateIgnoreMissingServicesState; ConfigurationState.Instance.System.IgnoreMissingServices.Event += UpdateIgnoreMissingServicesState;
ConfigurationState.Instance.Graphics.AspectRatio.Event += UpdateAspectRatioState; ConfigurationState.Instance.Graphics.AspectRatio.Event += UpdateAspectRatioState;
@@ -162,25 +168,27 @@ namespace Ryujinx.Ava
_gpuCancellationTokenSource = new CancellationTokenSource(); _gpuCancellationTokenSource = new CancellationTokenSource();
} }
private void Parent_PointerMoved(object sender, PointerEventArgs e) private void TopLevel_PointerMoved(object sender, PointerEventArgs e)
{ {
_lastCursorMoveTime = Stopwatch.GetTimestamp(); if (sender is Control visual)
var p = e.GetCurrentPoint(_parent).Position; {
var r = _parent.InputHitTest(p); _lastCursorMoveTime = Stopwatch.GetTimestamp();
_isMouseInRenderer = r == Renderer; var point = e.GetCurrentPoint(visual).Position;
_isMouseInRenderer = Equals(visual.InputHitTest(point), Renderer);
}
} }
private void Parent_PointerLeft(object sender, PointerEventArgs e) private void TopLevel_PointerLeave(object sender, PointerEventArgs e)
{ {
_isMouseInRenderer = false; _isMouseInRenderer = false;
_parent.Cursor = Cursor.Default; _viewModel.Cursor = Cursor.Default;
} }
private void SetRendererWindowSize(Size size) private void SetRendererWindowSize(Size size)
{ {
if (_renderer != null) if (_renderer != null)
{ {
double scale = _parent.PlatformImpl.RenderScaling; double scale = _topLevel.PlatformImpl.RenderScaling;
_renderer.Window?.SetSize((int)(size.Width * scale), (int)(size.Height * scale)); _renderer.Window?.SetSize((int)(size.Width * scale), (int)(size.Height * scale));
} }
} }
@@ -256,7 +264,7 @@ namespace Ryujinx.Ava
NpadManager.Initialize(Device, ConfigurationState.Instance.Hid.InputConfig, ConfigurationState.Instance.Hid.EnableKeyboard, ConfigurationState.Instance.Hid.EnableMouse); NpadManager.Initialize(Device, ConfigurationState.Instance.Hid.InputConfig, ConfigurationState.Instance.Hid.EnableKeyboard, ConfigurationState.Instance.Hid.EnableMouse);
TouchScreenManager.Initialize(Device); TouchScreenManager.Initialize(Device);
_parent.ViewModel.IsGameRunning = true; _viewModel.IsGameRunning = true;
string titleNameSection = string.IsNullOrWhiteSpace(Device.Application.TitleName) string titleNameSection = string.IsNullOrWhiteSpace(Device.Application.TitleName)
? string.Empty ? string.Empty
@@ -276,10 +284,10 @@ namespace Ryujinx.Ava
Dispatcher.UIThread.InvokeAsync(() => Dispatcher.UIThread.InvokeAsync(() =>
{ {
_parent.Title = $"Ryujinx {Program.Version}{titleNameSection}{titleVersionSection}{titleIdSection}{titleArchSection}"; _viewModel.Title = $"Ryujinx {Program.Version}{titleNameSection}{titleVersionSection}{titleIdSection}{titleArchSection}";
}); });
_parent.ViewModel.SetUiProgressHandlers(Device); _viewModel.SetUIProgressHandlers(Device);
Renderer.SizeChanged += Window_SizeChanged; Renderer.SizeChanged += Window_SizeChanged;
@@ -287,7 +295,7 @@ namespace Ryujinx.Ava
_renderingThread.Start(); _renderingThread.Start();
_parent.ViewModel.Volume = ConfigurationState.Instance.System.AudioVolume.Value; _viewModel.Volume = ConfigurationState.Instance.System.AudioVolume.Value;
MainLoop(); MainLoop();
@@ -321,7 +329,7 @@ namespace Ryujinx.Ava
Dispatcher.UIThread.Post(() => Dispatcher.UIThread.Post(() =>
{ {
var value = e.NewValue; var value = e.NewValue;
_parent.ViewModel.Volume = e.NewValue; _viewModel.Volume = e.NewValue;
}); });
} }
@@ -369,7 +377,7 @@ namespace Ryujinx.Ava
{ {
if (Device.Application != null) if (Device.Application != null)
{ {
_parent.UpdateGameMetadata(Device.Application.TitleIdText); _viewModel.UpdateGameMetadata(Device.Application.TitleIdText);
} }
ConfigurationState.Instance.System.IgnoreMissingServices.Event -= UpdateIgnoreMissingServicesState; ConfigurationState.Instance.System.IgnoreMissingServices.Event -= UpdateIgnoreMissingServicesState;
@@ -377,6 +385,9 @@ namespace Ryujinx.Ava
ConfigurationState.Instance.System.EnableDockedMode.Event -= UpdateDockedModeState; ConfigurationState.Instance.System.EnableDockedMode.Event -= UpdateDockedModeState;
ConfigurationState.Instance.System.AudioVolume.Event -= UpdateAudioVolumeState; ConfigurationState.Instance.System.AudioVolume.Event -= UpdateAudioVolumeState;
_topLevel.PointerLeave -= TopLevel_PointerLeave;
_topLevel.PointerMoved -= TopLevel_PointerMoved;
_gpuCancellationTokenSource.Cancel(); _gpuCancellationTokenSource.Cancel();
_gpuCancellationTokenSource.Dispose(); _gpuCancellationTokenSource.Dispose();
@@ -410,7 +421,7 @@ namespace Ryujinx.Ava
} }
else else
{ {
_parent.Cursor = Cursor.Default; _viewModel.Cursor = Cursor.Default;
} }
}); });
} }
@@ -422,57 +433,65 @@ namespace Ryujinx.Ava
SystemVersion firmwareVersion = ContentManager.GetCurrentFirmwareVersion(); SystemVersion firmwareVersion = ContentManager.GetCurrentFirmwareVersion();
if (!SetupValidator.CanStartApplication(ContentManager, ApplicationPath, out UserError userError)) if (Avalonia.Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{ {
if (SetupValidator.CanFixStartApplication(ContentManager, ApplicationPath, userError, out firmwareVersion)) if (!SetupValidator.CanStartApplication(ContentManager, ApplicationPath, out UserError userError))
{ {
if (userError == UserError.NoFirmware)
{ {
UserResult result = await ContentDialogHelper.CreateConfirmationDialog( if (SetupValidator.CanFixStartApplication(ContentManager, ApplicationPath, userError, out firmwareVersion))
LocaleManager.Instance[LocaleKeys.DialogFirmwareNoFirmwareInstalledMessage],
string.Format(LocaleManager.Instance[LocaleKeys.DialogFirmwareInstallEmbeddedMessage], firmwareVersion.VersionString),
LocaleManager.Instance[LocaleKeys.InputDialogYes],
LocaleManager.Instance[LocaleKeys.InputDialogNo],
"");
if (result != UserResult.Yes)
{ {
await UserErrorDialog.ShowUserErrorDialog(userError, _parent); if (userError == UserError.NoFirmware)
{
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(
LocaleManager.Instance[LocaleKeys.DialogFirmwareNoFirmwareInstalledMessage],
string.Format(LocaleManager.Instance[LocaleKeys.DialogFirmwareInstallEmbeddedMessage],
firmwareVersion.VersionString),
LocaleManager.Instance[LocaleKeys.InputDialogYes],
LocaleManager.Instance[LocaleKeys.InputDialogNo],
"");
if (result != UserResult.Yes)
{
await UserErrorDialog.ShowUserErrorDialog(userError, (desktop.MainWindow as MainWindow));
Device.Dispose();
return false;
}
}
if (!SetupValidator.TryFixStartApplication(ContentManager, ApplicationPath, userError, out _))
{
await UserErrorDialog.ShowUserErrorDialog(userError, (desktop.MainWindow as MainWindow));
Device.Dispose();
return false;
}
// Tell the user that we installed a firmware for them.
if (userError == UserError.NoFirmware)
{
firmwareVersion = ContentManager.GetCurrentFirmwareVersion();
_viewModel.RefreshFirmwareStatus();
await ContentDialogHelper.CreateInfoDialog(
string.Format(LocaleManager.Instance[LocaleKeys.DialogFirmwareInstalledMessage],
firmwareVersion.VersionString),
string.Format(LocaleManager.Instance[LocaleKeys.DialogFirmwareInstallEmbeddedSuccessMessage],
firmwareVersion.VersionString),
LocaleManager.Instance[LocaleKeys.InputDialogOk],
"",
LocaleManager.Instance[LocaleKeys.RyujinxInfo]);
}
}
else
{
await UserErrorDialog.ShowUserErrorDialog(userError, (desktop.MainWindow as MainWindow));
Device.Dispose(); Device.Dispose();
return false; return false;
} }
} }
if (!SetupValidator.TryFixStartApplication(ContentManager, ApplicationPath, userError, out _))
{
await UserErrorDialog.ShowUserErrorDialog(userError, _parent);
Device.Dispose();
return false;
}
// Tell the user that we installed a firmware for them.
if (userError == UserError.NoFirmware)
{
firmwareVersion = ContentManager.GetCurrentFirmwareVersion();
_parent.RefreshFirmwareStatus();
await ContentDialogHelper.CreateInfoDialog(
string.Format(LocaleManager.Instance[LocaleKeys.DialogFirmwareInstalledMessage], firmwareVersion.VersionString),
string.Format(LocaleManager.Instance[LocaleKeys.DialogFirmwareInstallEmbeddedSuccessMessage], firmwareVersion.VersionString),
LocaleManager.Instance[LocaleKeys.InputDialogOk],
"",
LocaleManager.Instance[LocaleKeys.RyujinxInfo]);
}
}
else
{
await UserErrorDialog.ShowUserErrorDialog(userError, _parent);
Device.Dispose();
return false;
} }
} }
@@ -567,7 +586,7 @@ namespace Ryujinx.Ava
DiscordIntegrationModule.SwitchToPlayingState(Device.Application.TitleIdText, Device.Application.TitleName); DiscordIntegrationModule.SwitchToPlayingState(Device.Application.TitleIdText, Device.Application.TitleName);
_parent.ApplicationLibrary.LoadAndSaveMetaData(Device.Application.TitleIdText, appMetadata => _viewModel.ApplicationLibrary.LoadAndSaveMetaData(Device.Application.TitleIdText, appMetadata =>
{ {
appMetadata.LastPlayed = DateTime.UtcNow.ToString(); appMetadata.LastPlayed = DateTime.UtcNow.ToString();
}); });
@@ -578,13 +597,13 @@ namespace Ryujinx.Ava
internal void Resume() internal void Resume()
{ {
Device?.System.TogglePauseEmulation(false); Device?.System.TogglePauseEmulation(false);
_parent.ViewModel.IsPaused = false; _viewModel.IsPaused = false;
} }
internal void Pause() internal void Pause()
{ {
Device?.System.TogglePauseEmulation(true); Device?.System.TogglePauseEmulation(true);
_parent.ViewModel.IsPaused = true; _viewModel.IsPaused = true;
} }
private void InitializeSwitchInstance() private void InitializeSwitchInstance()
@@ -632,7 +651,7 @@ namespace Ryujinx.Ava
Logger.Warning?.Print(LogClass.Audio, "Found OpenAL, changing configuration."); Logger.Warning?.Print(LogClass.Audio, "Found OpenAL, changing configuration.");
ConfigurationState.Instance.System.AudioBackend.Value = AudioBackend.OpenAl; ConfigurationState.Instance.System.AudioBackend.Value = AudioBackend.OpenAl;
MainWindow.SaveConfig(); MainWindowViewModel.SaveConfig();
deviceDriver = new OpenALHardwareDeviceDriver(); deviceDriver = new OpenALHardwareDeviceDriver();
} }
@@ -645,7 +664,7 @@ namespace Ryujinx.Ava
Logger.Warning?.Print(LogClass.Audio, "Found SoundIO, changing configuration."); Logger.Warning?.Print(LogClass.Audio, "Found SoundIO, changing configuration.");
ConfigurationState.Instance.System.AudioBackend.Value = AudioBackend.SoundIo; ConfigurationState.Instance.System.AudioBackend.Value = AudioBackend.SoundIo;
MainWindow.SaveConfig(); MainWindowViewModel.SaveConfig();
deviceDriver = new SoundIoHardwareDeviceDriver(); deviceDriver = new SoundIoHardwareDeviceDriver();
} }
@@ -671,7 +690,7 @@ namespace Ryujinx.Ava
Logger.Warning?.Print(LogClass.Audio, "Found SDL2, changing configuration."); Logger.Warning?.Print(LogClass.Audio, "Found SDL2, changing configuration.");
ConfigurationState.Instance.System.AudioBackend.Value = AudioBackend.SDL2; ConfigurationState.Instance.System.AudioBackend.Value = AudioBackend.SDL2;
MainWindow.SaveConfig(); MainWindowViewModel.SaveConfig();
deviceDriver = new SDL2HardwareDeviceDriver(); deviceDriver = new SDL2HardwareDeviceDriver();
} }
@@ -684,7 +703,7 @@ namespace Ryujinx.Ava
Logger.Warning?.Print(LogClass.Audio, "Found OpenAL, changing configuration."); Logger.Warning?.Print(LogClass.Audio, "Found OpenAL, changing configuration.");
ConfigurationState.Instance.System.AudioBackend.Value = AudioBackend.OpenAl; ConfigurationState.Instance.System.AudioBackend.Value = AudioBackend.OpenAl;
MainWindow.SaveConfig(); MainWindowViewModel.SaveConfig();
deviceDriver = new OpenALHardwareDeviceDriver(); deviceDriver = new OpenALHardwareDeviceDriver();
} }
@@ -710,7 +729,7 @@ namespace Ryujinx.Ava
Logger.Warning?.Print(LogClass.Audio, "Found SDL2, changing configuration."); Logger.Warning?.Print(LogClass.Audio, "Found SDL2, changing configuration.");
ConfigurationState.Instance.System.AudioBackend.Value = AudioBackend.SDL2; ConfigurationState.Instance.System.AudioBackend.Value = AudioBackend.SDL2;
MainWindow.SaveConfig(); MainWindowViewModel.SaveConfig();
deviceDriver = new SDL2HardwareDeviceDriver(); deviceDriver = new SDL2HardwareDeviceDriver();
} }
@@ -723,7 +742,7 @@ namespace Ryujinx.Ava
Logger.Warning?.Print(LogClass.Audio, "Found SoundIO, changing configuration."); Logger.Warning?.Print(LogClass.Audio, "Found SoundIO, changing configuration.");
ConfigurationState.Instance.System.AudioBackend.Value = AudioBackend.SoundIo; ConfigurationState.Instance.System.AudioBackend.Value = AudioBackend.SoundIo;
MainWindow.SaveConfig(); MainWindowViewModel.SaveConfig();
deviceDriver = new SoundIoHardwareDeviceDriver(); deviceDriver = new SoundIoHardwareDeviceDriver();
} }
@@ -740,14 +759,14 @@ namespace Ryujinx.Ava
IntegrityCheckLevel fsIntegrityCheckLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks ? IntegrityCheckLevel.ErrorOnInvalid : IntegrityCheckLevel.None; IntegrityCheckLevel fsIntegrityCheckLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks ? IntegrityCheckLevel.ErrorOnInvalid : IntegrityCheckLevel.None;
HLE.HLEConfiguration configuration = new HLE.HLEConfiguration(VirtualFileSystem, HLE.HLEConfiguration configuration = new HLE.HLEConfiguration(VirtualFileSystem,
_parent.LibHacHorizonManager, _viewModel.LibHacHorizonManager,
ContentManager, ContentManager,
_accountManager, _accountManager,
_userChannelPersistence, _userChannelPersistence,
renderer, renderer,
deviceDriver, deviceDriver,
memoryConfiguration, memoryConfiguration,
_parent.UiHandler, _viewModel.UiHandler,
(SystemLanguage)ConfigurationState.Instance.System.Language.Value, (SystemLanguage)ConfigurationState.Instance.System.Language.Value,
(RegionCode)ConfigurationState.Instance.System.Region.Value, (RegionCode)ConfigurationState.Instance.System.Region.Value,
ConfigurationState.Instance.Graphics.EnableVsync, ConfigurationState.Instance.Graphics.EnableVsync,
@@ -788,14 +807,14 @@ namespace Ryujinx.Ava
{ {
Dispatcher.UIThread.InvokeAsync(() => Dispatcher.UIThread.InvokeAsync(() =>
{ {
if (_parent.ViewModel.StartGamesInFullscreen) if (_viewModel.StartGamesInFullscreen)
{ {
_parent.WindowState = WindowState.FullScreen; _viewModel.WindowState = WindowState.FullScreen;
} }
if (_parent.WindowState == WindowState.FullScreen) if (_viewModel.WindowState == WindowState.FullScreen)
{ {
_parent.ViewModel.ShowMenuAndStatusBar = false; _viewModel.ShowMenuAndStatusBar = false;
} }
}); });
@@ -819,7 +838,7 @@ namespace Ryujinx.Ava
Width = (int)Renderer.Bounds.Width; Width = (int)Renderer.Bounds.Width;
Height = (int)Renderer.Bounds.Height; Height = (int)Renderer.Bounds.Height;
_renderer.Window.SetSize((int)(Width * _parent.PlatformImpl.RenderScaling), (int)(Height * _parent.PlatformImpl.RenderScaling)); _renderer.Window.SetSize((int)(Width * _topLevel.PlatformImpl.RenderScaling), (int)(Height * _topLevel.PlatformImpl.RenderScaling));
_chrono.Start(); _chrono.Start();
@@ -847,7 +866,7 @@ namespace Ryujinx.Ava
if (!_renderingStarted) if (!_renderingStarted)
{ {
_renderingStarted = true; _renderingStarted = true;
_parent.SwitchToGameControl(); _viewModel.SwitchToRenderer(false);
} }
Device.PresentFrame(() => Renderer?.SwapBuffers()); Device.PresentFrame(() => Renderer?.SwapBuffers());
@@ -914,7 +933,7 @@ namespace Ryujinx.Ava
{ {
Dispatcher.UIThread.Post(() => Dispatcher.UIThread.Post(() =>
{ {
_parent.Cursor = _isMouseInRenderer ? InvisibleCursor : Cursor.Default; _viewModel.Cursor = _isMouseInRenderer ? InvisibleCursor : Cursor.Default;
}); });
} }
else else
@@ -925,7 +944,7 @@ namespace Ryujinx.Ava
Dispatcher.UIThread.Post(() => Dispatcher.UIThread.Post(() =>
{ {
_parent.Cursor = cursorMoveDelta >= CursorHideIdleTime * Stopwatch.Frequency ? InvisibleCursor : Cursor.Default; _viewModel.Cursor = cursorMoveDelta >= CursorHideIdleTime * Stopwatch.Frequency ? InvisibleCursor : Cursor.Default;
}); });
} }
} }
@@ -938,13 +957,13 @@ namespace Ryujinx.Ava
return false; return false;
} }
if (_parent.IsActive) if (_viewModel.IsActive)
{ {
Dispatcher.UIThread.Post(() => Dispatcher.UIThread.Post(() =>
{ {
HandleScreenState(); HandleScreenState();
if (_keyboardInterface.GetKeyboardStateSnapshot().IsPressed(Key.Delete) && _parent.WindowState != WindowState.FullScreen) if (_keyboardInterface.GetKeyboardStateSnapshot().IsPressed(Key.Delete) && _viewModel.WindowState != WindowState.FullScreen)
{ {
Device.Application.DiskCacheLoadState?.Cancel(); Device.Application.DiskCacheLoadState?.Cancel();
} }
@@ -953,7 +972,7 @@ namespace Ryujinx.Ava
NpadManager.Update(ConfigurationState.Instance.Graphics.AspectRatio.Value.ToFloat()); NpadManager.Update(ConfigurationState.Instance.Graphics.AspectRatio.Value.ToFloat());
if (_parent.IsActive) if (_viewModel.IsActive)
{ {
KeyboardHotkeyState currentHotkeyState = GetHotkeyState(); KeyboardHotkeyState currentHotkeyState = GetHotkeyState();
@@ -969,10 +988,10 @@ namespace Ryujinx.Ava
ScreenshotRequested = true; ScreenshotRequested = true;
break; break;
case KeyboardHotkeyState.ShowUi: case KeyboardHotkeyState.ShowUi:
_parent.ViewModel.ShowMenuAndStatusBar = true; _viewModel.ShowMenuAndStatusBar = true;
break; break;
case KeyboardHotkeyState.Pause: case KeyboardHotkeyState.Pause:
if (_parent.ViewModel.IsPaused) if (_viewModel.IsPaused)
{ {
Resume(); Resume();
} }
@@ -991,7 +1010,7 @@ namespace Ryujinx.Ava
Device.SetVolume(0); Device.SetVolume(0);
} }
_parent.ViewModel.Volume = Device.GetVolume(); _viewModel.Volume = Device.GetVolume();
break; break;
case KeyboardHotkeyState.ResScaleUp: case KeyboardHotkeyState.ResScaleUp:
GraphicsConfig.ResScale = GraphicsConfig.ResScale % MaxResolutionScale + 1; GraphicsConfig.ResScale = GraphicsConfig.ResScale % MaxResolutionScale + 1;
@@ -1004,13 +1023,13 @@ namespace Ryujinx.Ava
_newVolume = MathF.Round((Device.GetVolume() + VolumeDelta), 2); _newVolume = MathF.Round((Device.GetVolume() + VolumeDelta), 2);
Device.SetVolume(_newVolume); Device.SetVolume(_newVolume);
_parent.ViewModel.Volume = Device.GetVolume(); _viewModel.Volume = Device.GetVolume();
break; break;
case KeyboardHotkeyState.VolumeDown: case KeyboardHotkeyState.VolumeDown:
_newVolume = MathF.Round((Device.GetVolume() - VolumeDelta), 2); _newVolume = MathF.Round((Device.GetVolume() - VolumeDelta), 2);
Device.SetVolume(_newVolume); Device.SetVolume(_newVolume);
_parent.ViewModel.Volume = Device.GetVolume(); _viewModel.Volume = Device.GetVolume();
break; break;
case KeyboardHotkeyState.None: case KeyboardHotkeyState.None:
(_keyboardInterface as AvaloniaKeyboard).Clear(); (_keyboardInterface as AvaloniaKeyboard).Clear();
@@ -1030,7 +1049,7 @@ namespace Ryujinx.Ava
// Touchscreen // Touchscreen
bool hasTouch = false; bool hasTouch = false;
if (_parent.IsActive && !ConfigurationState.Instance.Hid.EnableMouse) if (_viewModel.IsActive && !ConfigurationState.Instance.Hid.EnableMouse)
{ {
hasTouch = TouchScreenManager.Update(true, (_inputManager.MouseDriver as AvaloniaMouseDriver).IsButtonPressed(MouseButton.Button1), ConfigurationState.Instance.Graphics.AspectRatio.Value.ToFloat()); hasTouch = TouchScreenManager.Update(true, (_inputManager.MouseDriver as AvaloniaMouseDriver).IsButtonPressed(MouseButton.Button1), ConfigurationState.Instance.Graphics.AspectRatio.Value.ToFloat());
} }

View File

@@ -269,6 +269,8 @@
<Color x:Key="DataGridSelectionColor">#FF00FABB</Color> <Color x:Key="DataGridSelectionColor">#FF00FABB</Color>
<Color x:Key="ThemeContentBackgroundColor">#FF2D2D2D</Color> <Color x:Key="ThemeContentBackgroundColor">#FF2D2D2D</Color>
<Color x:Key="ThemeControlBorderColor">#FF505050</Color> <Color x:Key="ThemeControlBorderColor">#FF505050</Color>
<Color x:Key="VsyncEnabled">#FF2EEAC9</Color>
<Color x:Key="VsyncDisabled">#FFFF4554</Color>
<x:Double x:Key="ScrollBarThickness">15</x:Double> <x:Double x:Key="ScrollBarThickness">15</x:Double>
<x:Double x:Key="FontSizeSmall">8</x:Double> <x:Double x:Key="FontSizeSmall">8</x:Double>
<x:Double x:Key="FontSizeNormal">10</x:Double> <x:Double x:Key="FontSizeNormal">10</x:Double>

View File

@@ -0,0 +1,111 @@
using Avalonia.Utilities;
using System;
using System.Text;
namespace Ryujinx.Ava.UI.Helper
{
using AvaLogger = Avalonia.Logging.Logger;
using AvaLogLevel = Avalonia.Logging.LogEventLevel;
using RyuLogger = Ryujinx.Common.Logging.Logger;
using RyuLogClass = Ryujinx.Common.Logging.LogClass;
internal class LoggerAdapter : Avalonia.Logging.ILogSink
{
public static void Register()
{
AvaLogger.Sink = new LoggerAdapter();
}
private static RyuLogger.Log? GetLog(AvaLogLevel level)
{
return level switch
{
AvaLogLevel.Verbose => RyuLogger.Trace,
AvaLogLevel.Debug => RyuLogger.Debug,
AvaLogLevel.Information => RyuLogger.Info,
AvaLogLevel.Warning => RyuLogger.Warning,
AvaLogLevel.Error => RyuLogger.Error,
AvaLogLevel.Fatal => RyuLogger.Notice,
_ => throw new ArgumentOutOfRangeException(nameof(level), level, null)
};
}
public bool IsEnabled(AvaLogLevel level, string area)
{
return GetLog(level) != null;
}
public void Log(AvaLogLevel level, string area, object source, string messageTemplate)
{
GetLog(level)?.PrintMsg(RyuLogClass.Ui, Format(area, messageTemplate, source, null));
}
public void Log<T0>(AvaLogLevel level, string area, object source, string messageTemplate, T0 propertyValue0)
{
GetLog(level)?.PrintMsg(RyuLogClass.Ui, Format(area, messageTemplate, source, new object[] { propertyValue0 }));
}
public void Log<T0, T1>(AvaLogLevel level, string area, object source, string messageTemplate, T0 propertyValue0, T1 propertyValue1)
{
GetLog(level)?.PrintMsg(RyuLogClass.Ui, Format(area, messageTemplate, source, new object[] { propertyValue0, propertyValue1 }));
}
public void Log<T0, T1, T2>(AvaLogLevel level, string area, object source, string messageTemplate, T0 propertyValue0, T1 propertyValue1, T2 propertyValue2)
{
GetLog(level)?.PrintMsg(RyuLogClass.Ui, Format(area, messageTemplate, source, new object[] { propertyValue0, propertyValue1, propertyValue2 }));
}
public void Log(AvaLogLevel level, string area, object source, string messageTemplate, params object[] propertyValues)
{
GetLog(level)?.PrintMsg(RyuLogClass.Ui, Format(area, messageTemplate, source, propertyValues));
}
private static string Format(string area, string template, object source, object[] v)
{
var result = new StringBuilder();
var r = new CharacterReader(template.AsSpan());
var i = 0;
result.Append('[');
result.Append(area);
result.Append("] ");
while (!r.End)
{
var c = r.Take();
if (c != '{')
{
result.Append(c);
}
else
{
if (r.Peek != '{')
{
result.Append('\'');
result.Append(i < v.Length ? v[i++] : null);
result.Append('\'');
r.TakeUntil('}');
r.Take();
}
else
{
result.Append('{');
r.Take();
}
}
}
if (source != null)
{
result.Append(" (");
result.Append(source.GetType().Name);
result.Append(" #");
result.Append(source.GetHashCode());
result.Append(')');
}
return result.ToString();
}
}
}

View File

@@ -11,10 +11,10 @@ namespace Ryujinx.Ava.Input
{ {
internal class AvaloniaMouseDriver : IGamepadDriver internal class AvaloniaMouseDriver : IGamepadDriver
{ {
private Control _widget; private Control _widget;
private bool _isDisposed; private bool _isDisposed;
private Size _size; private Size _size;
private readonly Window _window; private readonly TopLevel _window;
public bool[] PressedButtons { get; } public bool[] PressedButtons { get; }
public Vector2 CurrentPosition { get; private set; } public Vector2 CurrentPosition { get; private set; }
@@ -23,7 +23,7 @@ namespace Ryujinx.Ava.Input
public string DriverName => "AvaloniaMouseDriver"; public string DriverName => "AvaloniaMouseDriver";
public ReadOnlySpan<string> GamepadsIds => new[] { "0" }; public ReadOnlySpan<string> GamepadsIds => new[] { "0" };
public AvaloniaMouseDriver(Window window, Control parent) public AvaloniaMouseDriver(TopLevel window, Control parent)
{ {
_widget = parent; _widget = parent;
_window = window; _window = window;

View File

@@ -56,7 +56,7 @@ namespace Ryujinx.Modules
} }
Running = true; Running = true;
mainWindow.CanUpdate = false; mainWindow.ViewModel.CanUpdate = false;
// Detect current platform // Detect current platform
if (OperatingSystem.IsMacOS()) if (OperatingSystem.IsMacOS())
@@ -182,7 +182,7 @@ namespace Ryujinx.Modules
} }
Running = false; Running = false;
mainWindow.CanUpdate = true; mainWindow.ViewModel.CanUpdate = true;
return; return;
} }

View File

@@ -1,5 +1,6 @@
using Avalonia; using Avalonia;
using Avalonia.Threading; using Avalonia.Threading;
using Ryujinx.Ava.UI.Helper;
using Ryujinx.Ava.UI.Windows; using Ryujinx.Ava.UI.Windows;
using Ryujinx.Common; using Ryujinx.Common;
using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration;
@@ -89,6 +90,8 @@ namespace Ryujinx.Ava
Initialize(args); Initialize(args);
LoggerAdapter.Register();
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args); BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
} }
@@ -110,8 +113,7 @@ namespace Ryujinx.Ava
AllowEglInitialization = false, AllowEglInitialization = false,
CompositionBackdropCornerRadius = 8.0f, CompositionBackdropCornerRadius = 8.0f,
}) })
.UseSkia() .UseSkia();
.LogToTrace();
} }
private static void Initialize(string[] args) private static void Initialize(string[] args)

View File

@@ -4,7 +4,6 @@
<RuntimeIdentifiers>win10-x64;osx-x64;linux-x64</RuntimeIdentifiers> <RuntimeIdentifiers>win10-x64;osx-x64;linux-x64</RuntimeIdentifiers>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<AssemblyName>Ryujinx</AssemblyName>
<Version>1.0.0-dirty</Version> <Version>1.0.0-dirty</Version>
<DefineConstants Condition=" '$(ExtraDefineConstants)' != '' ">$(DefineConstants);$(ExtraDefineConstants)</DefineConstants> <DefineConstants Condition=" '$(ExtraDefineConstants)' != '' ">$(DefineConstants);$(ExtraDefineConstants)</DefineConstants>
<RootNamespace>Ryujinx.Ava</RootNamespace> <RootNamespace>Ryujinx.Ava</RootNamespace>

View File

@@ -144,9 +144,9 @@ namespace Ryujinx.Ava.UI.Applet
public void ExecuteProgram(Switch device, ProgramSpecifyKind kind, ulong value) public void ExecuteProgram(Switch device, ProgramSpecifyKind kind, ulong value)
{ {
device.Configuration.UserChannelPersistence.ExecuteProgram(kind, value); device.Configuration.UserChannelPersistence.ExecuteProgram(kind, value);
if (_parent.AppHost != null) if (_parent.ViewModel.AppHost != null)
{ {
_parent.AppHost.Stop(); _parent.ViewModel.AppHost.Stop();
} }
} }

View File

@@ -136,7 +136,7 @@ namespace Ryujinx.Ava.UI.Applet
Dispatcher.UIThread.Post(() => Dispatcher.UIThread.Post(() =>
{ {
_hiddenTextBox.Clear(); _hiddenTextBox.Clear();
_parent.RendererControl.Focus(); _parent.ViewModel.RendererControl.Focus();
_parent = null; _parent = null;
}); });

View File

@@ -13,8 +13,8 @@ namespace Ryujinx.Ava.UI.Applet
DefaultBackgroundColor = BrushToThemeColor(parent.Background); DefaultBackgroundColor = BrushToThemeColor(parent.Background);
DefaultForegroundColor = BrushToThemeColor(parent.Foreground); DefaultForegroundColor = BrushToThemeColor(parent.Foreground);
DefaultBorderColor = BrushToThemeColor(parent.BorderBrush); DefaultBorderColor = BrushToThemeColor(parent.BorderBrush);
SelectionBackgroundColor = BrushToThemeColor(parent.SearchBox.SelectionBrush); SelectionBackgroundColor = BrushToThemeColor(parent.ViewControls.SearchBox.SelectionBrush);
SelectionForegroundColor = BrushToThemeColor(parent.SearchBox.SelectionForegroundBrush); SelectionForegroundColor = BrushToThemeColor(parent.ViewControls.SearchBox.SelectionForegroundBrush);
} }
public string FontFamily { get; } public string FontFamily { get; }

View File

@@ -139,6 +139,9 @@
<Style Selector="ListBoxItem:pointerover /template/ ContentPresenter"> <Style Selector="ListBoxItem:pointerover /template/ ContentPresenter">
<Setter Property="Background" Value="{DynamicResource AppListHoverBackgroundColor}" /> <Setter Property="Background" Value="{DynamicResource AppListHoverBackgroundColor}" />
</Style> </Style>
<Style Selector="ListBoxItem:selected /template/ Border#SelectionIndicator">
<Setter Property="MinHeight" Value="{Binding $parent[UserControl].DataContext.GridItemSelectorSize}" />
</Style>
</ListBox.Styles> </ListBox.Styles>
<ListBox.ItemTemplate> <ListBox.ItemTemplate>
<DataTemplate> <DataTemplate>

View File

@@ -137,12 +137,12 @@
<Style Selector="ListBoxItem:selected /template/ ContentPresenter"> <Style Selector="ListBoxItem:selected /template/ ContentPresenter">
<Setter Property="Background" Value="{DynamicResource AppListBackgroundColor}" /> <Setter Property="Background" Value="{DynamicResource AppListBackgroundColor}" />
</Style> </Style>
<Style Selector="ListBoxItem:selected /template/ Border#SelectionIndicator">
<Setter Property="MinHeight" Value="100" />
</Style>
<Style Selector="ListBoxItem:pointerover /template/ ContentPresenter"> <Style Selector="ListBoxItem:pointerover /template/ ContentPresenter">
<Setter Property="Background" Value="{DynamicResource AppListHoverBackgroundColor}" /> <Setter Property="Background" Value="{DynamicResource AppListHoverBackgroundColor}" />
</Style> </Style>
<Style Selector="ListBoxItem:selected /template/ Border#SelectionIndicator">
<Setter Property="MinHeight" Value="{Binding $parent[UserControl].DataContext.ListItemSelectorSize}" />
</Style>
</ListBox.Styles> </ListBox.Styles>
<ListBox.ItemTemplate> <ListBox.ItemTemplate>
<DataTemplate> <DataTemplate>

View File

@@ -244,9 +244,9 @@ namespace Ryujinx.Ava.UI.ViewModels
_mainWindow.InputManager.GamepadDriver.OnGamepadConnected += HandleOnGamepadConnected; _mainWindow.InputManager.GamepadDriver.OnGamepadConnected += HandleOnGamepadConnected;
_mainWindow.InputManager.GamepadDriver.OnGamepadDisconnected += HandleOnGamepadDisconnected; _mainWindow.InputManager.GamepadDriver.OnGamepadDisconnected += HandleOnGamepadDisconnected;
if (_mainWindow.AppHost != null) if (_mainWindow.ViewModel.AppHost != null)
{ {
_mainWindow.AppHost.NpadManager.BlockInputUpdates(); _mainWindow.ViewModel.AppHost.NpadManager.BlockInputUpdates();
} }
_isLoaded = false; _isLoaded = false;
@@ -862,7 +862,7 @@ namespace Ryujinx.Ava.UI.ViewModels
} }
} }
_mainWindow.AppHost?.NpadManager.ReloadConfiguration(newConfig, ConfigurationState.Instance.Hid.EnableKeyboard, ConfigurationState.Instance.Hid.EnableMouse); _mainWindow.ViewModel.AppHost?.NpadManager.ReloadConfiguration(newConfig, ConfigurationState.Instance.Hid.EnableKeyboard, ConfigurationState.Instance.Hid.EnableMouse);
// Atomically replace and signal input change. // Atomically replace and signal input change.
// NOTE: Do not modify InputConfig.Value directly as other code depends on the on-change event. // NOTE: Do not modify InputConfig.Value directly as other code depends on the on-change event.
@@ -891,7 +891,7 @@ namespace Ryujinx.Ava.UI.ViewModels
_mainWindow.InputManager.GamepadDriver.OnGamepadConnected -= HandleOnGamepadConnected; _mainWindow.InputManager.GamepadDriver.OnGamepadConnected -= HandleOnGamepadConnected;
_mainWindow.InputManager.GamepadDriver.OnGamepadDisconnected -= HandleOnGamepadDisconnected; _mainWindow.InputManager.GamepadDriver.OnGamepadDisconnected -= HandleOnGamepadDisconnected;
_mainWindow.AppHost?.NpadManager.UnblockInputUpdates(); _mainWindow.ViewModel.AppHost?.NpadManager.UnblockInputUpdates();
SelectedGamepad?.Dispose(); SelectedGamepad?.Dispose();

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,216 @@
<UserControl
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"
mc:Ignorable="d"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
x:DataType="viewModels:MainWindowViewModel"
x:Class="Ryujinx.Ava.UI.Views.Main.MainMenuBarView"
x:CompileBindings="True">
<Design.DataContext>
<viewModels:MainWindowViewModel />
</Design.DataContext>
<DockPanel HorizontalAlignment="Stretch">
<Menu
Name="Menu"
Height="35"
Margin="0"
HorizontalAlignment="Left">
<Menu.ItemsPanel>
<ItemsPanelTemplate>
<DockPanel Margin="0" HorizontalAlignment="Stretch" />
</ItemsPanelTemplate>
</Menu.ItemsPanel>
<MenuItem VerticalAlignment="Center" Header="{locale:Locale MenuBarFile}">
<MenuItem
Command="{ReflectionBinding OpenFile}"
Header="{locale:Locale MenuBarFileOpenFromFile}"
IsEnabled="{Binding EnableNonGameRunningControls}"
ToolTip.Tip="{locale:Locale LoadApplicationFileTooltip}" />
<MenuItem
Command="{ReflectionBinding OpenFolder}"
Header="{locale:Locale MenuBarFileOpenUnpacked}"
IsEnabled="{Binding EnableNonGameRunningControls}"
ToolTip.Tip="{locale:Locale LoadApplicationFolderTooltip}" />
<MenuItem Header="{locale:Locale MenuBarFileOpenApplet}" IsEnabled="{Binding IsAppletMenuActive}">
<MenuItem
Click="OpenMiiApplet"
Header="Mii Edit Applet"
ToolTip.Tip="{locale:Locale MenuBarFileOpenAppletOpenMiiAppletToolTip}" />
</MenuItem>
<Separator />
<MenuItem
Command="{ReflectionBinding OpenRyujinxFolder}"
Header="{locale:Locale MenuBarFileOpenEmuFolder}"
ToolTip.Tip="{locale:Locale OpenRyujinxFolderTooltip}" />
<MenuItem
Command="{ReflectionBinding OpenLogsFolder}"
Header="{locale:Locale MenuBarFileOpenLogsFolder}"
ToolTip.Tip="{locale:Locale OpenRyujinxLogsTooltip}" />
<Separator />
<MenuItem
Click="CloseWindow"
Header="{locale:Locale MenuBarFileExit}"
ToolTip.Tip="{locale:Locale ExitTooltip}" />
</MenuItem>
<MenuItem VerticalAlignment="Center" Header="{locale:Locale MenuBarOptions}">
<MenuItem
Command="{ReflectionBinding ToggleFullscreen}"
Header="{locale:Locale MenuBarOptionsToggleFullscreen}"
InputGesture="F11" />
<MenuItem>
<MenuItem.Icon>
<CheckBox IsChecked="{Binding StartGamesInFullscreen, Mode=TwoWay}"
MinWidth="250">
<TextBlock Text="{locale:Locale MenuBarOptionsStartGamesInFullscreen}"/>
</CheckBox>
</MenuItem.Icon>
</MenuItem>
<MenuItem IsVisible="{Binding ShowConsoleVisible}">
<MenuItem.Icon>
<CheckBox IsChecked="{Binding ShowConsole, Mode=TwoWay}"
MinWidth="250">
<TextBlock Text="{locale:Locale MenuBarOptionsShowConsole}"/>
</CheckBox>
</MenuItem.Icon>
</MenuItem>
<Separator />
<MenuItem Header="{locale:Locale MenuBarOptionsChangeLanguage}">
<MenuItem
Command="{ReflectionBinding ChangeLanguage}"
CommandParameter="de_DE"
Header="Deutsch" />
<MenuItem
Command="{ReflectionBinding ChangeLanguage}"
CommandParameter="en_US"
Header="English (US)" />
<MenuItem
Command="{ReflectionBinding ChangeLanguage}"
CommandParameter="es_ES"
Header="Español (ES)" />
<MenuItem
Command="{ReflectionBinding ChangeLanguage}"
CommandParameter="fr_FR"
Header="Français" />
<MenuItem
Command="{ReflectionBinding ChangeLanguage}"
CommandParameter="it_IT"
Header="Italiano" />
<MenuItem
Command="{ReflectionBinding ChangeLanguage}"
CommandParameter="pt_BR"
Header="Português (BR)" />
<MenuItem
Command="{ReflectionBinding ChangeLanguage}"
CommandParameter="tr_TR"
Header="Türkçe" />
<MenuItem
Command="{ReflectionBinding ChangeLanguage}"
CommandParameter="el_GR"
Header="Ελληνικά" />
<MenuItem
Command="{ReflectionBinding ChangeLanguage}"
CommandParameter="pl_PL"
Header="Polski" />
<MenuItem
Command="{ReflectionBinding ChangeLanguage}"
CommandParameter="ru_RU"
Header="Русский" />
<MenuItem
Command="{ReflectionBinding ChangeLanguage}"
CommandParameter="zh_CN"
Header="简体中文" />
<MenuItem
Command="{ReflectionBinding ChangeLanguage}"
CommandParameter="zh_TW"
Header="繁體中文" />
<MenuItem
Command="{ReflectionBinding ChangeLanguage}"
CommandParameter="ja_JP"
Header="日本語" />
<MenuItem
Command="{ReflectionBinding ChangeLanguage}"
CommandParameter="ko_KR"
Header="한국어" />
</MenuItem>
<Separator />
<MenuItem
Click="OpenSettings"
Header="{locale:Locale MenuBarOptionsSettings}"
ToolTip.Tip="{locale:Locale OpenSettingsTooltip}" />
<MenuItem
Command="{ReflectionBinding ManageProfiles}"
Header="{locale:Locale MenuBarOptionsManageUserProfiles}"
IsEnabled="{Binding EnableNonGameRunningControls}"
ToolTip.Tip="{locale:Locale OpenProfileManagerTooltip}" />
</MenuItem>
<MenuItem
Name="ActionsMenuItem"
VerticalAlignment="Center"
Header="{locale:Locale MenuBarActions}"
IsEnabled="{Binding IsGameRunning}">
<MenuItem
Click="PauseEmulation_Click"
Header="{locale:Locale MenuBarOptionsPauseEmulation}"
InputGesture="{Binding PauseKey}"
IsEnabled="{Binding !IsPaused}"
IsVisible="{Binding !IsPaused}" />
<MenuItem
Click="ResumeEmulation_Click"
Header="{locale:Locale MenuBarOptionsResumeEmulation}"
InputGesture="{Binding PauseKey}"
IsEnabled="{Binding IsPaused}"
IsVisible="{Binding IsPaused}" />
<MenuItem
Click="StopEmulation_Click"
Header="{locale:Locale MenuBarOptionsStopEmulation}"
InputGesture="Escape"
IsEnabled="{Binding IsGameRunning}"
ToolTip.Tip="{locale:Locale StopEmulationTooltip}" />
<MenuItem Command="{ReflectionBinding SimulateWakeUpMessage}" Header="{locale:Locale MenuBarOptionsSimulateWakeUpMessage}" />
<Separator />
<MenuItem
Name="ScanAmiiboMenuItem"
AttachedToVisualTree="ScanAmiiboMenuItem_AttachedToVisualTree"
Click="OpenAmiiboWindow"
Header="{locale:Locale MenuBarActionsScanAmiibo}"
IsEnabled="{Binding IsAmiiboRequested}" />
<MenuItem
Command="{ReflectionBinding TakeScreenshot}"
Header="{locale:Locale MenuBarFileToolsTakeScreenshot}"
InputGesture="{Binding ScreenshotKey}"
IsEnabled="{Binding IsGameRunning}" />
<MenuItem
Command="{ReflectionBinding HideUi}"
Header="{locale:Locale MenuBarFileToolsHideUi}"
InputGesture="{Binding ShowUiKey}"
IsEnabled="{Binding IsGameRunning}" />
<MenuItem
Click="OpenCheatManagerForCurrentApp"
Header="{locale:Locale GameListContextMenuManageCheat}"
IsEnabled="{Binding IsGameRunning}" />
</MenuItem>
<MenuItem VerticalAlignment="Center" Header="{locale:Locale MenuBarTools}">
<MenuItem Header="{locale:Locale MenuBarToolsInstallFirmware}" IsEnabled="{Binding EnableNonGameRunningControls}">
<MenuItem Command="{ReflectionBinding InstallFirmwareFromFile}" Header="{locale:Locale MenuBarFileToolsInstallFirmwareFromFile}" />
<MenuItem Command="{ReflectionBinding InstallFirmwareFromFolder}" Header="{locale:Locale MenuBarFileToolsInstallFirmwareFromDirectory}" />
</MenuItem>
</MenuItem>
<MenuItem VerticalAlignment="Center" Header="{locale:Locale MenuBarHelp}">
<MenuItem
Name="UpdateMenuItem"
IsEnabled="{Binding CanUpdate}"
Click="CheckForUpdates"
Header="{locale:Locale MenuBarHelpCheckForUpdates}"
ToolTip.Tip="{locale:Locale CheckUpdatesTooltip}" />
<Separator />
<MenuItem
Click="OpenAboutWindow"
Header="{locale:Locale MenuBarHelpAbout}"
ToolTip.Tip="{locale:Locale OpenAboutTooltip}" />
</MenuItem>
</Menu>
</DockPanel>
</UserControl>

View File

@@ -0,0 +1,146 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ava.UI.Windows;
using System.Threading.Tasks;
using LibHac.FsSystem;
using LibHac.Ncm;
using Ryujinx.HLE.HOS;
using Ryujinx.Modules;
namespace Ryujinx.Ava.UI.Views.Main
{
public partial class MainMenuBarView : UserControl
{
public MainWindow Window { get; private set; }
public MainWindowViewModel ViewModel { get; private set; }
public MainMenuBarView()
{
InitializeComponent();
}
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
if (this.VisualRoot is MainWindow window)
{
Window = window;
}
ViewModel = Window.ViewModel;
DataContext = ViewModel;
}
private async void StopEmulation_Click(object sender, RoutedEventArgs e)
{
await Task.Run(() =>
{
Window.ViewModel.AppHost?.ShowExitPrompt();
});
}
private async void PauseEmulation_Click(object sender, RoutedEventArgs e)
{
await Task.Run(() =>
{
Window.ViewModel.AppHost?.Pause();
});
}
private async void ResumeEmulation_Click(object sender, RoutedEventArgs e)
{
await Task.Run(() =>
{
Window.ViewModel.AppHost?.Resume();
});
}
public async void OpenSettings(object sender, RoutedEventArgs e)
{
Window.SettingsWindow = new(Window.VirtualFileSystem, Window.ContentManager);
await Window.SettingsWindow.ShowDialog(Window);
ViewModel.LoadConfigurableHotKeys();
}
public void OpenMiiApplet(object sender, RoutedEventArgs e)
{
string contentPath = ViewModel.ContentManager.GetInstalledContentPath(0x0100000000001009, StorageId.BuiltInSystem, NcaContentType.Program);
if (!string.IsNullOrEmpty(contentPath))
{
ViewModel.LoadApplication(contentPath, false, "Mii Applet");
}
}
public async void OpenAmiiboWindow(object sender, RoutedEventArgs e)
{
if (!ViewModel.IsAmiiboRequested)
{
return;
}
if (ViewModel.AppHost.Device.System.SearchingForAmiibo(out int deviceId))
{
string titleId = ViewModel.AppHost.Device.Application.TitleIdText.ToUpper();
AmiiboWindow window = new(ViewModel.ShowAll, ViewModel.LastScannedAmiiboId, titleId);
await window.ShowDialog(Window);
if (window.IsScanned)
{
ViewModel.ShowAll = window.ViewModel.ShowAllAmiibo;
ViewModel.LastScannedAmiiboId = window.ScannedAmiibo.GetId();
ViewModel.AppHost.Device.System.ScanAmiibo(deviceId, ViewModel.LastScannedAmiiboId, window.ViewModel.UseRandomUuid);
}
}
}
public async void OpenCheatManagerForCurrentApp(object sender, RoutedEventArgs e)
{
if (!ViewModel.IsGameRunning)
{
return;
}
ApplicationLoader application = ViewModel.AppHost.Device.Application;
if (application != null)
{
await new CheatWindow(Window.VirtualFileSystem, application.TitleIdText, application.TitleName).ShowDialog(Window);
ViewModel.AppHost.Device.EnableCheats();
}
}
private void ScanAmiiboMenuItem_AttachedToVisualTree(object sender, VisualTreeAttachmentEventArgs e)
{
if (sender is MenuItem)
{
ViewModel.IsAmiiboRequested = Window.ViewModel.AppHost.Device.System.SearchingForAmiibo(out _);
}
}
public async void CheckForUpdates(object sender, RoutedEventArgs e)
{
if (Updater.CanUpdate(true, Window))
{
await Updater.BeginParse(Window, true);
}
}
public async void OpenAboutWindow(object sender, RoutedEventArgs e)
{
await AboutWindow.Show();
}
public void CloseWindow(object sender, RoutedEventArgs e)
{
Window.Close();
}
}
}

View File

@@ -0,0 +1,232 @@
<UserControl
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:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Ryujinx.Ava.UI.Views.Main.MainStatusBarView"
x:CompileBindings="True"
x:DataType="viewModels:MainWindowViewModel">
<Design.DataContext>
<viewModels:MainWindowViewModel />
</Design.DataContext>
<Grid
Name="StatusBar"
Margin="0"
MinHeight="22"
HorizontalAlignment="Stretch"
VerticalAlignment="Bottom"
Background="{DynamicResource ThemeContentBackgroundColor}"
DockPanel.Dock="Bottom"
IsVisible="{Binding ShowMenuAndStatusBar}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel
Grid.Column="0"
Margin="5"
VerticalAlignment="Center"
IsVisible="{Binding EnableNonGameRunningControls}">
<Grid Margin="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Button
Width="25"
Height="25"
MinWidth="0"
Margin="0,0,5,0"
VerticalAlignment="Center"
Background="Transparent"
Command="{ReflectionBinding LoadApplications}">
<ui:SymbolIcon
Width="50"
Height="100"
Symbol="Refresh" />
</Button>
<TextBlock
Name="LoadStatus"
Grid.Column="1"
Margin="0,0,5,0"
VerticalAlignment="Center"
IsVisible="{Binding EnableNonGameRunningControls}"
Text="{locale:Locale StatusBarGamesLoaded}" />
<ProgressBar
Name="LoadProgressBar"
Grid.Column="2"
Height="6"
VerticalAlignment="Center"
Foreground="{DynamicResource HighlightColor}"
IsVisible="{Binding StatusBarVisible}"
Maximum="{Binding StatusBarProgressMaximum}"
Value="{Binding StatusBarProgressValue}" />
</Grid>
</StackPanel>
<StackPanel
Grid.Column="1"
Margin="0,2"
HorizontalAlignment="Left"
VerticalAlignment="Center"
IsVisible="{Binding IsGameRunning}"
MaxHeight="18"
Orientation="Horizontal">
<TextBlock
Name="VsyncStatus"
Margin="5,0,5,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Foreground="{Binding VsyncColor}"
IsVisible="{Binding !ShowLoadProgress}"
PointerReleased="VsyncStatus_PointerReleased"
Text="VSync"
TextAlignment="Left" />
<Border
Width="2"
Height="12"
Margin="0"
BorderBrush="Gray"
BorderThickness="1"
IsVisible="{Binding !ShowLoadProgress}" />
<TextBlock
Name="DockedStatus"
Margin="5,0,5,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
IsVisible="{Binding !ShowLoadProgress}"
PointerReleased="DockedStatus_PointerReleased"
Text="{Binding DockedStatusText}"
TextAlignment="Left" />
<Border
Width="2"
Height="12"
Margin="0"
BorderBrush="Gray"
BorderThickness="1"
IsVisible="{Binding !ShowLoadProgress}" />
<TextBlock
Name="AspectRatioStatus"
Margin="5,0,5,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
IsVisible="{Binding !ShowLoadProgress}"
PointerReleased="AspectRatioStatus_PointerReleased"
Text="{Binding AspectRatioStatusText}"
TextAlignment="Left" />
<Border
Width="2"
Height="12"
Margin="0"
BorderBrush="Gray"
BorderThickness="1"
IsVisible="{Binding !ShowLoadProgress}" />
<ui:ToggleSplitButton
Name="VolumeStatus"
Padding="5,0,5,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
VerticalContentAlignment="Center"
Background="{DynamicResource ThemeContentBackgroundColor}"
BorderThickness="0"
Content="{Binding VolumeStatusText}"
IsChecked="{Binding VolumeMuted}"
IsVisible="{Binding !ShowLoadProgress}">
<ui:ToggleSplitButton.Flyout>
<Flyout Placement="Bottom" ShowMode="TransientWithDismissOnPointerMoveAway">
<Grid Margin="0">
<Slider
MaxHeight="40"
Width="150"
Margin="0"
Padding="0"
IsSnapToTickEnabled="True"
LargeChange="0.05"
Maximum="1"
Minimum="0"
SmallChange="0.01"
TickFrequency="0.05"
ToolTip.Tip="{locale:Locale AudioVolumeTooltip}"
Value="{Binding Volume}" />
</Grid>
</Flyout>
</ui:ToggleSplitButton.Flyout>
</ui:ToggleSplitButton>
<Border
Width="2"
Height="12"
Margin="0"
BorderBrush="Gray"
BorderThickness="1"
IsVisible="{Binding !ShowLoadProgress}" />
<TextBlock
Margin="5,0,5,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
IsVisible="{Binding !ShowLoadProgress}"
Text="{Binding GameStatusText}"
TextAlignment="Left" />
<Border
Width="2"
Height="12"
Margin="0"
BorderBrush="Gray"
BorderThickness="1"
IsVisible="{Binding !ShowLoadProgress}" />
<TextBlock
Margin="5,0,5,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
IsVisible="{Binding !ShowLoadProgress}"
Text="{Binding FifoStatusText}"
TextAlignment="Left" />
<Border
Width="2"
Height="12"
Margin="0"
BorderBrush="Gray"
BorderThickness="1"
IsVisible="{Binding !ShowLoadProgress}" />
<TextBlock
Margin="5,0,5,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
IsVisible="{Binding !ShowLoadProgress}"
Text="{Binding BackendText}"
TextAlignment="Left" />
<Border
Width="2"
Height="12"
Margin="0"
BorderBrush="Gray"
BorderThickness="1"
IsVisible="{Binding !ShowLoadProgress}" />
<TextBlock
Margin="5,0,5,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
IsVisible="{Binding !ShowLoadProgress}"
Text="{Binding GpuNameText}"
TextAlignment="Left" />
</StackPanel>
<StackPanel
Grid.Column="3"
Margin="0,0,5,0"
VerticalAlignment="Center"
IsVisible="{Binding ShowFirmwareStatus}"
Orientation="Horizontal">
<TextBlock
Name="FirmwareStatus"
Margin="0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Text="{locale:Locale StatusBarSystemVersion}" />
</StackPanel>
</Grid>
</UserControl>

View File

@@ -0,0 +1,52 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using Ryujinx.Ava.UI.Windows;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using Ryujinx.Ui.Common.Configuration;
using System;
namespace Ryujinx.Ava.UI.Views.Main
{
public partial class MainStatusBarView : UserControl
{
public MainWindow Window;
public MainStatusBarView()
{
InitializeComponent();
}
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
if (this.VisualRoot is MainWindow window)
{
Window = window;
}
DataContext = Window.ViewModel;
}
private void VsyncStatus_PointerReleased(object sender, PointerReleasedEventArgs e)
{
Window.ViewModel.AppHost.Device.EnableDeviceVsync = !Window.ViewModel.AppHost.Device.EnableDeviceVsync;
Logger.Info?.Print(LogClass.Application, $"VSync toggled to: {Window.ViewModel.AppHost.Device.EnableDeviceVsync}");
}
private void DockedStatus_PointerReleased(object sender, PointerReleasedEventArgs e)
{
ConfigurationState.Instance.System.EnableDockedMode.Value = !ConfigurationState.Instance.System.EnableDockedMode.Value;
}
private void AspectRatioStatus_PointerReleased(object sender, PointerReleasedEventArgs e)
{
AspectRatio aspectRatio = ConfigurationState.Instance.Graphics.AspectRatio.Value;
ConfigurationState.Instance.Graphics.AspectRatio.Value = (int)aspectRatio + 1 > Enum.GetNames(typeof(AspectRatio)).Length - 1 ? AspectRatio.Fixed4x3 : aspectRatio + 1;
}
}
}

View File

@@ -0,0 +1,176 @@
<UserControl
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:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Ryujinx.Ava.UI.Views.Main.MainViewControls"
x:CompileBindings="True"
x:DataType="viewModels:MainWindowViewModel">
<Design.DataContext>
<viewModels:MainWindowViewModel />
</Design.DataContext>
<DockPanel
Margin="0,0,0,5"
HorizontalAlignment="Stretch">
<Button
Width="40"
MinWidth="40"
Margin="5,2,0,2"
VerticalAlignment="Stretch"
Command="{ReflectionBinding SetListMode}"
IsEnabled="{Binding IsGrid}">
<ui:FontIcon
Margin="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
FontFamily="avares://FluentAvalonia/Fonts#Symbols"
Glyph="{helpers:GlyphValueConverter List}" />
</Button>
<Button
Width="40"
MinWidth="40"
Margin="5,2,5,2"
VerticalAlignment="Stretch"
Command="{ReflectionBinding SetGridMode}"
IsEnabled="{Binding IsList}">
<ui:FontIcon
Margin="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
FontFamily="avares://FluentAvalonia/Fonts#Symbols"
Glyph="{helpers:GlyphValueConverter Grid}" />
</Button>
<TextBlock
Margin="10,0"
VerticalAlignment="Center"
Text="{locale:Locale IconSize}"
ToolTip.Tip="{locale:Locale IconSizeTooltip}" />
<Slider
Width="150"
Height="35"
Margin="5,-10,5,0"
VerticalAlignment="Center"
IsSnapToTickEnabled="True"
Maximum="4"
Minimum="1"
TickFrequency="1"
ToolTip.Tip="{locale:Locale IconSizeTooltip}"
Value="{Binding GridSizeScale}" />
<CheckBox
Margin="0"
VerticalAlignment="Center"
IsChecked="{Binding ShowNames, Mode=TwoWay}"
IsVisible="{Binding IsGrid}">
<TextBlock Margin="5,3,0,0" Text="{locale:Locale CommonShowNames}" />
</CheckBox>
<TextBox
Name="SearchBox"
MinWidth="200"
Margin="5,0,5,0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
VerticalContentAlignment="Center"
DockPanel.Dock="Right"
KeyUp="SearchBox_OnKeyUp"
Text="{Binding SearchText}"
Watermark="{locale:Locale MenuSearch}" />
<ui:DropDownButton
Width="150"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Content="{Binding SortName}"
DockPanel.Dock="Right">
<ui:DropDownButton.Flyout>
<Flyout Placement="Bottom">
<StackPanel
Margin="0"
HorizontalAlignment="Stretch"
Orientation="Vertical">
<StackPanel>
<RadioButton
Checked="Sort_Checked"
Content="{locale:Locale CommonFavorite}"
GroupName="Sort"
IsChecked="{Binding IsSortedByFavorite, Mode=OneTime}"
Tag="Favorite" />
<RadioButton
Checked="Sort_Checked"
Content="{locale:Locale GameListHeaderApplication}"
GroupName="Sort"
IsChecked="{Binding IsSortedByTitle, Mode=OneTime}"
Tag="Title" />
<RadioButton
Checked="Sort_Checked"
Content="{locale:Locale GameListHeaderDeveloper}"
GroupName="Sort"
IsChecked="{Binding IsSortedByDeveloper, Mode=OneTime}"
Tag="Developer" />
<RadioButton
Checked="Sort_Checked"
Content="{locale:Locale GameListHeaderTimePlayed}"
GroupName="Sort"
IsChecked="{Binding IsSortedByTimePlayed, Mode=OneTime}"
Tag="TotalTimePlayed" />
<RadioButton
Checked="Sort_Checked"
Content="{locale:Locale GameListHeaderLastPlayed}"
GroupName="Sort"
IsChecked="{Binding IsSortedByLastPlayed, Mode=OneTime}"
Tag="LastPlayed" />
<RadioButton
Checked="Sort_Checked"
Content="{locale:Locale GameListHeaderFileExtension}"
GroupName="Sort"
IsChecked="{Binding IsSortedByType, Mode=OneTime}"
Tag="FileType" />
<RadioButton
Checked="Sort_Checked"
Content="{locale:Locale GameListHeaderFileSize}"
GroupName="Sort"
IsChecked="{Binding IsSortedBySize, Mode=OneTime}"
Tag="FileSize" />
<RadioButton
Checked="Sort_Checked"
Content="{locale:Locale GameListHeaderPath}"
GroupName="Sort"
IsChecked="{Binding IsSortedByPath, Mode=OneTime}"
Tag="Path" />
</StackPanel>
<Border
Width="60"
Height="2"
Margin="5"
HorizontalAlignment="Stretch"
BorderBrush="White"
BorderThickness="0,1,0,0">
<Separator Height="0" HorizontalAlignment="Stretch" />
</Border>
<RadioButton
Checked="Order_Checked"
Content="{locale:Locale OrderAscending}"
GroupName="Order"
IsChecked="{Binding IsAscending, Mode=OneTime}"
Tag="Ascending" />
<RadioButton
Checked="Order_Checked"
Content="{locale:Locale OrderDescending}"
GroupName="Order"
IsChecked="{Binding !IsAscending, Mode=OneTime}"
Tag="Descending" />
</StackPanel>
</Flyout>
</ui:DropDownButton.Flyout>
</ui:DropDownButton>
<TextBlock
Margin="10,0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
DockPanel.Dock="Right"
Text="{locale:Locale CommonSort}" />
</DockPanel>
</UserControl>

View File

@@ -0,0 +1,54 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
using Ryujinx.Ava.Common;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ava.UI.Windows;
using System;
namespace Ryujinx.Ava.UI.Views.Main
{
public partial class MainViewControls : UserControl
{
public MainWindowViewModel ViewModel;
public MainViewControls()
{
InitializeComponent();
}
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
if (this.VisualRoot is MainWindow window)
{
ViewModel = window.ViewModel;
}
DataContext = ViewModel;
}
public void Sort_Checked(object sender, RoutedEventArgs args)
{
if (sender is RadioButton button)
{
ViewModel.Sort(Enum.Parse<ApplicationSort>(button.Tag.ToString()));
}
}
public void Order_Checked(object sender, RoutedEventArgs args)
{
if (sender is RadioButton button)
{
ViewModel.Sort(button.Tag.ToString() != "Descending");
}
}
private void SearchBox_OnKeyUp(object sender, KeyEventArgs e)
{
ViewModel.SearchText = SearchBox.Text;
}
}
}

View File

@@ -2,15 +2,16 @@
x:Class="Ryujinx.Ava.UI.Windows.MainWindow" x:Class="Ryujinx.Ava.UI.Windows.MainWindow"
xmlns="https://github.com/avaloniaui" xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:Ryujinx.Ava.UI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
xmlns:window="clr-namespace:Ryujinx.Ava.UI.Windows" xmlns:window="clr-namespace:Ryujinx.Ava.UI.Windows"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers" xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
Title="Ryujinx" xmlns:controls="clr-namespace:Ryujinx.Ava.UI.Controls"
xmlns:main="clr-namespace:Ryujinx.Ava.UI.Views.Main"
Cursor="{Binding Cursor}"
Title="{Binding Title}"
WindowState="{Binding WindowState}"
Width="1280" Width="1280"
Height="777" Height="777"
MinWidth="1092" MinWidth="1092"
@@ -66,206 +67,8 @@
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
IsVisible="{Binding ShowMenuAndStatusBar}" IsVisible="{Binding ShowMenuAndStatusBar}"
Orientation="Vertical"> Orientation="Vertical">
<DockPanel HorizontalAlignment="Stretch"> <main:MainMenuBarView
<Menu Name="MenuBarView" />
Name="Menu"
Height="35"
Margin="0"
HorizontalAlignment="Left">
<Menu.ItemsPanel>
<ItemsPanelTemplate>
<DockPanel Margin="0" HorizontalAlignment="Stretch" />
</ItemsPanelTemplate>
</Menu.ItemsPanel>
<MenuItem VerticalAlignment="Center" Header="{locale:Locale MenuBarFile}">
<MenuItem
Command="{ReflectionBinding OpenFile}"
Header="{locale:Locale MenuBarFileOpenFromFile}"
IsEnabled="{Binding EnableNonGameRunningControls}"
ToolTip.Tip="{locale:Locale LoadApplicationFileTooltip}" />
<MenuItem
Command="{ReflectionBinding OpenFolder}"
Header="{locale:Locale MenuBarFileOpenUnpacked}"
IsEnabled="{Binding EnableNonGameRunningControls}"
ToolTip.Tip="{locale:Locale LoadApplicationFolderTooltip}" />
<MenuItem Header="{locale:Locale MenuBarFileOpenApplet}" IsEnabled="{Binding IsAppletMenuActive}">
<MenuItem
Command="{ReflectionBinding OpenMiiApplet}"
Header="Mii Edit Applet"
ToolTip.Tip="{locale:Locale MenuBarFileOpenAppletOpenMiiAppletToolTip}" />
</MenuItem>
<Separator />
<MenuItem
Command="{ReflectionBinding OpenRyujinxFolder}"
Header="{locale:Locale MenuBarFileOpenEmuFolder}"
ToolTip.Tip="{locale:Locale OpenRyujinxFolderTooltip}" />
<MenuItem
Command="{ReflectionBinding OpenLogsFolder}"
Header="{locale:Locale MenuBarFileOpenLogsFolder}"
ToolTip.Tip="{locale:Locale OpenRyujinxLogsTooltip}" />
<Separator />
<MenuItem
Command="{ReflectionBinding CloseWindow}"
Header="{locale:Locale MenuBarFileExit}"
ToolTip.Tip="{locale:Locale ExitTooltip}" />
</MenuItem>
<MenuItem VerticalAlignment="Center" Header="{locale:Locale MenuBarOptions}">
<MenuItem
Command="{ReflectionBinding ToggleFullscreen}"
Header="{locale:Locale MenuBarOptionsToggleFullscreen}"
InputGesture="F11" />
<MenuItem>
<MenuItem.Icon>
<CheckBox IsChecked="{Binding StartGamesInFullscreen, Mode=TwoWay}"
MinWidth="250">
<TextBlock Text="{locale:Locale MenuBarOptionsStartGamesInFullscreen}"/>
</CheckBox>
</MenuItem.Icon>
</MenuItem>
<MenuItem IsVisible="{Binding ShowConsoleVisible}">
<MenuItem.Icon>
<CheckBox IsChecked="{Binding ShowConsole, Mode=TwoWay}"
MinWidth="250">
<TextBlock Text="{locale:Locale MenuBarOptionsShowConsole}"/>
</CheckBox>
</MenuItem.Icon>
</MenuItem>
<Separator />
<MenuItem Header="{locale:Locale MenuBarOptionsChangeLanguage}">
<MenuItem
Command="{ReflectionBinding ChangeLanguage}"
CommandParameter="de_DE"
Header="Deutsch" />
<MenuItem
Command="{ReflectionBinding ChangeLanguage}"
CommandParameter="en_US"
Header="English (US)" />
<MenuItem
Command="{ReflectionBinding ChangeLanguage}"
CommandParameter="es_ES"
Header="Español (ES)" />
<MenuItem
Command="{ReflectionBinding ChangeLanguage}"
CommandParameter="fr_FR"
Header="Français" />
<MenuItem
Command="{ReflectionBinding ChangeLanguage}"
CommandParameter="it_IT"
Header="Italiano" />
<MenuItem
Command="{ReflectionBinding ChangeLanguage}"
CommandParameter="pt_BR"
Header="Português (BR)" />
<MenuItem
Command="{ReflectionBinding ChangeLanguage}"
CommandParameter="tr_TR"
Header="Türkçe" />
<MenuItem
Command="{ReflectionBinding ChangeLanguage}"
CommandParameter="el_GR"
Header="Ελληνικά" />
<MenuItem
Command="{ReflectionBinding ChangeLanguage}"
CommandParameter="pl_PL"
Header="Polski" />
<MenuItem
Command="{ReflectionBinding ChangeLanguage}"
CommandParameter="ru_RU"
Header="Русский" />
<MenuItem
Command="{ReflectionBinding ChangeLanguage}"
CommandParameter="zh_CN"
Header="简体中文" />
<MenuItem
Command="{ReflectionBinding ChangeLanguage}"
CommandParameter="zh_TW"
Header="繁體中文" />
<MenuItem
Command="{ReflectionBinding ChangeLanguage}"
CommandParameter="ja_JP"
Header="日本語" />
<MenuItem
Command="{ReflectionBinding ChangeLanguage}"
CommandParameter="ko_KR"
Header="한국어" />
</MenuItem>
<Separator />
<MenuItem
Command="{ReflectionBinding OpenSettings}"
Header="{locale:Locale MenuBarOptionsSettings}"
ToolTip.Tip="{locale:Locale OpenSettingsTooltip}" />
<MenuItem
Command="{ReflectionBinding ManageProfiles}"
Header="{locale:Locale MenuBarOptionsManageUserProfiles}"
IsEnabled="{Binding EnableNonGameRunningControls}"
ToolTip.Tip="{locale:Locale OpenProfileManagerTooltip}" />
</MenuItem>
<MenuItem
Name="ActionsMenuItem"
VerticalAlignment="Center"
Header="{locale:Locale MenuBarActions}"
IsEnabled="{Binding IsGameRunning}">
<MenuItem
Click="PauseEmulation_Click"
Header="{locale:Locale MenuBarOptionsPauseEmulation}"
InputGesture="{Binding PauseKey}"
IsEnabled="{Binding !IsPaused}"
IsVisible="{Binding !IsPaused}" />
<MenuItem
Click="ResumeEmulation_Click"
Header="{locale:Locale MenuBarOptionsResumeEmulation}"
InputGesture="{Binding PauseKey}"
IsEnabled="{Binding IsPaused}"
IsVisible="{Binding IsPaused}" />
<MenuItem
Click="StopEmulation_Click"
Header="{locale:Locale MenuBarOptionsStopEmulation}"
InputGesture="Escape"
IsEnabled="{Binding IsGameRunning}"
ToolTip.Tip="{locale:Locale StopEmulationTooltip}" />
<MenuItem Command="{ReflectionBinding SimulateWakeUpMessage}" Header="{locale:Locale MenuBarOptionsSimulateWakeUpMessage}" />
<Separator />
<MenuItem
Name="ScanAmiiboMenuItem"
AttachedToVisualTree="ScanAmiiboMenuItem_AttachedToVisualTree"
Command="{ReflectionBinding OpenAmiiboWindow}"
Header="{locale:Locale MenuBarActionsScanAmiibo}"
IsEnabled="{Binding IsAmiiboRequested}" />
<MenuItem
Command="{ReflectionBinding TakeScreenshot}"
Header="{locale:Locale MenuBarFileToolsTakeScreenshot}"
InputGesture="{Binding ScreenshotKey}"
IsEnabled="{Binding IsGameRunning}" />
<MenuItem
Command="{ReflectionBinding HideUi}"
Header="{locale:Locale MenuBarFileToolsHideUi}"
InputGesture="{Binding ShowUiKey}"
IsEnabled="{Binding IsGameRunning}" />
<MenuItem
Command="{ReflectionBinding OpenCheatManagerForCurrentApp}"
Header="{locale:Locale GameListContextMenuManageCheat}"
IsEnabled="{Binding IsGameRunning}" />
</MenuItem>
<MenuItem VerticalAlignment="Center" Header="{locale:Locale MenuBarTools}">
<MenuItem Header="{locale:Locale MenuBarToolsInstallFirmware}" IsEnabled="{Binding EnableNonGameRunningControls}">
<MenuItem Command="{ReflectionBinding InstallFirmwareFromFile}" Header="{locale:Locale MenuBarFileToolsInstallFirmwareFromFile}" />
<MenuItem Command="{ReflectionBinding InstallFirmwareFromFolder}" Header="{locale:Locale MenuBarFileToolsInstallFirmwareFromDirectory}" />
</MenuItem>
</MenuItem>
<MenuItem VerticalAlignment="Center" Header="{locale:Locale MenuBarHelp}">
<MenuItem
Name="UpdateMenuItem"
Command="{ReflectionBinding CheckForUpdates}"
Header="{locale:Locale MenuBarHelpCheckForUpdates}"
ToolTip.Tip="{locale:Locale CheckUpdatesTooltip}" />
<Separator />
<MenuItem
Command="{ReflectionBinding OpenAboutWindow}"
Header="{locale:Locale MenuBarHelpAbout}"
ToolTip.Tip="{locale:Locale OpenAboutTooltip}" />
</MenuItem>
</Menu>
</DockPanel>
</StackPanel> </StackPanel>
<ContentControl <ContentControl
Name="MainContent" Name="MainContent"
@@ -277,170 +80,14 @@
BorderThickness="0,0,0,0" BorderThickness="0,0,0,0"
DockPanel.Dock="Top" DockPanel.Dock="Top"
IsVisible="{Binding ShowContent}"> IsVisible="{Binding ShowContent}">
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch"> <Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Name="GameLibrary">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="*" /> <RowDefinition Height="*" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<DockPanel <main:MainViewControls
Grid.Row="0" Name="ViewControls"
Margin="0,0,0,5" Grid.Row="0"/>
HorizontalAlignment="Stretch">
<Button
Width="40"
MinWidth="40"
Margin="5,2,0,2"
VerticalAlignment="Stretch"
Command="{ReflectionBinding SetListMode}"
IsEnabled="{Binding IsGrid}">
<ui:FontIcon
Margin="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
FontFamily="avares://FluentAvalonia/Fonts#Symbols"
Glyph="{helpers:GlyphValueConverter List}" />
</Button>
<Button
Width="40"
MinWidth="40"
Margin="5,2,5,2"
VerticalAlignment="Stretch"
Command="{ReflectionBinding SetGridMode}"
IsEnabled="{Binding IsList}">
<ui:FontIcon
Margin="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
FontFamily="avares://FluentAvalonia/Fonts#Symbols"
Glyph="{helpers:GlyphValueConverter Grid}" />
</Button>
<TextBlock
Margin="10,0"
VerticalAlignment="Center"
Text="{locale:Locale IconSize}"
ToolTip.Tip="{locale:Locale IconSizeTooltip}" />
<Slider
Width="150"
Height="35"
Margin="5,-10,5,0"
VerticalAlignment="Center"
IsSnapToTickEnabled="True"
Maximum="4"
Minimum="1"
TickFrequency="1"
ToolTip.Tip="{locale:Locale IconSizeTooltip}"
Value="{Binding GridSizeScale}" />
<CheckBox
Margin="0"
VerticalAlignment="Center"
IsChecked="{Binding ShowNames, Mode=TwoWay}"
IsVisible="{Binding IsGrid}">
<TextBlock Margin="5,3,0,0" Text="{locale:Locale CommonShowNames}" />
</CheckBox>
<TextBox
Name="SearchBox"
MinWidth="200"
Margin="5,0,5,0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
DockPanel.Dock="Right"
KeyUp="SearchBox_OnKeyUp"
Text="{Binding SearchText}"
Watermark="{locale:Locale MenuSearch}" />
<ui:DropDownButton
Width="150"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Content="{Binding SortName}"
DockPanel.Dock="Right">
<ui:DropDownButton.Flyout>
<Flyout Placement="Bottom">
<StackPanel
Margin="0"
HorizontalAlignment="Stretch"
Orientation="Vertical">
<StackPanel>
<RadioButton
Checked="Sort_Checked"
Content="{locale:Locale CommonFavorite}"
GroupName="Sort"
IsChecked="{Binding IsSortedByFavorite, Mode=OneTime}"
Tag="Favorite" />
<RadioButton
Checked="Sort_Checked"
Content="{locale:Locale GameListHeaderApplication}"
GroupName="Sort"
IsChecked="{Binding IsSortedByTitle, Mode=OneTime}"
Tag="Title" />
<RadioButton
Checked="Sort_Checked"
Content="{locale:Locale GameListHeaderDeveloper}"
GroupName="Sort"
IsChecked="{Binding IsSortedByDeveloper, Mode=OneTime}"
Tag="Developer" />
<RadioButton
Checked="Sort_Checked"
Content="{locale:Locale GameListHeaderTimePlayed}"
GroupName="Sort"
IsChecked="{Binding IsSortedByTimePlayed, Mode=OneTime}"
Tag="TotalTimePlayed" />
<RadioButton
Checked="Sort_Checked"
Content="{locale:Locale GameListHeaderLastPlayed}"
GroupName="Sort"
IsChecked="{Binding IsSortedByLastPlayed, Mode=OneTime}"
Tag="LastPlayed" />
<RadioButton
Checked="Sort_Checked"
Content="{locale:Locale GameListHeaderFileExtension}"
GroupName="Sort"
IsChecked="{Binding IsSortedByType, Mode=OneTime}"
Tag="FileType" />
<RadioButton
Checked="Sort_Checked"
Content="{locale:Locale GameListHeaderFileSize}"
GroupName="Sort"
IsChecked="{Binding IsSortedBySize, Mode=OneTime}"
Tag="FileSize" />
<RadioButton
Checked="Sort_Checked"
Content="{locale:Locale GameListHeaderPath}"
GroupName="Sort"
IsChecked="{Binding IsSortedByPath, Mode=OneTime}"
Tag="Path" />
</StackPanel>
<Border
Width="60"
Height="2"
Margin="5"
HorizontalAlignment="Stretch"
BorderBrush="White"
BorderThickness="0,1,0,0">
<Separator Height="0" HorizontalAlignment="Stretch" />
</Border>
<RadioButton
Checked="Order_Checked"
Content="{locale:Locale OrderAscending}"
GroupName="Order"
IsChecked="{Binding IsAscending, Mode=OneTime}"
Tag="Ascending" />
<RadioButton
Checked="Order_Checked"
Content="{locale:Locale OrderDescending}"
GroupName="Order"
IsChecked="{Binding !IsAscending, Mode=OneTime}"
Tag="Descending" />
</StackPanel>
</Flyout>
</ui:DropDownButton.Flyout>
</ui:DropDownButton>
<TextBlock
Margin="10,0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
DockPanel.Dock="Right"
Text="{locale:Locale CommonSort}" />
</DockPanel>
<controls:GameListView <controls:GameListView
x:Name="GameList" x:Name="GameList"
Grid.Row="1" Grid.Row="1"
@@ -512,7 +159,8 @@
IsVisible="{Binding ShowLoadProgress}" IsVisible="{Binding ShowLoadProgress}"
Text="{Binding LoadHeading}" Text="{Binding LoadHeading}"
TextAlignment="Left" TextAlignment="Left"
TextWrapping="Wrap" /> TextWrapping="Wrap"
MaxWidth="500" />
<Border <Border
Grid.Row="1" Grid.Row="1"
Margin="10" Margin="10"
@@ -529,7 +177,6 @@
Margin="0" Margin="0"
Padding="0" Padding="0"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
Background="{Binding ProgressBarBackgroundColor}"
ClipToBounds="True" ClipToBounds="True"
CornerRadius="5" CornerRadius="5"
Foreground="{Binding ProgressBarForegroundColor}" Foreground="{Binding ProgressBarForegroundColor}"
@@ -545,226 +192,14 @@
FontSize="18" FontSize="18"
IsVisible="{Binding ShowLoadProgress}" IsVisible="{Binding ShowLoadProgress}"
Text="{Binding CacheLoadStatus}" Text="{Binding CacheLoadStatus}"
TextAlignment="Left" /> TextAlignment="Left"
MaxWidth="500" />
</Grid> </Grid>
</Grid> </Grid>
</Grid> </Grid>
<Grid <main:MainStatusBarView
Name="StatusBar" Name="StatusBarView"
Grid.Row="2" Grid.Row="2" />
Margin="0"
MinHeight="22"
HorizontalAlignment="Stretch"
VerticalAlignment="Bottom"
Background="{DynamicResource ThemeContentBackgroundColor}"
DockPanel.Dock="Bottom"
IsVisible="{Binding ShowMenuAndStatusBar}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel
Grid.Column="0"
Margin="5"
VerticalAlignment="Center"
IsVisible="{Binding EnableNonGameRunningControls}">
<Grid Margin="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Button
Width="25"
Height="25"
MinWidth="0"
Margin="0,0,5,0"
VerticalAlignment="Center"
Background="Transparent"
Command="{ReflectionBinding LoadApplications}">
<ui:SymbolIcon
Width="50"
Height="100"
Symbol="Refresh" />
</Button>
<TextBlock
Name="LoadStatus"
Grid.Column="1"
Margin="0,0,5,0"
VerticalAlignment="Center"
IsVisible="{Binding EnableNonGameRunningControls}"
Text="{locale:Locale StatusBarGamesLoaded}" />
<ProgressBar
Name="LoadProgressBar"
Grid.Column="2"
Height="6"
VerticalAlignment="Center"
Foreground="{DynamicResource HighlightColor}"
IsVisible="{Binding EnableNonGameRunningControls}"
Maximum="{Binding StatusBarProgressMaximum}"
Value="{Binding StatusBarProgressValue}" />
</Grid>
</StackPanel>
<StackPanel
Grid.Column="1"
Margin="0,2"
HorizontalAlignment="Left"
VerticalAlignment="Center"
IsVisible="{Binding IsGameRunning}"
Orientation="Horizontal">
<TextBlock
Name="VsyncStatus"
Margin="5,0,5,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Foreground="{Binding VsyncColor}"
IsVisible="{Binding !ShowLoadProgress}"
PointerReleased="VsyncStatus_PointerReleased"
Text="VSync"
TextAlignment="Left" />
<Border
Width="2"
Height="12"
Margin="0"
BorderBrush="Gray"
BorderThickness="1"
IsVisible="{Binding !ShowLoadProgress}" />
<TextBlock
Name="DockedStatus"
Margin="5,0,5,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
IsVisible="{Binding !ShowLoadProgress}"
PointerReleased="DockedStatus_PointerReleased"
Text="{Binding DockedStatusText}"
TextAlignment="Left" />
<Border
Width="2"
Height="12"
Margin="0"
BorderBrush="Gray"
BorderThickness="1"
IsVisible="{Binding !ShowLoadProgress}" />
<TextBlock
Name="AspectRatioStatus"
Margin="5,0,5,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
IsVisible="{Binding !ShowLoadProgress}"
PointerReleased="AspectRatioStatus_PointerReleased"
Text="{Binding AspectRatioStatusText}"
TextAlignment="Left" />
<Border
Width="2"
Height="12"
Margin="0"
BorderBrush="Gray"
BorderThickness="1"
IsVisible="{Binding !ShowLoadProgress}" />
<ui:ToggleSplitButton
Name="VolumeStatus"
Padding="5,0,5,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
VerticalContentAlignment="Center"
Background="{DynamicResource ThemeContentBackgroundColor}"
BorderThickness="0"
Content="{Binding VolumeStatusText}"
IsChecked="{Binding VolumeMuted}"
IsVisible="{Binding !ShowLoadProgress}">
<ui:ToggleSplitButton.Flyout>
<Flyout Placement="Bottom" ShowMode="TransientWithDismissOnPointerMoveAway">
<Grid Margin="0">
<Slider
MaxHeight="40"
Width="150"
Margin="0"
Padding="0"
IsSnapToTickEnabled="True"
LargeChange="0.05"
Maximum="1"
Minimum="0"
SmallChange="0.01"
TickFrequency="0.05"
ToolTip.Tip="{locale:Locale AudioVolumeTooltip}"
Value="{Binding Volume}" />
</Grid>
</Flyout>
</ui:ToggleSplitButton.Flyout>
</ui:ToggleSplitButton>
<Border
Width="2"
Height="12"
Margin="0"
BorderBrush="Gray"
BorderThickness="1"
IsVisible="{Binding !ShowLoadProgress}" />
<TextBlock
Margin="5,0,5,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
IsVisible="{Binding !ShowLoadProgress}"
Text="{Binding GameStatusText}"
TextAlignment="Left" />
<Border
Width="2"
Height="12"
Margin="0"
BorderBrush="Gray"
BorderThickness="1"
IsVisible="{Binding !ShowLoadProgress}" />
<TextBlock
Margin="5,0,5,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
IsVisible="{Binding !ShowLoadProgress}"
Text="{Binding FifoStatusText}"
TextAlignment="Left" />
<Border
Width="2"
Height="12"
Margin="0"
BorderBrush="Gray"
BorderThickness="1"
IsVisible="{Binding !ShowLoadProgress}" />
<TextBlock
Margin="5,0,5,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
IsVisible="{Binding !ShowLoadProgress}"
Text="{Binding BackendText}"
TextAlignment="Left" />
<Border
Width="2"
Height="12"
Margin="0"
BorderBrush="Gray"
BorderThickness="1"
IsVisible="{Binding !ShowLoadProgress}" />
<TextBlock
Margin="5,0,5,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
IsVisible="{Binding !ShowLoadProgress}"
Text="{Binding GpuNameText}"
TextAlignment="Left" />
</StackPanel>
<StackPanel
Grid.Column="3"
Margin="0,0,5,0"
VerticalAlignment="Center"
IsVisible="{Binding ShowFirmwareStatus}"
Orientation="Horizontal">
<TextBlock
Name="FirmwareStatus"
Margin="0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Text="{locale:Locale StatusBarSystemVersion}" />
</StackPanel>
</Grid>
</Grid> </Grid>
</Grid> </Grid>
</window:StyleableWindow> </window:StyleableWindow>

View File

@@ -1,19 +1,14 @@
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Media;
using Avalonia.Threading; using Avalonia.Threading;
using FluentAvalonia.UI.Controls; using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.Common; using Ryujinx.Ava.Common;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Input; using Ryujinx.Ava.Input;
using Ryujinx.Ava.UI.Applet; using Ryujinx.Ava.UI.Applet;
using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.Helpers; using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Models;
using Ryujinx.Ava.UI.ViewModels; using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.Graphics.Gpu; using Ryujinx.Graphics.Gpu;
using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.FileSystem;
@@ -25,11 +20,9 @@ using Ryujinx.Ui.App.Common;
using Ryujinx.Ui.Common; using Ryujinx.Ui.Common;
using Ryujinx.Ui.Common.Configuration; using Ryujinx.Ui.Common.Configuration;
using Ryujinx.Ui.Common.Helper; using Ryujinx.Ui.Common.Helper;
using SixLabors.ImageSharp.PixelFormats;
using System; using System;
using System.ComponentModel; using System.ComponentModel;
using System.IO; using System.IO;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using InputManager = Ryujinx.Input.HLE.InputManager; using InputManager = Ryujinx.Input.HLE.InputManager;
@@ -38,19 +31,14 @@ namespace Ryujinx.Ava.UI.Windows
public partial class MainWindow : StyleableWindow public partial class MainWindow : StyleableWindow
{ {
internal static MainWindowViewModel MainWindowViewModel { get; private set; } internal static MainWindowViewModel MainWindowViewModel { get; private set; }
private bool _canUpdate;
private bool _isClosing;
private bool _isLoading;
private Control _mainViewContent; private bool _isLoading;
private UserChannelPersistence _userChannelPersistence; private UserChannelPersistence _userChannelPersistence;
private static bool _deferLoad; private static bool _deferLoad;
private static string _launchPath; private static string _launchPath;
private static bool _startFullscreen; private static bool _startFullscreen;
private string _currentEmulatedGamePath;
internal readonly AvaHostUiHandler UiHandler; internal readonly AvaHostUiHandler UiHandler;
private AutoResetEvent _rendererWaitEvent;
public VirtualFileSystem VirtualFileSystem { get; private set; } public VirtualFileSystem VirtualFileSystem { get; private set; }
public ContentManager ContentManager { get; private set; } public ContentManager ContentManager { get; private set; }
@@ -58,30 +46,17 @@ namespace Ryujinx.Ava.UI.Windows
public LibHacHorizonManager LibHacHorizonManager { get; private set; } public LibHacHorizonManager LibHacHorizonManager { get; private set; }
internal AppHost AppHost { get; private set; }
public InputManager InputManager { get; private set; } public InputManager InputManager { get; private set; }
internal RendererHost RendererControl { get; private set; }
internal MainWindowViewModel ViewModel { get; private set; } internal MainWindowViewModel ViewModel { get; private set; }
public SettingsWindow SettingsWindow { get; set; } public SettingsWindow SettingsWindow { get; set; }
public bool CanUpdate
{
get => _canUpdate;
set
{
_canUpdate = value;
Dispatcher.UIThread.InvokeAsync(() => UpdateMenuItem.IsEnabled = _canUpdate);
}
}
public static bool ShowKeyErrorOnLoad { get; set; } public static bool ShowKeyErrorOnLoad { get; set; }
public ApplicationLibrary ApplicationLibrary { get; set; } public ApplicationLibrary ApplicationLibrary { get; set; }
public MainWindow() public MainWindow()
{ {
ViewModel = new MainWindowViewModel(this); ViewModel = new MainWindowViewModel();
MainWindowViewModel = ViewModel; MainWindowViewModel = ViewModel;
@@ -92,10 +67,10 @@ namespace Ryujinx.Ava.UI.Windows
UiHandler = new AvaHostUiHandler(this); UiHandler = new AvaHostUiHandler(this);
Title = $"Ryujinx {Program.Version}"; ViewModel.Title = $"Ryujinx {Program.Version}";
// NOTE: Height of MenuBar and StatusBar is not usable here, since it would still be 0 at this point. // NOTE: Height of MenuBar and StatusBar is not usable here, since it would still be 0 at this point.
double barHeight = MenuBar.MinHeight + StatusBar.MinHeight; double barHeight = MenuBar.MinHeight + StatusBarView.StatusBar.MinHeight;
Height = ((Height - barHeight) / Program.WindowScaleFactor) + barHeight; Height = ((Height - barHeight) / Program.WindowScaleFactor) + barHeight;
Width /= Program.WindowScaleFactor; Width /= Program.WindowScaleFactor;
@@ -103,14 +78,37 @@ namespace Ryujinx.Ava.UI.Windows
{ {
Initialize(); Initialize();
ViewModel.Initialize();
InputManager = new InputManager(new AvaloniaKeyboardDriver(this), new SDL2GamepadDriver()); InputManager = new InputManager(new AvaloniaKeyboardDriver(this), new SDL2GamepadDriver());
ViewModel.Initialize(
ContentManager,
ApplicationLibrary,
VirtualFileSystem,
AccountManager,
InputManager,
_userChannelPersistence,
LibHacHorizonManager,
UiHandler,
ShowLoading,
SwitchToGameControl,
SetMainContent,
this);
ViewModel.RefreshFirmwareStatus();
LoadGameList(); LoadGameList();
this.GetObservable(IsActiveProperty).Subscribe(IsActiveChanged);
} }
_rendererWaitEvent = new AutoResetEvent(false); ApplicationLibrary.ApplicationCountUpdated += ApplicationLibrary_ApplicationCountUpdated;
ApplicationLibrary.ApplicationAdded += ApplicationLibrary_ApplicationAdded;
ViewModel.ReloadGameList += ReloadGameList;
}
private void IsActiveChanged(bool obj)
{
ViewModel.IsActive = obj;
} }
public void LoadGameList() public void LoadGameList()
@@ -122,45 +120,51 @@ namespace Ryujinx.Ava.UI.Windows
_isLoading = true; _isLoading = true;
ViewModel.LoadApplications(); LoadApplications();
_isLoading = false; _isLoading = false;
} }
private void Update_StatusBar(object sender, StatusUpdatedEventArgs args)
{
if (ViewModel.ShowMenuAndStatusBar && !ViewModel.ShowLoadProgress)
{
Dispatcher.UIThread.InvokeAsync(() =>
{
if (args.VSyncEnabled)
{
ViewModel.VsyncColor = new SolidColorBrush(Color.Parse("#ff2eeac9"));
}
else
{
ViewModel.VsyncColor = new SolidColorBrush(Color.Parse("#ffff4554"));
}
ViewModel.DockedStatusText = args.DockedMode;
ViewModel.AspectRatioStatusText = args.AspectRatio;
ViewModel.GameStatusText = args.GameStatus;
ViewModel.VolumeStatusText = args.VolumeStatus;
ViewModel.FifoStatusText = args.FifoStatus;
ViewModel.GpuNameText = args.GpuName;
ViewModel.BackendText = args.GpuBackend;
ViewModel.ShowStatusSeparator = true;
});
}
}
protected override void HandleScalingChanged(double scale) protected override void HandleScalingChanged(double scale)
{ {
Program.DesktopScaleFactor = scale; Program.DesktopScaleFactor = scale;
base.HandleScalingChanged(scale); base.HandleScalingChanged(scale);
} }
public void AddApplication(ApplicationData applicationData)
{
Dispatcher.UIThread.InvokeAsync(() =>
{
ViewModel.Applications.Add(applicationData);
});
}
private void ApplicationLibrary_ApplicationAdded(object sender, ApplicationAddedEventArgs e)
{
AddApplication(e.AppData);
}
private void ApplicationLibrary_ApplicationCountUpdated(object sender, ApplicationCountUpdatedEventArgs e)
{
LocaleManager.Instance.UpdateDynamicValue(LocaleKeys.StatusBarGamesLoaded, e.NumAppsLoaded, e.NumAppsFound);
Dispatcher.UIThread.Post(() =>
{
ViewModel.StatusBarProgressValue = e.NumAppsLoaded;
ViewModel.StatusBarProgressMaximum = e.NumAppsFound;
if (e.NumAppsFound == 0)
{
StatusBarView.LoadProgressBar.IsVisible = false;
}
if (e.NumAppsLoaded == e.NumAppsFound)
{
StatusBarView.LoadProgressBar.IsVisible = false;
}
});
}
public void Application_Opened(object sender, ApplicationOpenedEventArgs args) public void Application_Opened(object sender, ApplicationOpenedEventArgs args)
{ {
if (args.Application != null) if (args.Application != null)
@@ -169,50 +173,12 @@ namespace Ryujinx.Ava.UI.Windows
string path = new FileInfo(args.Application.Path).FullName; string path = new FileInfo(args.Application.Path).FullName;
LoadApplication(path); ViewModel.LoadApplication(path);
} }
args.Handled = true; args.Handled = true;
} }
public async Task PerformanceCheck()
{
if (ConfigurationState.Instance.Logger.EnableTrace.Value)
{
string mainMessage = LocaleManager.Instance[LocaleKeys.DialogPerformanceCheckLoggingEnabledMessage];
string secondaryMessage = LocaleManager.Instance[LocaleKeys.DialogPerformanceCheckLoggingEnabledConfirmMessage];
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(mainMessage, secondaryMessage,
LocaleManager.Instance[LocaleKeys.InputDialogYes], LocaleManager.Instance[LocaleKeys.InputDialogNo],
LocaleManager.Instance[LocaleKeys.RyujinxConfirm]);
if (result != UserResult.Yes)
{
ConfigurationState.Instance.Logger.EnableTrace.Value = false;
SaveConfig();
}
}
if (!string.IsNullOrWhiteSpace(ConfigurationState.Instance.Graphics.ShadersDumpPath.Value))
{
string mainMessage = LocaleManager.Instance[LocaleKeys.DialogPerformanceCheckShaderDumpEnabledMessage];
string secondaryMessage =
LocaleManager.Instance[LocaleKeys.DialogPerformanceCheckShaderDumpEnabledConfirmMessage];
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(mainMessage, secondaryMessage,
LocaleManager.Instance[LocaleKeys.InputDialogYes], LocaleManager.Instance[LocaleKeys.InputDialogNo],
LocaleManager.Instance[LocaleKeys.RyujinxConfirm]);
if (result != UserResult.Yes)
{
ConfigurationState.Instance.Graphics.ShadersDumpPath.Value = "";
SaveConfig();
}
}
}
internal static void DeferLoadApplication(string launchPathArg, bool startFullscreenArg) internal static void DeferLoadApplication(string launchPathArg, bool startFullscreenArg)
{ {
_deferLoad = true; _deferLoad = true;
@@ -220,109 +186,6 @@ namespace Ryujinx.Ava.UI.Windows
_startFullscreen = startFullscreenArg; _startFullscreen = startFullscreenArg;
} }
#pragma warning disable CS1998
public async void LoadApplication(string path, bool startFullscreen = false, string titleName = "")
#pragma warning restore CS1998
{
if (AppHost != null)
{
await ContentDialogHelper.CreateInfoDialog(
LocaleManager.Instance[LocaleKeys.DialogLoadAppGameAlreadyLoadedMessage],
LocaleManager.Instance[LocaleKeys.DialogLoadAppGameAlreadyLoadedSubMessage],
LocaleManager.Instance[LocaleKeys.InputDialogOk],
"",
LocaleManager.Instance[LocaleKeys.RyujinxInfo]);
return;
}
#if RELEASE
await PerformanceCheck();
#endif
Logger.RestartTime();
if (ViewModel.SelectedIcon == null)
{
ViewModel.SelectedIcon = ApplicationLibrary.GetApplicationIcon(path);
}
PrepareLoadScreen();
_mainViewContent = MainContent.Content as Control;
RendererControl = new RendererHost(ConfigurationState.Instance.Logger.GraphicsDebugLevel);
if (ConfigurationState.Instance.Graphics.GraphicsBackend.Value == GraphicsBackend.OpenGl)
{
RendererControl.CreateOpenGL();
}
else
{
RendererControl.CreateVulkan();
}
AppHost = new AppHost(RendererControl, InputManager, path, VirtualFileSystem, ContentManager, AccountManager, _userChannelPersistence, this);
Dispatcher.UIThread.Post(async () =>
{
if (!await AppHost.LoadGuestApplication())
{
AppHost.DisposeContext();
AppHost = null;
return;
}
CanUpdate = false;
ViewModel.LoadHeading = string.IsNullOrWhiteSpace(titleName) ? string.Format(LocaleManager.Instance[LocaleKeys.LoadingHeading], AppHost.Device.Application.TitleName) : titleName;
ViewModel.TitleName = string.IsNullOrWhiteSpace(titleName) ? AppHost.Device.Application.TitleName : titleName;
SwitchToGameControl(startFullscreen);
_currentEmulatedGamePath = path;
Thread gameThread = new(InitializeGame)
{
Name = "GUI.WindowThread"
};
gameThread.Start();
});
}
private void InitializeGame()
{
RendererControl.RendererInitialized += GlRenderer_Created;
AppHost.StatusUpdatedEvent += Update_StatusBar;
AppHost.AppExit += AppHost_AppExit;
_rendererWaitEvent.WaitOne();
AppHost?.Start();
AppHost.DisposeContext();
}
private void HandleRelaunch()
{
if (_userChannelPersistence.PreviousIndex != -1 && _userChannelPersistence.ShouldRestart)
{
_userChannelPersistence.ShouldRestart = false;
Dispatcher.UIThread.Post(() =>
{
LoadApplication(_currentEmulatedGamePath);
});
}
else
{
// otherwise, clear state.
_userChannelPersistence = new UserChannelPersistence();
_currentEmulatedGamePath = null;
}
}
public void SwitchToGameControl(bool startFullscreen = false) public void SwitchToGameControl(bool startFullscreen = false)
{ {
ViewModel.ShowLoadProgress = false; ViewModel.ShowLoadProgress = false;
@@ -331,14 +194,10 @@ namespace Ryujinx.Ava.UI.Windows
Dispatcher.UIThread.InvokeAsync(() => Dispatcher.UIThread.InvokeAsync(() =>
{ {
MainContent.Content = RendererControl; if (startFullscreen && ViewModel.WindowState != WindowState.FullScreen)
if (startFullscreen && WindowState != WindowState.FullScreen)
{ {
ViewModel.ToggleFullscreen(); ViewModel.ToggleFullscreen();
} }
RendererControl.Focus();
}); });
} }
@@ -350,71 +209,16 @@ namespace Ryujinx.Ava.UI.Windows
Dispatcher.UIThread.InvokeAsync(() => Dispatcher.UIThread.InvokeAsync(() =>
{ {
if (startFullscreen && WindowState != WindowState.FullScreen) if (startFullscreen && ViewModel.WindowState != WindowState.FullScreen)
{ {
ViewModel.ToggleFullscreen(); ViewModel.ToggleFullscreen();
} }
}); });
} }
private void GlRenderer_Created(object sender, EventArgs e)
{
ShowLoading();
_rendererWaitEvent.Set();
}
private void AppHost_AppExit(object sender, EventArgs e)
{
if (_isClosing)
{
return;
}
ViewModel.IsGameRunning = false;
Dispatcher.UIThread.InvokeAsync(() =>
{
ViewModel.ShowMenuAndStatusBar = true;
ViewModel.ShowContent = true;
ViewModel.ShowLoadProgress = false;
ViewModel.IsLoadingIndeterminate = false;
CanUpdate = true;
Cursor = Cursor.Default;
if (MainContent.Content != _mainViewContent)
{
MainContent.Content = _mainViewContent;
}
AppHost = null;
HandleRelaunch();
});
RendererControl.RendererInitialized -= GlRenderer_Created;
RendererControl = null;
ViewModel.SelectedIcon = null;
Dispatcher.UIThread.InvokeAsync(() =>
{
Title = $"Ryujinx {Program.Version}";
});
}
public void Sort_Checked(object sender, RoutedEventArgs args)
{
if (sender is RadioButton button)
{
var sort = Enum.Parse<ApplicationSort>(button.Tag.ToString());
ViewModel.Sort(sort);
}
}
protected override void HandleWindowStateChanged(WindowState state) protected override void HandleWindowStateChanged(WindowState state)
{ {
WindowState = state; ViewModel.WindowState = state;
if (state != WindowState.Minimized) if (state != WindowState.Minimized)
{ {
@@ -422,15 +226,6 @@ namespace Ryujinx.Ava.UI.Windows
} }
} }
public void Order_Checked(object sender, RoutedEventArgs args)
{
if (sender is RadioButton button)
{
var tag = button.Tag.ToString();
ViewModel.Sort(tag != "Descending");
}
}
private void Initialize() private void Initialize()
{ {
_userChannelPersistence = new UserChannelPersistence(); _userChannelPersistence = new UserChannelPersistence();
@@ -457,8 +252,6 @@ namespace Ryujinx.Ava.UI.Windows
VirtualFileSystem.ReloadKeySet(); VirtualFileSystem.ReloadKeySet();
ApplicationHelper.Initialize(VirtualFileSystem, AccountManager, LibHacHorizonManager.RyujinxClient, this); ApplicationHelper.Initialize(VirtualFileSystem, AccountManager, LibHacHorizonManager.RyujinxClient, this);
RefreshFirmwareStatus();
} }
protected void CheckLaunchState() protected void CheckLaunchState()
@@ -475,7 +268,7 @@ namespace Ryujinx.Ava.UI.Windows
{ {
_deferLoad = false; _deferLoad = false;
LoadApplication(_launchPath, _startFullscreen); ViewModel.LoadApplication(_launchPath, _startFullscreen);
} }
if (ConfigurationState.Instance.CheckUpdatesOnStart.Value && Updater.CanUpdate(false, this)) if (ConfigurationState.Instance.CheckUpdatesOnStart.Value && Updater.CanUpdate(false, this))
@@ -487,35 +280,9 @@ namespace Ryujinx.Ava.UI.Windows
} }
} }
public void RefreshFirmwareStatus()
{
SystemVersion version = null;
try
{
version = ContentManager.GetCurrentFirmwareVersion();
}
catch (Exception) { }
bool hasApplet = false;
if (version != null)
{
LocaleManager.Instance.UpdateDynamicValue(LocaleKeys.StatusBarSystemVersion,
version.VersionString);
hasApplet = version.Major > 3;
}
else
{
LocaleManager.Instance.UpdateDynamicValue(LocaleKeys.StatusBarSystemVersion, "0.0");
}
ViewModel.IsAppletMenuActive = hasApplet;
}
private void Load() private void Load()
{ {
VolumeStatus.Click += VolumeStatus_CheckedChanged; StatusBarView.VolumeStatus.Click += VolumeStatus_CheckedChanged;
GameGrid.ApplicationOpened += Application_Opened; GameGrid.ApplicationOpened += Application_Opened;
@@ -535,6 +302,19 @@ namespace Ryujinx.Ava.UI.Windows
CheckLaunchState(); CheckLaunchState();
} }
private void SetMainContent(Control content = null)
{
if (content == null)
{
content = GameLibrary;
}
if (MainContent.Content != content)
{
MainContent.Content = content;
}
}
public static void UpdateGraphicsConfig() public static void UpdateGraphicsConfig()
{ {
GraphicsConfig.ResScale = ConfigurationState.Instance.Graphics.ResScale == -1 ? ConfigurationState.Instance.Graphics.ResScaleCustom : ConfigurationState.Instance.Graphics.ResScale; GraphicsConfig.ResScale = ConfigurationState.Instance.Graphics.ResScale == -1 ? ConfigurationState.Instance.Graphics.ResScaleCustom : ConfigurationState.Instance.Graphics.ResScale;
@@ -553,99 +333,6 @@ namespace Ryujinx.Ava.UI.Windows
HotKeyManager.SetHotKey(ExitHotKey, new KeyGesture(Key.Escape)); HotKeyManager.SetHotKey(ExitHotKey, new KeyGesture(Key.Escape));
} }
public static void SaveConfig()
{
ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
}
public void UpdateGameMetadata(string titleId)
{
ApplicationLibrary.LoadAndSaveMetaData(titleId, appMetadata =>
{
if (DateTime.TryParse(appMetadata.LastPlayed, out DateTime lastPlayedDateTime))
{
double sessionTimePlayed = DateTime.UtcNow.Subtract(lastPlayedDateTime).TotalSeconds;
appMetadata.TimePlayed += Math.Round(sessionTimePlayed, MidpointRounding.AwayFromZero);
}
});
}
private void PrepareLoadScreen()
{
using MemoryStream stream = new MemoryStream(ViewModel.SelectedIcon);
using var gameIconBmp = SixLabors.ImageSharp.Image.Load<Bgra32>(stream);
var dominantColor = IconColorPicker.GetFilteredColor(gameIconBmp).ToPixel<Bgra32>();
const int ColorDivisor = 4;
Color progressFgColor = Color.FromRgb(dominantColor.R, dominantColor.G, dominantColor.B);
Color progressBgColor = Color.FromRgb(
(byte)(dominantColor.R / ColorDivisor),
(byte)(dominantColor.G / ColorDivisor),
(byte)(dominantColor.B / ColorDivisor));
ViewModel.ProgressBarForegroundColor = new SolidColorBrush(progressFgColor);
ViewModel.ProgressBarBackgroundColor = new SolidColorBrush(progressBgColor);
}
private void SearchBox_OnKeyUp(object sender, KeyEventArgs e)
{
ViewModel.SearchText = SearchBox.Text;
}
private async void StopEmulation_Click(object sender, RoutedEventArgs e)
{
if (AppHost != null)
{
await AppHost.ShowExitPrompt();
}
}
private async void PauseEmulation_Click(object sender, RoutedEventArgs e)
{
await Task.Run(() =>
{
AppHost?.Pause();
});
}
private async void ResumeEmulation_Click(object sender, RoutedEventArgs e)
{
await Task.Run(() =>
{
AppHost?.Resume();
});
}
private void ScanAmiiboMenuItem_AttachedToVisualTree(object sender, VisualTreeAttachmentEventArgs e)
{
if (sender is MenuItem)
{
ViewModel.IsAmiiboRequested = AppHost.Device.System.SearchingForAmiibo(out _);
}
}
private void VsyncStatus_PointerReleased(object sender, PointerReleasedEventArgs e)
{
AppHost.Device.EnableDeviceVsync = !AppHost.Device.EnableDeviceVsync;
Logger.Info?.Print(LogClass.Application, $"VSync toggled to: {AppHost.Device.EnableDeviceVsync}");
}
private void DockedStatus_PointerReleased(object sender, PointerReleasedEventArgs e)
{
ConfigurationState.Instance.System.EnableDockedMode.Value = !ConfigurationState.Instance.System.EnableDockedMode.Value;
}
private void AspectRatioStatus_PointerReleased(object sender, PointerReleasedEventArgs e)
{
AspectRatio aspectRatio = ConfigurationState.Instance.Graphics.AspectRatio.Value;
ConfigurationState.Instance.Graphics.AspectRatio.Value = (int)aspectRatio + 1 > Enum.GetNames(typeof(AspectRatio)).Length - 1 ? AspectRatio.Fixed4x3 : aspectRatio + 1;
}
private void VolumeStatus_CheckedChanged(object sender, SplitButtonClickEventArgs e) private void VolumeStatus_CheckedChanged(object sender, SplitButtonClickEventArgs e)
{ {
var volumeSplitButton = sender as ToggleSplitButton; var volumeSplitButton = sender as ToggleSplitButton;
@@ -653,20 +340,20 @@ namespace Ryujinx.Ava.UI.Windows
{ {
if (!volumeSplitButton.IsChecked) if (!volumeSplitButton.IsChecked)
{ {
AppHost.Device.SetVolume(ConfigurationState.Instance.System.AudioVolume); ViewModel.AppHost.Device.SetVolume(ConfigurationState.Instance.System.AudioVolume);
} }
else else
{ {
AppHost.Device.SetVolume(0); ViewModel.AppHost.Device.SetVolume(0);
} }
ViewModel.Volume = AppHost.Device.GetVolume(); ViewModel.Volume = ViewModel.AppHost.Device.GetVolume();
} }
} }
protected override void OnClosing(CancelEventArgs e) protected override void OnClosing(CancelEventArgs e)
{ {
if (!_isClosing && AppHost != null && ConfigurationState.Instance.ShowConfirmExit) if (!ViewModel.IsClosing && ViewModel.AppHost != null && ConfigurationState.Instance.ShowConfirmExit)
{ {
e.Cancel = true; e.Cancel = true;
@@ -675,14 +362,14 @@ namespace Ryujinx.Ava.UI.Windows
return; return;
} }
_isClosing = true; ViewModel.IsClosing = true;
if (AppHost != null) if (ViewModel.AppHost != null)
{ {
AppHost.AppExit -= AppHost_AppExit; ViewModel.AppHost.AppExit -= ViewModel.AppHost_AppExit;
AppHost.AppExit += (sender, e) => ViewModel.AppHost.AppExit += (sender, e) =>
{ {
AppHost = null; ViewModel.AppHost = null;
Dispatcher.UIThread.Post(() => Dispatcher.UIThread.Post(() =>
{ {
@@ -691,7 +378,7 @@ namespace Ryujinx.Ava.UI.Windows
Close(); Close();
}); });
}; };
AppHost?.Stop(); ViewModel.AppHost?.Stop();
e.Cancel = true; e.Cancel = true;
@@ -709,13 +396,43 @@ namespace Ryujinx.Ava.UI.Windows
{ {
Dispatcher.UIThread.InvokeAsync(async () => Dispatcher.UIThread.InvokeAsync(async () =>
{ {
_isClosing = await ContentDialogHelper.CreateExitDialog(); ViewModel.IsClosing = await ContentDialogHelper.CreateExitDialog();
if (_isClosing) if (ViewModel.IsClosing)
{ {
Close(); Close();
} }
}); });
} }
public async void LoadApplications()
{
await Dispatcher.UIThread.InvokeAsync(() =>
{
ViewModel.Applications.Clear();
StatusBarView.LoadProgressBar.IsVisible = true;
ViewModel.StatusBarProgressMaximum = 0;
ViewModel.StatusBarProgressValue = 0;
LocaleManager.Instance.UpdateDynamicValue(LocaleKeys.StatusBarGamesLoaded, 0, 0);
});
ReloadGameList();
}
private void ReloadGameList()
{
if (_isLoading)
{
return;
}
_isLoading = true;
ApplicationLibrary.LoadApplications(ConfigurationState.Instance.Ui.GameDirs.Value, ConfigurationState.Instance.System.Language);
_isLoading = false;
}
} }
} }

View File

@@ -7,9 +7,9 @@ namespace Ryujinx.Common.Logging
AudioRenderer, AudioRenderer,
Configuration, Configuration,
Cpu, Cpu,
Font,
Emulation, Emulation,
FFmpeg, FFmpeg,
Font,
Gpu, Gpu,
Hid, Hid,
Host1x, Host1x,
@@ -66,6 +66,7 @@ namespace Ryujinx.Common.Logging
ServiceVi, ServiceVi,
SurfaceFlinger, SurfaceFlinger,
TamperMachine, TamperMachine,
Ui,
Vic Vic
} }
} }

View File

@@ -123,6 +123,8 @@ namespace Ryujinx.HLE.HOS
internal LibHacHorizonManager LibHacHorizonManager { get; private set; } internal LibHacHorizonManager LibHacHorizonManager { get; private set; }
internal ServiceTable ServiceTable { get; private set; }
public bool IsPaused { get; private set; } public bool IsPaused { get; private set; }
public Horizon(Switch device) public Horizon(Switch device)
@@ -326,6 +328,7 @@ namespace Ryujinx.HLE.HOS
private void StartNewServices() private void StartNewServices()
{ {
ServiceTable = new ServiceTable();
var services = ServiceTable.GetServices(new HorizonOptions(Device.Configuration.IgnoreMissingServices)); var services = ServiceTable.GetServices(new HorizonOptions(Device.Configuration.IgnoreMissingServices));
foreach (var service in services) foreach (var service in services)

View File

@@ -182,6 +182,8 @@ namespace Ryujinx.HLE.HOS
byte[] arguments = null, byte[] arguments = null,
params IExecutable[] executables) params IExecutable[] executables)
{ {
context.Device.System.ServiceTable.WaitServicesReady();
LibHac.Result rc = metaData.GetNpdm(out var npdm); LibHac.Result rc = metaData.GetNpdm(out var npdm);
if (rc.IsFailure()) if (rc.IsFailure())

View File

@@ -1,19 +0,0 @@
using Ryujinx.HLE.HOS.Services.Lm.LogService;
namespace Ryujinx.HLE.HOS.Services.Lm
{
[Service("lm")]
class ILogService : IpcService
{
public ILogService(ServiceCtx context) { }
[CommandHipc(0)]
// Initialize(u64, pid) -> object<nn::lm::ILogger>
public ResultCode Initialize(ServiceCtx context)
{
MakeObject(context, new ILogger());
return ResultCode.Success;
}
}
}

View File

@@ -1,109 +0,0 @@
using Ryujinx.Common.Logging;
using System.IO;
using System.Text;
namespace Ryujinx.HLE.HOS.Services.Lm.LogService
{
class ILogger : IpcService
{
public ILogger() { }
[CommandHipc(0)]
// Log(buffer<unknown, 0x21>)
public ResultCode Log(ServiceCtx context)
{
Logger.Guest?.Print(LogClass.ServiceLm, LogImpl(context));
return ResultCode.Success;
}
private string LogImpl(ServiceCtx context)
{
(ulong bufPos, ulong bufSize) = context.Request.GetBufferType0x21();
byte[] logBuffer = new byte[bufSize];
context.Memory.Read(bufPos, logBuffer);
using MemoryStream ms = new MemoryStream(logBuffer);
BinaryReader reader = new BinaryReader(ms);
long pid = reader.ReadInt64();
long threadContext = reader.ReadInt64();
short flags = reader.ReadInt16();
byte level = reader.ReadByte();
byte verbosity = reader.ReadByte();
int payloadLength = reader.ReadInt32();
StringBuilder sb = new StringBuilder();
sb.AppendLine($"Guest Log:\n Log level: {(LmLogLevel)level}");
while (ms.Position < ms.Length)
{
int type = ReadEncodedInt(reader);
int size = ReadEncodedInt(reader);
LmLogField field = (LmLogField)type;
string fieldStr = string.Empty;
if (field == LmLogField.Start)
{
reader.ReadBytes(size);
continue;
}
else if (field == LmLogField.Stop)
{
break;
}
else if (field == LmLogField.Line)
{
fieldStr = $"{field}: {reader.ReadInt32()}";
}
else if (field == LmLogField.DropCount)
{
fieldStr = $"{field}: {reader.ReadInt64()}";
}
else if (field == LmLogField.Time)
{
fieldStr = $"{field}: {reader.ReadInt64()}s";
}
else if (field < LmLogField.Count)
{
fieldStr = $"{field}: '{Encoding.UTF8.GetString(reader.ReadBytes(size)).TrimEnd()}'";
}
else
{
fieldStr = $"Field{field}: '{Encoding.UTF8.GetString(reader.ReadBytes(size)).TrimEnd()}'";
}
sb.AppendLine($" {fieldStr}");
}
return sb.ToString();
}
private static int ReadEncodedInt(BinaryReader reader)
{
int result = 0;
int position = 0;
byte encoded;
do
{
encoded = reader.ReadByte();
result += (encoded & 0x7F) << (7 * position);
position++;
} while ((encoded & 0x80) != 0);
return result;
}
}
}

View File

@@ -1,18 +0,0 @@
namespace Ryujinx.HLE.HOS.Services.Lm.LogService
{
enum LmLogField
{
Start = 0,
Stop = 1,
Message = 2,
Line = 3,
Filename = 4,
Function = 5,
Module = 6,
Thread = 7,
DropCount = 8,
Time = 9,
ProgramName = 10,
Count
}
}

View File

@@ -1,11 +0,0 @@
namespace Ryujinx.HLE.HOS.Services.Lm.LogService
{
enum LmLogLevel
{
Trace,
Info,
Warning,
Error,
Critical
}
}

View File

@@ -1,182 +0,0 @@
using MsgPack;
using MsgPack.Serialization;
using Ryujinx.Common;
using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities;
using Ryujinx.HLE.HOS.Services.Account.Acc;
using Ryujinx.HLE.Utilities;
using System;
using System.Text;
namespace Ryujinx.HLE.HOS.Services.Prepo
{
[Service("prepo:a", PrepoServicePermissionLevel.Admin)] // 1.0.0-5.1.0
[Service("prepo:a2", PrepoServicePermissionLevel.Admin)] // 6.0.0+
[Service("prepo:m", PrepoServicePermissionLevel.Manager)]
[Service("prepo:u", PrepoServicePermissionLevel.User)]
[Service("prepo:s", PrepoServicePermissionLevel.System)]
class IPrepoService : IpcService
{
private PrepoServicePermissionLevel _permission;
private ulong _systemSessionId;
public IPrepoService(ServiceCtx context, PrepoServicePermissionLevel permission)
{
_permission = permission;
}
[CommandHipc(10100)] // 1.0.0-5.1.0
[CommandHipc(10102)] // 6.0.0-9.2.0
[CommandHipc(10104)] // 10.0.0+
// SaveReport(u64, pid, buffer<u8, 9>, buffer<bytes, 5>)
public ResultCode SaveReport(ServiceCtx context)
{
if ((_permission & PrepoServicePermissionLevel.User) == 0)
{
return ResultCode.PermissionDenied;
}
// We don't care about the differences since we don't use the play report.
return ProcessReport(context, withUserID: false);
}
[CommandHipc(10101)] // 1.0.0-5.1.0
[CommandHipc(10103)] // 6.0.0-9.2.0
[CommandHipc(10105)] // 10.0.0+
// SaveReportWithUser(nn::account::Uid, u64, pid, buffer<u8, 9>, buffer<bytes, 5>)
public ResultCode SaveReportWithUser(ServiceCtx context)
{
if ((_permission & PrepoServicePermissionLevel.User) == 0)
{
return ResultCode.PermissionDenied;
}
// We don't care about the differences since we don't use the play report.
return ProcessReport(context, withUserID: true);
}
[CommandHipc(10200)]
// RequestImmediateTransmission()
public ResultCode RequestImmediateTransmission(ServiceCtx context)
{
// It signals an event of nn::prepo::detail::service::core::TransmissionStatusManager that requests the transmission of the report.
// Since we don't use reports it's fine to do nothing.
return ResultCode.Success;
}
[CommandHipc(10300)]
// GetTransmissionStatus() -> u32
public ResultCode GetTransmissionStatus(ServiceCtx context)
{
// It returns the transmission result of nn::prepo::detail::service::core::TransmissionStatusManager.
// Since we don't use reports it's fine to return ResultCode.Success.
context.ResponseData.Write((int)ResultCode.Success);
return ResultCode.Success;
}
[CommandHipc(10400)] // 9.0.0+
// GetSystemSessionId() -> u64
public ResultCode GetSystemSessionId(ServiceCtx context)
{
if ((_permission & PrepoServicePermissionLevel.User) == 0)
{
return ResultCode.PermissionDenied;
}
if (_systemSessionId == 0)
{
byte[] randomBuffer = new byte[8];
Random.Shared.NextBytes(randomBuffer);
_systemSessionId = BitConverter.ToUInt64(randomBuffer, 0);
}
context.ResponseData.Write(_systemSessionId);
return ResultCode.Success;
}
[CommandHipc(20100)]
// SaveSystemReport(u64, pid, buffer<u8, 9>, buffer<bytes, 5>)
public ResultCode SaveSystemReport(ServiceCtx context)
{
if ((_permission & PrepoServicePermissionLevel.System) != 0)
{
return ResultCode.PermissionDenied;
}
// We don't care about the differences since we don't use the play report.
return ProcessReport(context, withUserID: false);
}
[CommandHipc(20101)]
// SaveSystemReportWithUser(nn::account::Uid, u64, pid, buffer<u8, 9>, buffer<bytes, 5>)
public ResultCode SaveSystemReportWithUser(ServiceCtx context)
{
if ((_permission & PrepoServicePermissionLevel.System) != 0)
{
return ResultCode.PermissionDenied;
}
// We don't care about the differences since we don't use the play report.
return ProcessReport(context, withUserID: true);
}
private ResultCode ProcessReport(ServiceCtx context, bool withUserID)
{
UserId userId = withUserID ? context.RequestData.ReadStruct<UserId>() : new UserId();
string gameRoom = StringUtils.ReadUtf8String(context);
if (withUserID)
{
if (userId.IsNull)
{
return ResultCode.InvalidArgument;
}
}
if (gameRoom == string.Empty)
{
return ResultCode.InvalidState;
}
ulong inputPosition = context.Request.SendBuff[0].Position;
ulong inputSize = context.Request.SendBuff[0].Size;
if (inputSize == 0)
{
return ResultCode.InvalidBufferSize;
}
byte[] inputBuffer = new byte[inputSize];
context.Memory.Read(inputPosition, inputBuffer);
Logger.Info?.Print(LogClass.ServicePrepo, ReadReportBuffer(inputBuffer, gameRoom, userId));
return ResultCode.Success;
}
private string ReadReportBuffer(byte[] buffer, string room, UserId userId)
{
StringBuilder builder = new StringBuilder();
MessagePackObject deserializedReport = MessagePackSerializer.UnpackMessagePackObject(buffer);
builder.AppendLine();
builder.AppendLine("PlayReport log:");
if (!userId.IsNull)
{
builder.AppendLine($" UserId: {userId}");
}
builder.AppendLine($" Room: {room}");
builder.AppendLine($" Report: {MessagePackObjectFormatter.Format(deserializedReport)}");
return builder.ToString();
}
}
}

View File

@@ -1,15 +0,0 @@
namespace Ryujinx.HLE.HOS.Services.Prepo
{
enum ResultCode
{
ModuleId = 129,
ErrorCodeShift = 9,
Success = 0,
InvalidArgument = (1 << ErrorCodeShift) | ModuleId,
InvalidState = (5 << ErrorCodeShift) | ModuleId,
InvalidBufferSize = (9 << ErrorCodeShift) | ModuleId,
PermissionDenied = (90 << ErrorCodeShift) | ModuleId
}
}

View File

@@ -180,7 +180,7 @@ namespace Ryujinx.HLE.HOS.Services.Sm
return ResultCode.InvalidName; return ResultCode.InvalidName;
} }
Logger.Info?.Print(LogClass.ServiceSm, $"Register \"{name}\"."); Logger.Debug?.Print(LogClass.ServiceSm, $"Register \"{name}\".");
KPort port = new KPort(context.Device.System.KernelContext, maxSessions, isLight, null); KPort port = new KPort(context.Device.System.KernelContext, maxSessions, isLight, null);

View File

@@ -100,14 +100,6 @@ namespace Ryujinx.Horizon.Common
} }
} }
public void AbortOnFailureUnless(Result result, Result result2)
{
if (this != Success && this != result && this != result2)
{
ThrowInvalidResult();
}
}
private void ThrowInvalidResult() private void ThrowInvalidResult()
{ {
throw new InvalidResultException(this); throw new InvalidResultException(this);

View File

@@ -123,44 +123,51 @@ namespace Ryujinx.Horizon.Generators.Hipc
{ {
string[] args = new string[method.ParameterList.Parameters.Count]; string[] args = new string[method.ParameterList.Parameters.Count];
int index = 0; if (args.Length == 0)
foreach (var parameter in method.ParameterList.Parameters)
{ {
string canonicalTypeName = GetCanonicalTypeNameWithGenericArguments(compilation, parameter.Type); generator.AppendLine($"{{ {commandId}, new CommandHandler({method.Identifier.Text}, Array.Empty<CommandArg>()) }},");
CommandArgType argType = GetCommandArgType(compilation, parameter); }
else
{
int index = 0;
string arg; foreach (var parameter in method.ParameterList.Parameters)
if (argType == CommandArgType.Buffer)
{ {
string bufferFlags = GetFirstAttributeAgument(compilation, parameter, TypeBufferAttribute, 0); string canonicalTypeName = GetCanonicalTypeNameWithGenericArguments(compilation, parameter.Type);
string bufferFixedSize = GetFirstAttributeAgument(compilation, parameter, TypeBufferAttribute, 1); CommandArgType argType = GetCommandArgType(compilation, parameter);
if (bufferFixedSize != null) string arg;
if (argType == CommandArgType.Buffer)
{ {
arg = $"new CommandArg({bufferFlags}, {bufferFixedSize})"; string bufferFlags = GetFirstAttributeAgument(compilation, parameter, TypeBufferAttribute, 0);
string bufferFixedSize = GetFirstAttributeAgument(compilation, parameter, TypeBufferAttribute, 1);
if (bufferFixedSize != null)
{
arg = $"new CommandArg({bufferFlags}, {bufferFixedSize})";
}
else
{
arg = $"new CommandArg({bufferFlags})";
}
}
else if (argType == CommandArgType.InArgument || argType == CommandArgType.OutArgument)
{
string alignment = GetTypeAlignmentExpression(compilation, parameter.Type);
arg = $"new CommandArg(CommandArgType.{argType}, Unsafe.SizeOf<{canonicalTypeName}>(), {alignment})";
} }
else else
{ {
arg = $"new CommandArg({bufferFlags})"; arg = $"new CommandArg(CommandArgType.{argType})";
} }
}
else if (argType == CommandArgType.InArgument || argType == CommandArgType.OutArgument)
{
string alignment = GetTypeAlignmentExpression(compilation, parameter.Type);
arg = $"new CommandArg(CommandArgType.{argType}, Unsafe.SizeOf<{canonicalTypeName}>(), {alignment})"; args[index++] = arg;
}
else
{
arg = $"new CommandArg(CommandArgType.{argType})";
} }
args[index++] = arg; generator.AppendLine($"{{ {commandId}, new CommandHandler({method.Identifier.Text}, {string.Join(", ", args)}) }},");
} }
generator.AppendLine($"{{ {commandId}, new CommandHandler({method.Identifier.Text}, {string.Join(", ", args)}) }},");
} }
} }

View File

@@ -12,12 +12,12 @@ namespace Ryujinx.Horizon
private struct Range : IComparable<Range> private struct Range : IComparable<Range>
{ {
public ulong Offset { get; } public ulong Offset { get; }
public ulong Size { get; } public ulong Size { get; }
public Range(ulong offset, ulong size) public Range(ulong offset, ulong size)
{ {
Offset = offset; Offset = offset;
Size = size; Size = size;
} }
public int CompareTo(Range other) public int CompareTo(Range other)
@@ -31,7 +31,7 @@ namespace Ryujinx.Horizon
public HeapAllocator() public HeapAllocator()
{ {
_freeRanges = new List<Range>(); _freeRanges = new List<Range>();
_currentHeapSize = 0; _currentHeapSize = 0;
} }
@@ -70,8 +70,8 @@ namespace Ryujinx.Horizon
var range = _freeRanges[i]; var range = _freeRanges[i];
ulong alignedOffset = BitUtils.AlignUp(range.Offset, alignment); ulong alignedOffset = BitUtils.AlignUp(range.Offset, alignment);
ulong sizeDelta = alignedOffset - range.Offset; ulong sizeDelta = alignedOffset - range.Offset;
ulong usableSize = range.Size - sizeDelta; ulong usableSize = range.Size - sizeDelta;
if (sizeDelta < range.Size && usableSize >= size) if (sizeDelta < range.Size && usableSize >= size)
{ {
@@ -82,7 +82,7 @@ namespace Ryujinx.Horizon
InsertFreeRange(range.Offset, sizeDelta); InsertFreeRange(range.Offset, sizeDelta);
} }
ulong endOffset = range.Offset + range.Size; ulong endOffset = range.Offset + range.Size;
ulong remainingSize = endOffset - (alignedOffset + size); ulong remainingSize = endOffset - (alignedOffset + size);
if (remainingSize != 0) if (remainingSize != 0)
{ {

View File

@@ -2,11 +2,13 @@ namespace Ryujinx.Horizon
{ {
public struct HorizonOptions public struct HorizonOptions
{ {
public bool IgnoreMissingServices { get; } public bool IgnoreMissingServices { get; }
public bool ThrowOnInvalidCommandIds { get; }
public HorizonOptions(bool ignoreMissingServices) public HorizonOptions(bool ignoreMissingServices)
{ {
IgnoreMissingServices = ignoreMissingServices; IgnoreMissingServices = ignoreMissingServices;
ThrowOnInvalidCommandIds = true;
} }
} }
} }

View File

@@ -21,24 +21,24 @@ namespace Ryujinx.Horizon
[ThreadStatic] [ThreadStatic]
private static int _threadHandle; private static int _threadHandle;
public static HorizonOptions Options => _options; public static HorizonOptions Options => _options;
public static ISyscallApi Syscall => _syscall; public static ISyscallApi Syscall => _syscall;
public static IVirtualMemoryManager AddressSpace => _addressSpace; public static IVirtualMemoryManager AddressSpace => _addressSpace;
public static IThreadContext ThreadContext => _threadContext; public static IThreadContext ThreadContext => _threadContext;
public static int CurrentThreadHandle => _threadHandle; public static int CurrentThreadHandle => _threadHandle;
public static void Register( public static void Register(
HorizonOptions options, HorizonOptions options,
ISyscallApi syscallApi, ISyscallApi syscallApi,
IVirtualMemoryManager addressSpace, IVirtualMemoryManager addressSpace,
IThreadContext threadContext, IThreadContext threadContext,
int threadHandle) int threadHandle)
{ {
_options = options; _options = options;
_syscall = syscallApi; _syscall = syscallApi;
_addressSpace = addressSpace; _addressSpace = addressSpace;
_threadContext = threadContext; _threadContext = threadContext;
_threadHandle = threadHandle; _threadHandle = threadHandle;
} }
} }
} }

View File

@@ -2,6 +2,6 @@
{ {
interface IService interface IService
{ {
abstract static void Main(); abstract static void Main(ServiceTable serviceTable);
} }
} }

View File

@@ -9,23 +9,23 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text; using System.Text;
namespace Ryujinx.Horizon.LogManager namespace Ryujinx.Horizon.LogManager.Ipc
{ {
partial class LmLogger : IServiceObject partial class LmLogger : ILmLogger
{ {
private readonly LmLog _log; private readonly LogService _log;
private readonly ulong _clientProcessId; private readonly ulong _pid;
public LmLogger(LmLog log, ulong clientProcessId) public LmLogger(LogService log, ulong pid)
{ {
_log = log; _log = log;
_clientProcessId = clientProcessId; _pid = pid;
} }
[CmifCommand(0)] [CmifCommand(0)]
public Result Log([Buffer(HipcBufferFlags.In | HipcBufferFlags.AutoSelect)] Span<byte> message) public Result Log([Buffer(HipcBufferFlags.In | HipcBufferFlags.AutoSelect)] Span<byte> message)
{ {
if (!SetProcessId(message, _clientProcessId)) if (!SetProcessId(message, _pid))
{ {
return Result.Success; return Result.Success;
} }
@@ -35,7 +35,7 @@ namespace Ryujinx.Horizon.LogManager
return Result.Success; return Result.Success;
} }
[CmifCommand(1)] [CmifCommand(1)] // 3.0.0+
public Result SetDestination(LogDestination destination) public Result SetDestination(LogDestination destination)
{ {
_log.LogDestination = destination; _log.LogDestination = destination;
@@ -48,7 +48,6 @@ namespace Ryujinx.Horizon.LogManager
ref LogPacketHeader header = ref MemoryMarshal.Cast<byte, LogPacketHeader>(message)[0]; ref LogPacketHeader header = ref MemoryMarshal.Cast<byte, LogPacketHeader>(message)[0];
uint expectedMessageSize = (uint)Unsafe.SizeOf<LogPacketHeader>() + header.PayloadSize; uint expectedMessageSize = (uint)Unsafe.SizeOf<LogPacketHeader>() + header.PayloadSize;
if (expectedMessageSize != (uint)message.Length) if (expectedMessageSize != (uint)message.Length)
{ {
Logger.Warning?.Print(LogClass.ServiceLm, $"Invalid message size (expected 0x{expectedMessageSize:X} but got 0x{message.Length:X})."); Logger.Warning?.Print(LogClass.ServiceLm, $"Invalid message size (expected 0x{expectedMessageSize:X} but got 0x{message.Length:X}).");
@@ -63,13 +62,11 @@ namespace Ryujinx.Horizon.LogManager
private static string LogImpl(ReadOnlySpan<byte> message) private static string LogImpl(ReadOnlySpan<byte> message)
{ {
SpanReader reader = new SpanReader(message); SpanReader reader = new(message);
LogPacketHeader header = reader.Read<LogPacketHeader>();
StringBuilder builder = new();
LogPacketHeader header = reader.Read<LogPacketHeader>(); builder.AppendLine($"Guest Log:\n Log level: {header.Severity}");
StringBuilder sb = new StringBuilder();
sb.AppendLine($"Guest Log:\n Log level: {header.Severity}");
while (reader.Length > 0) while (reader.Length > 0)
{ {
@@ -78,7 +75,7 @@ namespace Ryujinx.Horizon.LogManager
LogDataChunkKey field = (LogDataChunkKey)type; LogDataChunkKey field = (LogDataChunkKey)type;
string fieldStr = string.Empty; string fieldStr;
if (field == LogDataChunkKey.Start) if (field == LogDataChunkKey.Start)
{ {
@@ -111,16 +108,16 @@ namespace Ryujinx.Horizon.LogManager
fieldStr = $"Field{field}: '{Encoding.UTF8.GetString(reader.GetSpan(size)).TrimEnd()}'"; fieldStr = $"Field{field}: '{Encoding.UTF8.GetString(reader.GetSpan(size)).TrimEnd()}'";
} }
sb.AppendLine($" {fieldStr}"); builder.AppendLine($" {fieldStr}");
} }
return sb.ToString(); return builder.ToString();
} }
private static int ReadUleb128(ref SpanReader reader) private static int ReadUleb128(ref SpanReader reader)
{ {
int result = 0; int result = 0;
int count = 0; int count = 0;
byte encoded; byte encoded;

View File

@@ -2,16 +2,17 @@
using Ryujinx.Horizon.Sdk.Lm; using Ryujinx.Horizon.Sdk.Lm;
using Ryujinx.Horizon.Sdk.Sf; using Ryujinx.Horizon.Sdk.Sf;
namespace Ryujinx.Horizon.LogManager namespace Ryujinx.Horizon.LogManager.Ipc
{ {
partial class LmLog : IServiceObject partial class LogService : ILogService
{ {
public LogDestination LogDestination { get; set; } = LogDestination.TargetManager; public LogDestination LogDestination { get; set; } = LogDestination.TargetManager;
[CmifCommand(0)] [CmifCommand(0)]
public Result OpenLogger(out LmLogger logger, [ClientProcessId] ulong clientProcessId) public Result OpenLogger(out LmLogger logger, [ClientProcessId] ulong pid)
{ {
logger = new LmLogger(this, clientProcessId); // NOTE: Internal name is Logger, but we rename it LmLogger to avoid name clash with Ryujinx.Common.Logging logger.
logger = new LmLogger(this, pid);
return Result.Success; return Result.Success;
} }

View File

@@ -1,6 +1,6 @@
using Ryujinx.Horizon.Sdk.Sf.Hipc; using Ryujinx.Horizon.LogManager.Ipc;
using Ryujinx.Horizon.Sdk.Sf.Hipc;
using Ryujinx.Horizon.Sdk.Sm; using Ryujinx.Horizon.Sdk.Sm;
using Ryujinx.Horizon.Sm;
namespace Ryujinx.Horizon.LogManager namespace Ryujinx.Horizon.LogManager
{ {
@@ -9,36 +9,25 @@ namespace Ryujinx.Horizon.LogManager
private const int LogMaxSessionsCount = 42; private const int LogMaxSessionsCount = 42;
private const int PointerBufferSize = 0x400; private const int PointerBufferSize = 0x400;
private const int MaxDomains = 31; private const int MaxDomains = 31;
private const int MaxDomainObjects = 61; private const int MaxDomainObjects = 61;
private const int MaxPortsCount = 1;
private const int MaxPortsCount = 1; private static readonly ManagerOptions _logManagerOptions = new(PointerBufferSize, MaxDomains, MaxDomainObjects, false);
private static readonly ManagerOptions _logManagerOptions = new ManagerOptions( private SmApi _sm;
PointerBufferSize,
MaxDomains,
MaxDomainObjects,
false);
private static readonly ServiceName _logServiceName = ServiceName.Encode("lm");
private SmApi _sm;
private ServerManager _serverManager; private ServerManager _serverManager;
private LmLog _logServiceObject;
public void Initialize() public void Initialize()
{ {
HeapAllocator allocator = new HeapAllocator(); HeapAllocator allocator = new();
_sm = new SmApi(); _sm = new SmApi();
_sm.Initialize().AbortOnFailure(); _sm.Initialize().AbortOnFailure();
_serverManager = new ServerManager(allocator, _sm, MaxPortsCount, _logManagerOptions, LogMaxSessionsCount); _serverManager = new ServerManager(allocator, _sm, MaxPortsCount, _logManagerOptions, LogMaxSessionsCount);
_logServiceObject = new LmLog(); _serverManager.RegisterObjectForServer(new LogService(), ServiceName.Encode("lm"), LogMaxSessionsCount);
_serverManager.RegisterObjectForServer(_logServiceObject, _logServiceName, LogMaxSessionsCount);
} }
public void ServiceRequests() public void ServiceRequests()

View File

@@ -2,11 +2,14 @@
{ {
class LmMain : IService class LmMain : IService
{ {
public static void Main() public static void Main(ServiceTable serviceTable)
{ {
LmIpcServer ipcServer = new LmIpcServer(); LmIpcServer ipcServer = new();
ipcServer.Initialize(); ipcServer.Initialize();
serviceTable.SignalServiceReady();
ipcServer.ServiceRequests(); ipcServer.ServiceRequests();
ipcServer.Shutdown(); ipcServer.Shutdown();
} }

View File

@@ -0,0 +1,218 @@
using MsgPack;
using MsgPack.Serialization;
using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities;
using Ryujinx.Horizon.Common;
using Ryujinx.Horizon.Prepo.Types;
using Ryujinx.Horizon.Sdk.Account;
using Ryujinx.Horizon.Sdk.Prepo;
using Ryujinx.Horizon.Sdk.Sf;
using Ryujinx.Horizon.Sdk.Sf.Hipc;
using System;
using System.Text;
namespace Ryujinx.Horizon.Prepo.Ipc
{
partial class PrepoService : IPrepoService
{
enum PlayReportKind
{
Normal,
System
}
private readonly PrepoServicePermissionLevel _permissionLevel;
private ulong _systemSessionId;
private bool _immediateTransmissionEnabled = false;
private bool _userAgreementCheckEnabled = true;
public PrepoService(PrepoServicePermissionLevel permissionLevel)
{
_permissionLevel = permissionLevel;
}
[CmifCommand(10100)] // 1.0.0-5.1.0
[CmifCommand(10102)] // 6.0.0-9.2.0
[CmifCommand(10104)] // 10.0.0+
public Result SaveReport([Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer)] ReadOnlySpan<byte> gameRoomBuffer, [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<byte> reportBuffer, [ClientProcessId] ulong pid)
{
if ((_permissionLevel & PrepoServicePermissionLevel.User) == 0)
{
return PrepoResult.PermissionDenied;
}
ProcessPlayReport(PlayReportKind.Normal, pid, gameRoomBuffer, reportBuffer, Uid.Null);
return Result.Success;
}
[CmifCommand(10101)] // 1.0.0-5.1.0
[CmifCommand(10103)] // 6.0.0-9.2.0
[CmifCommand(10105)] // 10.0.0+
public Result SaveReportWithUser(Uid userId, [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer)] ReadOnlySpan<byte> gameRoomBuffer, [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<byte> reportBuffer, [ClientProcessId] ulong pid)
{
if ((_permissionLevel & PrepoServicePermissionLevel.User) == 0)
{
return PrepoResult.PermissionDenied;
}
ProcessPlayReport(PlayReportKind.Normal, pid, gameRoomBuffer, reportBuffer, userId, true);
return Result.Success;
}
[CmifCommand(10200)]
public Result RequestImmediateTransmission()
{
_immediateTransmissionEnabled = true;
// It signals an event of nn::prepo::detail::service::core::TransmissionStatusManager that requests the transmission of the report.
// Since we don't use reports, it's fine to do nothing.
return Result.Success;
}
[CmifCommand(10300)]
public Result GetTransmissionStatus(out int status)
{
status = 0;
if (_immediateTransmissionEnabled && _userAgreementCheckEnabled)
{
status = 1;
}
return Result.Success;
}
[CmifCommand(10400)] // 9.0.0+
public Result GetSystemSessionId(out ulong systemSessionId)
{
systemSessionId = default;
if ((_permissionLevel & PrepoServicePermissionLevel.User) == 0)
{
return PrepoResult.PermissionDenied;
}
if (_systemSessionId == 0)
{
_systemSessionId = (ulong)Random.Shared.NextInt64();
}
systemSessionId = _systemSessionId;
return Result.Success;
}
[CmifCommand(20100)]
public Result SaveSystemReport([Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer)] ReadOnlySpan<byte> gameRoomBuffer, [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<byte> reportBuffer, [ClientProcessId] ulong pid)
{
if ((_permissionLevel & PrepoServicePermissionLevel.System) != 0)
{
return PrepoResult.PermissionDenied;
}
return ProcessPlayReport(PlayReportKind.System, pid, gameRoomBuffer, reportBuffer, Uid.Null);
}
[CmifCommand(20101)]
public Result SaveSystemReportWithUser(Uid userId, [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer)] ReadOnlySpan<byte> gameRoomBuffer, [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<byte> reportBuffer, [ClientProcessId] ulong pid)
{
if ((_permissionLevel & PrepoServicePermissionLevel.System) != 0)
{
return PrepoResult.PermissionDenied;
}
return ProcessPlayReport(PlayReportKind.System, pid, gameRoomBuffer, reportBuffer, userId, true);
}
[CmifCommand(40100)] // 2.0.0+
public Result IsUserAgreementCheckEnabled(out bool enabled)
{
enabled = false;
if (_permissionLevel == PrepoServicePermissionLevel.User || _permissionLevel == PrepoServicePermissionLevel.System)
{
enabled = _userAgreementCheckEnabled;
// If "enabled" is false, it sets some internal fields to 0.
// Then, it mounts "prepo-sys:/is_user_agreement_check_enabled.bin" and returns the contained bool.
// We can return the private bool instead, we don't care about the agreement since we don't send reports.
return Result.Success;
}
return PrepoResult.PermissionDenied;
}
[CmifCommand(40101)] // 2.0.0+
public Result SetUserAgreementCheckEnabled(bool enabled)
{
if (_permissionLevel == PrepoServicePermissionLevel.User || _permissionLevel == PrepoServicePermissionLevel.System)
{
_userAgreementCheckEnabled = enabled;
// If "enabled" is false, it sets some internal fields to 0.
// Then, it mounts "prepo-sys:/is_user_agreement_check_enabled.bin" and stores the "enabled" value.
// We can store in the private bool instead, we don't care about the agreement since we don't send reports.
return Result.Success;
}
return PrepoResult.PermissionDenied;
}
private static Result ProcessPlayReport(PlayReportKind playReportKind, ulong pid, ReadOnlySpan<byte> gameRoomBuffer, ReadOnlySpan<byte> reportBuffer, Uid userId, bool withUserId = false)
{
if (withUserId)
{
if (userId.IsNull)
{
return PrepoResult.InvalidArgument;
}
}
if (gameRoomBuffer.Length > 31)
{
return PrepoResult.InvalidArgument;
}
string gameRoom = Encoding.UTF8.GetString(gameRoomBuffer).TrimEnd();
if (gameRoom == string.Empty)
{
return PrepoResult.InvalidState;
}
if (reportBuffer.Length == 0)
{
return PrepoResult.InvalidBufferSize;
}
// NOTE: The service calls arp:r using the pid to get the application id, if it fails PrepoResult.InvalidPid is returned.
// Reports are stored internally and an event is signaled to transmit them.
StringBuilder builder = new();
MessagePackObject deserializedReport = MessagePackSerializer.UnpackMessagePackObject(reportBuffer.ToArray());
builder.AppendLine();
builder.AppendLine("PlayReport log:");
builder.AppendLine($" Kind: {playReportKind}");
builder.AppendLine($" Pid: {pid}");
if (!userId.IsNull)
{
builder.AppendLine($" UserId: {userId}");
}
builder.AppendLine($" Room: {gameRoom}");
builder.AppendLine($" Report: {MessagePackObjectFormatter.Format(deserializedReport)}");
Logger.Info?.Print(LogClass.ServicePrepo, builder.ToString());
return Result.Success;
}
}
}

View File

@@ -0,0 +1,49 @@
using Ryujinx.Horizon.Prepo.Types;
using Ryujinx.Horizon.Sdk.Sf.Hipc;
using Ryujinx.Horizon.Sdk.Sm;
namespace Ryujinx.Horizon.Prepo
{
class PrepoIpcServer
{
private const int PrepoMaxSessionsCount = 12;
private const int PrepoTotalMaxSessionsCount = PrepoMaxSessionsCount * 6;
private const int PointerBufferSize = 0x3800;
private const int MaxDomains = 64;
private const int MaxDomainObjects = 16;
private const int MaxPortsCount = 6;
private static readonly ManagerOptions _logManagerOptions = new(PointerBufferSize, MaxDomains, MaxDomainObjects, false);
private SmApi _sm;
private PrepoServerManager _serverManager;
public void Initialize()
{
HeapAllocator allocator = new();
_sm = new SmApi();
_sm.Initialize().AbortOnFailure();
_serverManager = new PrepoServerManager(allocator, _sm, MaxPortsCount, _logManagerOptions, PrepoTotalMaxSessionsCount);
_serverManager.RegisterServer((int)PrepoPortIndex.Admin, ServiceName.Encode("prepo:a"), PrepoMaxSessionsCount); // 1.0.0-5.1.0
_serverManager.RegisterServer((int)PrepoPortIndex.Admin2, ServiceName.Encode("prepo:a2"), PrepoMaxSessionsCount); // 6.0.0+
_serverManager.RegisterServer((int)PrepoPortIndex.Manager, ServiceName.Encode("prepo:m"), PrepoMaxSessionsCount);
_serverManager.RegisterServer((int)PrepoPortIndex.User, ServiceName.Encode("prepo:u"), PrepoMaxSessionsCount);
_serverManager.RegisterServer((int)PrepoPortIndex.System, ServiceName.Encode("prepo:s"), PrepoMaxSessionsCount);
_serverManager.RegisterServer((int)PrepoPortIndex.Debug, ServiceName.Encode("prepo:d"), PrepoMaxSessionsCount); // 1.0.0
}
public void ServiceRequests()
{
_serverManager.ServiceRequests();
}
public void Shutdown()
{
_serverManager.Dispose();
}
}
}

View File

@@ -0,0 +1,17 @@
namespace Ryujinx.Horizon.Prepo
{
class PrepoMain : IService
{
public static void Main(ServiceTable serviceTable)
{
PrepoIpcServer ipcServer = new();
ipcServer.Initialize();
serviceTable.SignalServiceReady();
ipcServer.ServiceRequests();
ipcServer.Shutdown();
}
}
}

View File

@@ -0,0 +1,15 @@
using Ryujinx.Horizon.Common;
namespace Ryujinx.Horizon.Prepo
{
static class PrepoResult
{
private const int ModuleId = 129;
public static Result InvalidArgument => new(ModuleId, 1);
public static Result InvalidState => new(ModuleId, 5);
public static Result InvalidBufferSize => new(ModuleId, 9);
public static Result PermissionDenied => new(ModuleId, 90);
public static Result InvalidPid => new(ModuleId, 101);
}
}

View File

@@ -0,0 +1,30 @@
using Ryujinx.Horizon.Common;
using Ryujinx.Horizon.Prepo.Ipc;
using Ryujinx.Horizon.Prepo.Types;
using Ryujinx.Horizon.Sdk.Sf.Hipc;
using Ryujinx.Horizon.Sdk.Sm;
using System;
namespace Ryujinx.Horizon.Prepo
{
class PrepoServerManager : ServerManager
{
public PrepoServerManager(HeapAllocator allocator, SmApi sm, int maxPorts, ManagerOptions options, int maxSessions) : base(allocator, sm, maxPorts, options, maxSessions)
{
}
protected override Result OnNeedsToAccept(int portIndex, Server server)
{
return (PrepoPortIndex)portIndex switch
{
PrepoPortIndex.Admin => AcceptImpl(server, new PrepoService(PrepoServicePermissionLevel.Admin)),
PrepoPortIndex.Admin2 => AcceptImpl(server, new PrepoService(PrepoServicePermissionLevel.Admin)),
PrepoPortIndex.Manager => AcceptImpl(server, new PrepoService(PrepoServicePermissionLevel.Manager)),
PrepoPortIndex.User => AcceptImpl(server, new PrepoService(PrepoServicePermissionLevel.User)),
PrepoPortIndex.System => AcceptImpl(server, new PrepoService(PrepoServicePermissionLevel.System)),
PrepoPortIndex.Debug => AcceptImpl(server, new PrepoService(PrepoServicePermissionLevel.Debug)),
_ => throw new ArgumentOutOfRangeException(nameof(portIndex)),
};
}
}
}

View File

@@ -0,0 +1,12 @@
namespace Ryujinx.Horizon.Prepo.Types
{
enum PrepoPortIndex
{
Admin,
Admin2,
Manager,
User,
System,
Debug
}
}

View File

@@ -1,10 +1,11 @@
namespace Ryujinx.HLE.HOS.Services.Prepo namespace Ryujinx.Horizon.Prepo.Types
{ {
enum PrepoServicePermissionLevel enum PrepoServicePermissionLevel
{ {
Admin = -1, Admin = -1,
User = 1, User = 1,
System = 2, System = 2,
Manager = 6 Manager = 6,
Debug = unchecked((int)0x80000006)
} }
} }

View File

@@ -11,4 +11,8 @@
<ProjectReference Include="..\Ryujinx.Memory\Ryujinx.Memory.csproj" /> <ProjectReference Include="..\Ryujinx.Memory\Ryujinx.Memory.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<PackageReference Include="LibHac" />
</ItemGroup>
</Project> </Project>

View File

@@ -0,0 +1,62 @@
using System;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
namespace Ryujinx.Horizon.Sdk.Account
{
[StructLayout(LayoutKind.Sequential)]
public readonly record struct Uid
{
public readonly long High;
public readonly long Low;
public bool IsNull => (Low | High) == 0;
public static Uid Null => new(0, 0);
public Uid(long low, long high)
{
Low = low;
High = high;
}
public Uid(byte[] bytes)
{
High = BitConverter.ToInt64(bytes, 0);
Low = BitConverter.ToInt64(bytes, 8);
}
public Uid(string hex)
{
if (hex == null || hex.Length != 32 || !hex.All("0123456789abcdefABCDEF".Contains))
{
throw new ArgumentException("Invalid Hex value!", nameof(hex));
}
Low = Convert.ToInt64(hex[16..], 16);
High = Convert.ToInt64(hex[..16], 16);
}
public void Write(BinaryWriter binaryWriter)
{
binaryWriter.Write(High);
binaryWriter.Write(Low);
}
public override string ToString()
{
return High.ToString("x16") + Low.ToString("x16");
}
public LibHac.Account.Uid ToLibHacUid()
{
return new LibHac.Account.Uid((ulong)High, (ulong)Low);
}
public UInt128 ToUInt128()
{
return new UInt128((ulong)High, (ulong)Low);
}
}
}

View File

@@ -0,0 +1,12 @@
using Ryujinx.Horizon.Common;
using Ryujinx.Horizon.Sdk.Sf;
using System;
namespace Ryujinx.Horizon.Sdk.Lm
{
interface ILmLogger : IServiceObject
{
Result Log(Span<byte> message);
Result SetDestination(LogDestination destination);
}
}

View File

@@ -0,0 +1,11 @@
using Ryujinx.Horizon.Common;
using Ryujinx.Horizon.LogManager.Ipc;
using Ryujinx.Horizon.Sdk.Sf;
namespace Ryujinx.Horizon.Sdk.Lm
{
interface ILogService : IServiceObject
{
Result OpenLogger(out LmLogger logger, ulong pid);
}
}

View File

@@ -0,0 +1,20 @@
using Ryujinx.Horizon.Common;
using Ryujinx.Horizon.Sdk.Account;
using Ryujinx.Horizon.Sdk.Sf;
using System;
namespace Ryujinx.Horizon.Sdk.Prepo
{
interface IPrepoService : IServiceObject
{
Result SaveReport(ReadOnlySpan<byte> gameRoomBuffer, ReadOnlySpan<byte> reportBuffer, ulong pid);
Result SaveReportWithUser(Uid userId, ReadOnlySpan<byte> gameRoomBuffer, ReadOnlySpan<byte> reportBuffer, ulong pid);
Result RequestImmediateTransmission();
Result GetTransmissionStatus(out int status);
Result GetSystemSessionId(out ulong systemSessionId);
Result SaveSystemReport(ReadOnlySpan<byte> gameRoomBuffer, ReadOnlySpan<byte> reportBuffer, ulong pid);
Result SaveSystemReportWithUser(Uid userId, ReadOnlySpan<byte> gameRoomBuffer, ReadOnlySpan<byte> reportBuffer, ulong pid);
Result IsUserAgreementCheckEnabled(out bool enabled);
Result SetUserAgreementCheckEnabled(bool enabled);
}
}

View File

@@ -10,15 +10,15 @@ namespace Ryujinx.Horizon.Sdk
public static Result SendRequest(out CmifResponse response, int sessionHandle, uint requestId, bool sendPid, scoped ReadOnlySpan<byte> data) public static Result SendRequest(out CmifResponse response, int sessionHandle, uint requestId, bool sendPid, scoped ReadOnlySpan<byte> data)
{ {
ulong tlsAddress = HorizonStatic.ThreadContext.TlsAddress; ulong tlsAddress = HorizonStatic.ThreadContext.TlsAddress;
int tlsSize = Api.TlsMessageBufferSize; int tlsSize = Api.TlsMessageBufferSize;
using (var tlsRegion = HorizonStatic.AddressSpace.GetWritableRegion(tlsAddress, tlsSize)) using (var tlsRegion = HorizonStatic.AddressSpace.GetWritableRegion(tlsAddress, tlsSize))
{ {
CmifRequest request = CmifMessage.CreateRequest(tlsRegion.Memory.Span, new CmifRequestFormat() CmifRequest request = CmifMessage.CreateRequest(tlsRegion.Memory.Span, new CmifRequestFormat()
{ {
DataSize = data.Length, DataSize = data.Length,
RequestId = requestId, RequestId = requestId,
SendPid = sendPid SendPid = sendPid
}); });
data.CopyTo(request.Data); data.CopyTo(request.Data);
@@ -29,6 +29,7 @@ namespace Ryujinx.Horizon.Sdk
if (result.IsFailure) if (result.IsFailure)
{ {
response = default; response = default;
return result; return result;
} }

View File

@@ -3,10 +3,10 @@
struct CmifDomainInHeader struct CmifDomainInHeader
{ {
public CmifDomainRequestType Type; public CmifDomainRequestType Type;
public byte ObjectsCount; public byte ObjectsCount;
public ushort DataSize; public ushort DataSize;
public int ObjectId; public int ObjectId;
public uint Padding; public uint Padding;
public uint Token; public uint Token;
} }
} }

View File

@@ -2,8 +2,8 @@
{ {
enum CmifDomainRequestType : byte enum CmifDomainRequestType : byte
{ {
Invalid = 0, Invalid = 0,
SendMessage = 1, SendMessage = 1,
Close = 2 Close = 2
} }
} }

View File

@@ -8,7 +8,7 @@ namespace Ryujinx.Horizon.Sdk.Sf.Cmif
{ {
static class CmifMessage static class CmifMessage
{ {
public const uint CmifInHeaderMagic = 0x49434653; // SFCI public const uint CmifInHeaderMagic = 0x49434653; // SFCI
public const uint CmifOutHeaderMagic = 0x4f434653; // SFCO public const uint CmifOutHeaderMagic = 0x4f434653; // SFCO
public static CmifRequest CreateRequest(Span<byte> output, CmifRequestFormat format) public static CmifRequest CreateRequest(Span<byte> output, CmifRequestFormat format)
@@ -21,27 +21,31 @@ namespace Ryujinx.Horizon.Sdk.Sf.Cmif
} }
totalSize += Unsafe.SizeOf<CmifInHeader>() + format.DataSize; totalSize += Unsafe.SizeOf<CmifInHeader>() + format.DataSize;
totalSize = (totalSize + 1) & ~1; totalSize = (totalSize + 1) & ~1;
int outPointerSizeTableOffset = totalSize; int outPointerSizeTableOffset = totalSize;
int outPointerSizeTableSize = format.OutAutoBuffersCount + format.OutPointersCount; int outPointerSizeTableSize = format.OutAutoBuffersCount + format.OutPointersCount;
totalSize += sizeof(ushort) * outPointerSizeTableSize; totalSize += sizeof(ushort) * outPointerSizeTableSize;
int rawDataSizeInWords = (totalSize + sizeof(uint) - 1) / sizeof(uint); int rawDataSizeInWords = (totalSize + sizeof(uint) - 1) / sizeof(uint);
CmifRequest request = new CmifRequest(); CmifRequest request = new()
request.Hipc = HipcMessage.WriteMessage(output, new HipcMetadata()
{ {
Type = format.Context != 0 ? (int)CommandType.RequestWithContext : (int)CommandType.Request, Hipc = HipcMessage.WriteMessage(output, new HipcMetadata()
SendStaticsCount = format.InAutoBuffersCount + format.InPointersCount, {
SendBuffersCount = format.InAutoBuffersCount + format.InBuffersCount, Type = format.Context != 0 ? (int)CommandType.RequestWithContext : (int)CommandType.Request,
ReceiveBuffersCount = format.OutAutoBuffersCount + format.OutBuffersCount, SendStaticsCount = format.InAutoBuffersCount + format.InPointersCount,
ExchangeBuffersCount = format.InOutBuffersCount, SendBuffersCount = format.InAutoBuffersCount + format.InBuffersCount,
DataWordsCount = rawDataSizeInWords, ReceiveBuffersCount = format.OutAutoBuffersCount + format.OutBuffersCount,
ReceiveStaticsCount = outPointerSizeTableSize + format.OutFixedPointersCount, ExchangeBuffersCount = format.InOutBuffersCount,
SendPid = format.SendPid, DataWordsCount = rawDataSizeInWords,
CopyHandlesCount = format.HandlesCount, ReceiveStaticsCount = outPointerSizeTableSize + format.OutFixedPointersCount,
MoveHandlesCount = 0 SendPid = format.SendPid,
}); CopyHandlesCount = format.HandlesCount,
MoveHandlesCount = 0
})
};
Span<uint> data = request.Hipc.DataWords; Span<uint> data = request.Hipc.DataWords;
@@ -53,35 +57,36 @@ namespace Ryujinx.Horizon.Sdk.Sf.Cmif
domainHeader = new CmifDomainInHeader() domainHeader = new CmifDomainInHeader()
{ {
Type = CmifDomainRequestType.SendMessage, Type = CmifDomainRequestType.SendMessage,
ObjectsCount = (byte)format.ObjectsCount, ObjectsCount = (byte)format.ObjectsCount,
DataSize = (ushort)payloadSize, DataSize = (ushort)payloadSize,
ObjectId = format.ObjectId, ObjectId = format.ObjectId,
Padding = 0, Padding = 0,
Token = format.Context Token = format.Context
}; };
data = data.Slice(Unsafe.SizeOf<CmifDomainInHeader>() / sizeof(uint)); data = data[(Unsafe.SizeOf<CmifDomainInHeader>() / sizeof(uint))..];
request.Objects = data.Slice((payloadSize + sizeof(uint) - 1) / sizeof(uint)); request.Objects = data[((payloadSize + sizeof(uint) - 1) / sizeof(uint))..];
} }
ref CmifInHeader header = ref MemoryMarshal.Cast<uint, CmifInHeader>(data)[0]; ref CmifInHeader header = ref MemoryMarshal.Cast<uint, CmifInHeader>(data)[0];
header = new CmifInHeader() header = new CmifInHeader()
{ {
Magic = CmifInHeaderMagic, Magic = CmifInHeaderMagic,
Version = format.Context != 0 ? 1u : 0u, Version = format.Context != 0 ? 1u : 0u,
CommandId = format.RequestId, CommandId = format.RequestId,
Token = format.ObjectId != 0 ? 0u : format.Context Token = format.ObjectId != 0 ? 0u : format.Context
}; };
request.Data = MemoryMarshal.Cast<uint, byte>(data).Slice(Unsafe.SizeOf<CmifInHeader>()); request.Data = MemoryMarshal.Cast<uint, byte>(data)[Unsafe.SizeOf<CmifInHeader>()..];
int paddingSizeBefore = (rawDataSizeInWords - request.Hipc.DataWords.Length) * sizeof(uint); int paddingSizeBefore = (rawDataSizeInWords - request.Hipc.DataWords.Length) * sizeof(uint);
Span<byte> outPointerTable = MemoryMarshal.Cast<uint, byte>(request.Hipc.DataWords).Slice(outPointerSizeTableOffset - paddingSizeBefore); Span<byte> outPointerTable = MemoryMarshal.Cast<uint, byte>(request.Hipc.DataWords)[(outPointerSizeTableOffset - paddingSizeBefore)..];
request.OutPointerSizes = MemoryMarshal.Cast<byte, ushort>(outPointerTable);
request.OutPointerSizes = MemoryMarshal.Cast<byte, ushort>(outPointerTable);
request.ServerPointerSize = format.ServerPointerSize; request.ServerPointerSize = format.ServerPointerSize;
return request; return request;
@@ -89,15 +94,15 @@ namespace Ryujinx.Horizon.Sdk.Sf.Cmif
public static Result ParseResponse(out CmifResponse response, Span<byte> input, bool isDomain, int size) public static Result ParseResponse(out CmifResponse response, Span<byte> input, bool isDomain, int size)
{ {
HipcMessage responseMessage = new HipcMessage(input); HipcMessage responseMessage = new(input);
Span<byte> data = MemoryMarshal.Cast<uint, byte>(responseMessage.Data.DataWords); Span<byte> data = MemoryMarshal.Cast<uint, byte>(responseMessage.Data.DataWords);
Span<uint> objects = Span<uint>.Empty; Span<uint> objects = Span<uint>.Empty;
if (isDomain) if (isDomain)
{ {
data = data.Slice(Unsafe.SizeOf<CmifDomainOutHeader>()); data = data[Unsafe.SizeOf<CmifDomainOutHeader>()..];
objects = MemoryMarshal.Cast<byte, uint>(data.Slice(Unsafe.SizeOf<CmifOutHeader>() + size)); objects = MemoryMarshal.Cast<byte, uint>(data[(Unsafe.SizeOf<CmifOutHeader>() + size)..]);
} }
CmifOutHeader header = MemoryMarshal.Cast<byte, CmifOutHeader>(data)[0]; CmifOutHeader header = MemoryMarshal.Cast<byte, CmifOutHeader>(data)[0];
@@ -105,19 +110,21 @@ namespace Ryujinx.Horizon.Sdk.Sf.Cmif
if (header.Magic != CmifOutHeaderMagic) if (header.Magic != CmifOutHeaderMagic)
{ {
response = default; response = default;
return SfResult.InvalidOutHeader; return SfResult.InvalidOutHeader;
} }
if (header.Result.IsFailure) if (header.Result.IsFailure)
{ {
response = default; response = default;
return header.Result; return header.Result;
} }
response = new CmifResponse() response = new CmifResponse()
{ {
Data = data.Slice(Unsafe.SizeOf<CmifOutHeader>()), Data = data[Unsafe.SizeOf<CmifOutHeader>()..],
Objects = objects, Objects = objects,
CopyHandles = responseMessage.Data.CopyHandles, CopyHandles = responseMessage.Data.CopyHandles,
MoveHandles = responseMessage.Data.MoveHandles MoveHandles = responseMessage.Data.MoveHandles
}; };

View File

@@ -5,10 +5,10 @@ namespace Ryujinx.Horizon.Sdk.Sf.Cmif
struct CmifOutHeader struct CmifOutHeader
{ {
#pragma warning disable CS0649 #pragma warning disable CS0649
public uint Magic; public uint Magic;
public uint Version; public uint Version;
public Result Result; public Result Result;
public uint Token; public uint Token;
#pragma warning restore CS0649 #pragma warning restore CS0649
} }
} }

View File

@@ -6,9 +6,9 @@ namespace Ryujinx.Horizon.Sdk.Sf.Cmif
ref struct CmifRequest ref struct CmifRequest
{ {
public HipcMessageData Hipc; public HipcMessageData Hipc;
public Span<byte> Data; public Span<byte> Data;
public Span<ushort> OutPointerSizes; public Span<ushort> OutPointerSizes;
public Span<uint> Objects; public Span<uint> Objects;
public int ServerPointerSize; public int ServerPointerSize;
} }
} }

View File

@@ -3,21 +3,21 @@
struct CmifRequestFormat struct CmifRequestFormat
{ {
#pragma warning disable CS0649 #pragma warning disable CS0649
public int ObjectId; public int ObjectId;
public uint RequestId; public uint RequestId;
public uint Context; public uint Context;
public int DataSize; public int DataSize;
public int ServerPointerSize; public int ServerPointerSize;
public int InAutoBuffersCount; public int InAutoBuffersCount;
public int OutAutoBuffersCount; public int OutAutoBuffersCount;
public int InBuffersCount; public int InBuffersCount;
public int OutBuffersCount; public int OutBuffersCount;
public int InOutBuffersCount; public int InOutBuffersCount;
public int InPointersCount; public int InPointersCount;
public int OutPointersCount; public int OutPointersCount;
public int OutFixedPointersCount; public int OutFixedPointersCount;
public int ObjectsCount; public int ObjectsCount;
public int HandlesCount; public int HandlesCount;
public bool SendPid; public bool SendPid;
#pragma warning restore CS0649 #pragma warning restore CS0649
} }

View File

@@ -6,7 +6,7 @@ namespace Ryujinx.Horizon.Sdk.Sf.Cmif
{ {
public ReadOnlySpan<byte> Data; public ReadOnlySpan<byte> Data;
public ReadOnlySpan<uint> Objects; public ReadOnlySpan<uint> Objects;
public ReadOnlySpan<int> CopyHandles; public ReadOnlySpan<int> CopyHandles;
public ReadOnlySpan<int> MoveHandles; public ReadOnlySpan<int> MoveHandles;
} }
} }

View File

@@ -2,12 +2,12 @@ namespace Ryujinx.Horizon.Sdk.Sf.Cmif
{ {
enum CommandType enum CommandType
{ {
Invalid = 0, Invalid = 0,
LegacyRequest = 1, LegacyRequest = 1,
Close = 2, Close = 2,
LegacyControl = 3, LegacyControl = 3,
Request = 4, Request = 4,
Control = 5, Control = 5,
RequestWithContext = 6, RequestWithContext = 6,
ControlWithContext = 7 ControlWithContext = 7
} }

View File

@@ -21,7 +21,7 @@ namespace Ryujinx.Horizon.Sdk.Sf.Cmif
var inHeader = MemoryMarshal.Cast<byte, CmifDomainInHeader>(inRawData)[0]; var inHeader = MemoryMarshal.Cast<byte, CmifDomainInHeader>(inRawData)[0];
ReadOnlySpan<byte> inDomainRawData = inRawData.Slice(Unsafe.SizeOf<CmifDomainInHeader>()); ReadOnlySpan<byte> inDomainRawData = inRawData[Unsafe.SizeOf<CmifDomainInHeader>()..];
int targetObjectId = inHeader.ObjectId; int targetObjectId = inHeader.ObjectId;
@@ -39,7 +39,7 @@ namespace Ryujinx.Horizon.Sdk.Sf.Cmif
return SfResult.InvalidHeaderSize; return SfResult.InvalidHeaderSize;
} }
ReadOnlySpan<byte> inMessageRawData = inDomainRawData.Slice(0, inHeader.DataSize); ReadOnlySpan<byte> inMessageRawData = inDomainRawData[..inHeader.DataSize];
if (inHeader.ObjectsCount > DomainServiceObjectProcessor.MaximumObjects) if (inHeader.ObjectsCount > DomainServiceObjectProcessor.MaximumObjects)
{ {

View File

@@ -63,7 +63,7 @@ namespace Ryujinx.Horizon.Sdk.Sf.Cmif
return SfResult.InvalidInObjectsCount; return SfResult.InvalidInObjectsCount;
} }
Result result = _domain.ReserveIds(new Span<int>(_reservedObjectIds).Slice(0, OutObjectsCount)); Result result = _domain.ReserveIds(new Span<int>(_reservedObjectIds)[..OutObjectsCount]);
if (result.IsFailure) if (result.IsFailure)
{ {
@@ -92,7 +92,7 @@ namespace Ryujinx.Horizon.Sdk.Sf.Cmif
DebugUtil.Assert(outHeaderSize + implOutDataTotalSize + OutObjectsCount * sizeof(int) <= outRawData.Length); DebugUtil.Assert(outHeaderSize + implOutDataTotalSize + OutObjectsCount * sizeof(int) <= outRawData.Length);
outRawData = outRawData.Slice(outHeaderSize); outRawData = outRawData[outHeaderSize..];
_outObjectIdsOffset = (response.DataWords.Length * sizeof(uint) - outRawData.Length) + implOutDataTotalSize; _outObjectIdsOffset = (response.DataWords.Length * sizeof(uint) - outRawData.Length) + implOutDataTotalSize;
return response; return response;
@@ -107,9 +107,9 @@ namespace Ryujinx.Horizon.Sdk.Sf.Cmif
DebugUtil.Assert(outHeaderSize + implOutDataTotalSize <= outRawData.Length); DebugUtil.Assert(outHeaderSize + implOutDataTotalSize <= outRawData.Length);
outRawData = outRawData.Slice(outHeaderSize); outRawData = outRawData[outHeaderSize..];
_domain.UnreserveIds(new Span<int>(_reservedObjectIds).Slice(0, OutObjectsCount)); _domain.UnreserveIds(new Span<int>(_reservedObjectIds)[..OutObjectsCount]);
} }
public override void SetOutObjects(scoped ref ServiceDispatchContext context, HipcMessageData response, Span<ServiceObjectHolder> outObjects) public override void SetOutObjects(scoped ref ServiceDispatchContext context, HipcMessageData response, Span<ServiceObjectHolder> outObjects)
@@ -129,7 +129,7 @@ namespace Ryujinx.Horizon.Sdk.Sf.Cmif
_domain.RegisterObject(objectIds[i], outObjects[i]); _domain.RegisterObject(objectIds[i], outObjects[i]);
} }
Span<int> outObjectIds = MemoryMarshal.Cast<byte, int>(MemoryMarshal.Cast<uint, byte>(response.DataWords).Slice(_outObjectIdsOffset)); Span<int> outObjectIds = MemoryMarshal.Cast<byte, int>(MemoryMarshal.Cast<uint, byte>(response.DataWords)[_outObjectIdsOffset..]);
for (int i = 0; i < outObjectsCount; i++) for (int i = 0; i < outObjectsCount; i++)
{ {

View File

@@ -2,7 +2,7 @@ namespace Ryujinx.Horizon.Sdk.Sf.Cmif
{ {
struct PointerAndSize struct PointerAndSize
{ {
public static PointerAndSize Empty => new PointerAndSize(0UL, 0UL); public static PointerAndSize Empty => new(0UL, 0UL);
public ulong Address { get; } public ulong Address { get; }
public ulong Size { get; } public ulong Size { get; }
@@ -11,7 +11,7 @@ namespace Ryujinx.Horizon.Sdk.Sf.Cmif
public PointerAndSize(ulong address, ulong size) public PointerAndSize(ulong address, ulong size)
{ {
Address = address; Address = address;
Size = size; Size = size;
} }
} }
} }

View File

@@ -2,27 +2,28 @@
{ {
struct ServerMessageRuntimeMetadata struct ServerMessageRuntimeMetadata
{ {
public ushort InDataSize { get; } public ushort InDataSize { get; }
public ushort OutDataSize { get; } public ushort OutDataSize { get; }
public byte InHeadersSize { get; } public byte InHeadersSize { get; }
public byte OutHeadersSize { get; } public byte OutHeadersSize { get; }
public byte InObjectsCount { get; } public byte InObjectsCount { get; }
public byte OutObjectsCount { get; } public byte OutObjectsCount { get; }
public int UnfixedOutPointerSizeOffset => InDataSize + InHeadersSize + 0x10; public int UnfixedOutPointerSizeOffset => InDataSize + InHeadersSize + 0x10;
public ServerMessageRuntimeMetadata( public ServerMessageRuntimeMetadata(
ushort inDataSize, ushort inDataSize,
ushort outDataSize, ushort outDataSize,
byte inHeadersSize, byte inHeadersSize,
byte outHeadersSize, byte outHeadersSize,
byte inObjectsCount, byte inObjectsCount,
byte outObjectsCount) byte outObjectsCount)
{ {
InDataSize = inDataSize; InDataSize = inDataSize;
OutDataSize = outDataSize; OutDataSize = outDataSize;
InHeadersSize = inHeadersSize; InHeadersSize = inHeadersSize;
OutHeadersSize = outHeadersSize; OutHeadersSize = outHeadersSize;
InObjectsCount = inObjectsCount; InObjectsCount = inObjectsCount;
OutObjectsCount = outObjectsCount; OutObjectsCount = outObjectsCount;
} }
} }

View File

@@ -5,14 +5,14 @@ namespace Ryujinx.Horizon.Sdk.Sf.Cmif
{ {
ref struct ServiceDispatchContext ref struct ServiceDispatchContext
{ {
public IServiceObject ServiceObject; public IServiceObject ServiceObject;
public ServerSessionManager Manager; public ServerSessionManager Manager;
public ServerSession Session; public ServerSession Session;
public ServerMessageProcessor Processor; public ServerMessageProcessor Processor;
public HandlesToClose HandlesToClose; public HandlesToClose HandlesToClose;
public PointerAndSize PointerBuffer; public PointerAndSize PointerBuffer;
public ReadOnlySpan<byte> InMessageBuffer; public ReadOnlySpan<byte> InMessageBuffer;
public Span<byte> OutMessageBuffer; public Span<byte> OutMessageBuffer;
public HipcMessage Request; public HipcMessage Request;
} }
} }

View File

@@ -12,7 +12,7 @@ namespace Ryujinx.Horizon.Sdk.Sf.Cmif
public ServiceDispatchTable(string objectName, IReadOnlyDictionary<int, CommandHandler> entries) public ServiceDispatchTable(string objectName, IReadOnlyDictionary<int, CommandHandler> entries)
{ {
_objectName = objectName; _objectName = objectName;
_entries = entries; _entries = entries;
} }
public override Result ProcessMessage(ref ServiceDispatchContext context, ReadOnlySpan<byte> inRawData) public override Result ProcessMessage(ref ServiceDispatchContext context, ReadOnlySpan<byte> inRawData)

View File

@@ -39,17 +39,21 @@ namespace Ryujinx.Horizon.Sdk.Sf.Cmif
if (!entries.TryGetValue((int)commandId, out var commandHandler)) if (!entries.TryGetValue((int)commandId, out var commandHandler))
{ {
Logger.Warning?.Print(LogClass.KernelIpc, $"{objectName} command ID 0x{commandId:X} is not implemented");
if (HorizonStatic.Options.IgnoreMissingServices) if (HorizonStatic.Options.IgnoreMissingServices)
{ {
// If ignore missing services is enabled, just pretend that everything is fine. // If ignore missing services is enabled, just pretend that everything is fine.
var response = PrepareForStubReply(ref context, out Span<byte> outRawData); PrepareForStubReply(ref context, out Span<byte> outRawData);
CommandHandler.GetCmifOutHeaderPointer(ref outHeader, ref outRawData); CommandHandler.GetCmifOutHeaderPointer(ref outHeader, ref outRawData);
outHeader[0] = new CmifOutHeader() { Magic = CmifMessage.CmifOutHeaderMagic, Result = Result.Success }; outHeader[0] = new CmifOutHeader() { Magic = CmifMessage.CmifOutHeaderMagic, Result = Result.Success };
Logger.Warning?.Print(LogClass.Service, $"Missing service {objectName} (command ID: {commandId}) ignored");
return Result.Success; return Result.Success;
} }
else if (HorizonStatic.Options.ThrowOnInvalidCommandIds)
{
throw new NotImplementedException($"{objectName} command ID: {commandId} is not implemented");
}
return SfResult.UnknownCommandId; return SfResult.UnknownCommandId;
} }
@@ -72,6 +76,7 @@ namespace Ryujinx.Horizon.Sdk.Sf.Cmif
if (outHeader.IsEmpty) if (outHeader.IsEmpty)
{ {
commandResult.AbortOnSuccess(); commandResult.AbortOnSuccess();
return commandResult; return commandResult;
} }
@@ -80,11 +85,10 @@ namespace Ryujinx.Horizon.Sdk.Sf.Cmif
return Result.Success; return Result.Success;
} }
private static HipcMessageData PrepareForStubReply(scoped ref ServiceDispatchContext context, out Span<byte> outRawData) private static void PrepareForStubReply(scoped ref ServiceDispatchContext context, out Span<byte> outRawData)
{ {
var response = HipcMessage.WriteResponse(context.OutMessageBuffer, 0, 0x20 / sizeof(uint), 0, 0); var response = HipcMessage.WriteResponse(context.OutMessageBuffer, 0, 0x20 / sizeof(uint), 0, 0);
outRawData = MemoryMarshal.Cast<uint, byte>(response.DataWords); outRawData = MemoryMarshal.Cast<uint, byte>(response.DataWords);
return response;
} }
} }
} }

View File

@@ -20,37 +20,37 @@ namespace Ryujinx.Horizon.Sdk.Sf
struct CommandArg struct CommandArg
{ {
public CommandArgType Type { get; } public CommandArgType Type { get; }
public HipcBufferFlags BufferFlags { get; } public HipcBufferFlags BufferFlags { get; }
public ushort BufferFixedSize { get; } public ushort BufferFixedSize { get; }
public int ArgSize { get; } public int ArgSize { get; }
public int ArgAlignment { get; } public int ArgAlignment { get; }
public CommandArg(CommandArgType type) public CommandArg(CommandArgType type)
{ {
Type = type; Type = type;
BufferFlags = default; BufferFlags = default;
BufferFixedSize = 0; BufferFixedSize = 0;
ArgSize = 0; ArgSize = 0;
ArgAlignment = 0; ArgAlignment = 0;
} }
public CommandArg(CommandArgType type, int argSize, int argAlignment) public CommandArg(CommandArgType type, int argSize, int argAlignment)
{ {
Type = type; Type = type;
BufferFlags = default; BufferFlags = default;
BufferFixedSize = 0; BufferFixedSize = 0;
ArgSize = argSize; ArgSize = argSize;
ArgAlignment = argAlignment; ArgAlignment = argAlignment;
} }
public CommandArg(HipcBufferFlags flags, ushort fixedSize = 0) public CommandArg(HipcBufferFlags flags, ushort fixedSize = 0)
{ {
Type = CommandArgType.Buffer; Type = CommandArgType.Buffer;
BufferFlags = flags; BufferFlags = flags;
BufferFixedSize = fixedSize; BufferFixedSize = fixedSize;
ArgSize = 0; ArgSize = 0;
ArgAlignment = 0; ArgAlignment = 0;
} }
} }
} }

View File

@@ -6,8 +6,8 @@ namespace Ryujinx.Horizon.Sdk.Sf
[AttributeUsage(AttributeTargets.Parameter)] [AttributeUsage(AttributeTargets.Parameter)]
class BufferAttribute : Attribute class BufferAttribute : Attribute
{ {
public HipcBufferFlags Flags { get; } public HipcBufferFlags Flags { get; }
public ushort FixedSize { get; } public ushort FixedSize { get; }
public BufferAttribute(HipcBufferFlags flags) public BufferAttribute(HipcBufferFlags flags)
{ {
@@ -16,7 +16,7 @@ namespace Ryujinx.Horizon.Sdk.Sf
public BufferAttribute(HipcBufferFlags flags, ushort fixedSize) public BufferAttribute(HipcBufferFlags flags, ushort fixedSize)
{ {
Flags = flags | HipcBufferFlags.FixedSize; Flags = flags | HipcBufferFlags.FixedSize;
FixedSize = fixedSize; FixedSize = fixedSize;
} }
} }

View File

@@ -9,20 +9,20 @@ namespace Ryujinx.Horizon.Sdk.Sf
class CommandHandler class CommandHandler
{ {
public delegate Result MethodInvoke( public delegate Result MethodInvoke(
ref ServiceDispatchContext context, ref ServiceDispatchContext context,
HipcCommandProcessor processor, HipcCommandProcessor processor,
ServerMessageRuntimeMetadata runtimeMetadata, ServerMessageRuntimeMetadata runtimeMetadata,
ReadOnlySpan<byte> inRawData, ReadOnlySpan<byte> inRawData,
ref Span<CmifOutHeader> outHeader); ref Span<CmifOutHeader> outHeader);
private readonly MethodInvoke _invoke; private readonly MethodInvoke _invoke;
private readonly HipcCommandProcessor _processor; private readonly HipcCommandProcessor _processor;
public string MethodName => _invoke.Method.Name; public string MethodName => _invoke.Method.Name;
public CommandHandler(MethodInvoke invoke, params CommandArg[] args) public CommandHandler(MethodInvoke invoke, params CommandArg[] args)
{ {
_invoke = invoke; _invoke = invoke;
_processor = new HipcCommandProcessor(args); _processor = new HipcCommandProcessor(args);
} }
@@ -37,8 +37,8 @@ namespace Ryujinx.Horizon.Sdk.Sf
context.Processor.SetImplementationProcessor(_processor); context.Processor.SetImplementationProcessor(_processor);
} }
var runtimeMetadata = context.Processor.GetRuntimeMetadata(); var runtimeMetadata = context.Processor.GetRuntimeMetadata();
Result result = context.Processor.PrepareForProcess(ref context, runtimeMetadata); Result result = context.Processor.PrepareForProcess(ref context, runtimeMetadata);
if (result.IsFailure) if (result.IsFailure)
{ {
@@ -50,8 +50,8 @@ namespace Ryujinx.Horizon.Sdk.Sf
public static void GetCmifOutHeaderPointer(ref Span<CmifOutHeader> outHeader, ref Span<byte> outRawData) public static void GetCmifOutHeaderPointer(ref Span<CmifOutHeader> outHeader, ref Span<byte> outRawData)
{ {
outHeader = MemoryMarshal.Cast<byte, CmifOutHeader>(outRawData).Slice(0, 1); outHeader = MemoryMarshal.Cast<byte, CmifOutHeader>(outRawData)[..1];
outRawData = outRawData.Slice(Unsafe.SizeOf<CmifOutHeader>()); outRawData = outRawData[Unsafe.SizeOf<CmifOutHeader>()..];
} }
} }
} }

View File

@@ -22,6 +22,7 @@ namespace Ryujinx.Horizon.Sdk.Sf
public static ref T GetRef<T>(PointerAndSize bufferRange) where T : unmanaged public static ref T GetRef<T>(PointerAndSize bufferRange) where T : unmanaged
{ {
var writableRegion = GetWritableRegion(bufferRange); var writableRegion = GetWritableRegion(bufferRange);
return ref MemoryMarshal.Cast<byte, T>(writableRegion.Memory.Span)[0]; return ref MemoryMarshal.Cast<byte, T>(writableRegion.Memory.Span)[0];
} }

View File

@@ -51,22 +51,18 @@ namespace Ryujinx.Horizon.Sdk.Sf.Hipc
{ {
Result result = ReplyImpl(sessionHandle, messageBuffer); Result result = ReplyImpl(sessionHandle, messageBuffer);
result.AbortOnFailureUnless(KernelResult.TimedOut, KernelResult.PortRemoteClosed); result.AbortUnless(KernelResult.TimedOut, KernelResult.PortRemoteClosed);
return Result.Success; return Result.Success;
} }
private static Result ReplyImpl(int sessionHandle, ReadOnlySpan<byte> messageBuffer) private static Result ReplyImpl(int sessionHandle, ReadOnlySpan<byte> messageBuffer)
{ {
Span<int> handles = stackalloc int[1];
handles[0] = sessionHandle;
var tlsSpan = HorizonStatic.AddressSpace.GetSpan(HorizonStatic.ThreadContext.TlsAddress, TlsMessageBufferSize); var tlsSpan = HorizonStatic.AddressSpace.GetSpan(HorizonStatic.ThreadContext.TlsAddress, TlsMessageBufferSize);
if (messageBuffer == tlsSpan) if (messageBuffer == tlsSpan)
{ {
return HorizonStatic.Syscall.ReplyAndReceive(out _, handles, sessionHandle, 0); return HorizonStatic.Syscall.ReplyAndReceive(out _, ReadOnlySpan<int>.Empty, sessionHandle, 0);
} }
else else
{ {

View File

@@ -5,12 +5,12 @@ namespace Ryujinx.Horizon.Sdk.Sf.Hipc
[Flags] [Flags]
enum HipcBufferFlags : byte enum HipcBufferFlags : byte
{ {
In = 1 << 0, In = 1 << 0,
Out = 1 << 1, Out = 1 << 1,
MapAlias = 1 << 2, MapAlias = 1 << 2,
Pointer = 1 << 3, Pointer = 1 << 3,
FixedSize = 1 << 4, FixedSize = 1 << 4,
AutoSelect = 1 << 5, AutoSelect = 1 << 5,
MapTransferAllowsNonSecure = 1 << 6, MapTransferAllowsNonSecure = 1 << 6,
MapTransferAllowsNonDevice = 1 << 7 MapTransferAllowsNonDevice = 1 << 7
} }

View File

@@ -2,9 +2,9 @@
{ {
enum HipcBufferMode enum HipcBufferMode
{ {
Normal = 0, Normal = 0,
NonSecure = 1, NonSecure = 1,
Invalid = 2, Invalid = 2,
NonDevice = 3 NonDevice = 3
} }
} }

View File

@@ -61,7 +61,7 @@ namespace Ryujinx.Horizon.Sdk.Sf.Hipc
{ {
clientHandle = 0; clientHandle = 0;
if (!(_session.ServiceObjectHolder.ServiceObject is DomainServiceObject domain)) if (_session.ServiceObjectHolder.ServiceObject is not DomainServiceObject domain)
{ {
return HipcResult.TargetNotDomain; return HipcResult.TargetNotDomain;
} }

View File

@@ -10,9 +10,9 @@ namespace Ryujinx.Horizon.Sdk.Sf.Hipc
{ {
public const int AutoReceiveStatic = byte.MaxValue; public const int AutoReceiveStatic = byte.MaxValue;
public HipcMetadata Meta; public HipcMetadata Meta;
public HipcMessageData Data; public HipcMessageData Data;
public ulong Pid; public ulong Pid;
public HipcMessage(Span<byte> data) public HipcMessage(Span<byte> data)
{ {
@@ -20,10 +20,10 @@ namespace Ryujinx.Horizon.Sdk.Sf.Hipc
Header header = MemoryMarshal.Cast<byte, Header>(data)[0]; Header header = MemoryMarshal.Cast<byte, Header>(data)[0];
data = data.Slice(Unsafe.SizeOf<Header>()); data = data[Unsafe.SizeOf<Header>()..];
int receiveStaticsCount = 0; int receiveStaticsCount = 0;
ulong pid = 0; ulong pid = 0;
if (header.ReceiveStaticMode != 0) if (header.ReceiveStaticMode != 0)
{ {
@@ -42,46 +42,44 @@ namespace Ryujinx.Horizon.Sdk.Sf.Hipc
if (header.HasSpecialHeader) if (header.HasSpecialHeader)
{ {
specialHeader = MemoryMarshal.Cast<byte, SpecialHeader>(data)[0]; specialHeader = MemoryMarshal.Cast<byte, SpecialHeader>(data)[0];
data = data[Unsafe.SizeOf<SpecialHeader>()..];
data = data.Slice(Unsafe.SizeOf<SpecialHeader>());
if (specialHeader.SendPid) if (specialHeader.SendPid)
{ {
pid = MemoryMarshal.Cast<byte, ulong>(data)[0]; pid = MemoryMarshal.Cast<byte, ulong>(data)[0];
data = data[sizeof(ulong)..];
data = data.Slice(sizeof(ulong));
} }
} }
Meta = new HipcMetadata() Meta = new HipcMetadata()
{ {
Type = (int)header.Type, Type = (int)header.Type,
SendStaticsCount = header.SendStaticsCount, SendStaticsCount = header.SendStaticsCount,
SendBuffersCount = header.SendBuffersCount, SendBuffersCount = header.SendBuffersCount,
ReceiveBuffersCount = header.ReceiveBuffersCount, ReceiveBuffersCount = header.ReceiveBuffersCount,
ExchangeBuffersCount = header.ExchangeBuffersCount, ExchangeBuffersCount = header.ExchangeBuffersCount,
DataWordsCount = header.DataWordsCount, DataWordsCount = header.DataWordsCount,
ReceiveStaticsCount = receiveStaticsCount, ReceiveStaticsCount = receiveStaticsCount,
SendPid = specialHeader.SendPid, SendPid = specialHeader.SendPid,
CopyHandlesCount = specialHeader.CopyHandlesCount, CopyHandlesCount = specialHeader.CopyHandlesCount,
MoveHandlesCount = specialHeader.MoveHandlesCount MoveHandlesCount = specialHeader.MoveHandlesCount
}; };
Data = CreateMessageData(Meta, data, initialLength); Data = CreateMessageData(Meta, data, initialLength);
Pid = pid; Pid = pid;
} }
public static HipcMessageData WriteResponse( public static HipcMessageData WriteResponse(
Span<byte> destination, Span<byte> destination,
int sendStaticCount, int sendStaticCount,
int dataWordsCount, int dataWordsCount,
int copyHandlesCount, int copyHandlesCount,
int moveHandlesCount) int moveHandlesCount)
{ {
return WriteMessage(destination, new HipcMetadata() return WriteMessage(destination, new HipcMetadata()
{ {
SendStaticsCount = sendStaticCount, SendStaticsCount = sendStaticCount,
DataWordsCount = dataWordsCount, DataWordsCount = dataWordsCount,
CopyHandlesCount = copyHandlesCount, CopyHandlesCount = copyHandlesCount,
MoveHandlesCount = moveHandlesCount MoveHandlesCount = moveHandlesCount
}); });
@@ -89,38 +87,37 @@ namespace Ryujinx.Horizon.Sdk.Sf.Hipc
public static HipcMessageData WriteMessage(Span<byte> destination, HipcMetadata meta) public static HipcMessageData WriteMessage(Span<byte> destination, HipcMetadata meta)
{ {
int initialLength = destination.Length; int initialLength = destination.Length;
bool hasSpecialHeader = meta.SendPid || meta.CopyHandlesCount != 0 || meta.MoveHandlesCount != 0; bool hasSpecialHeader = meta.SendPid || meta.CopyHandlesCount != 0 || meta.MoveHandlesCount != 0;
MemoryMarshal.Cast<byte, Header>(destination)[0] = new Header() MemoryMarshal.Cast<byte, Header>(destination)[0] = new Header()
{ {
Type = (CommandType)meta.Type, Type = (CommandType)meta.Type,
SendStaticsCount = meta.SendStaticsCount, SendStaticsCount = meta.SendStaticsCount,
SendBuffersCount = meta.SendBuffersCount, SendBuffersCount = meta.SendBuffersCount,
ReceiveBuffersCount = meta.ReceiveBuffersCount, ReceiveBuffersCount = meta.ReceiveBuffersCount,
ExchangeBuffersCount = meta.ExchangeBuffersCount, ExchangeBuffersCount = meta.ExchangeBuffersCount,
DataWordsCount = meta.DataWordsCount, DataWordsCount = meta.DataWordsCount,
ReceiveStaticMode = meta.ReceiveStaticsCount != 0 ? (meta.ReceiveStaticsCount != AutoReceiveStatic ? meta.ReceiveStaticsCount + 2 : 2) : 0, ReceiveStaticMode = meta.ReceiveStaticsCount != 0 ? (meta.ReceiveStaticsCount != AutoReceiveStatic ? meta.ReceiveStaticsCount + 2 : 2) : 0,
HasSpecialHeader = hasSpecialHeader HasSpecialHeader = hasSpecialHeader
}; };
destination = destination.Slice(Unsafe.SizeOf<Header>()); destination = destination[Unsafe.SizeOf<Header>()..];
if (hasSpecialHeader) if (hasSpecialHeader)
{ {
MemoryMarshal.Cast<byte, SpecialHeader>(destination)[0] = new SpecialHeader() MemoryMarshal.Cast<byte, SpecialHeader>(destination)[0] = new SpecialHeader()
{ {
SendPid = meta.SendPid, SendPid = meta.SendPid,
CopyHandlesCount = meta.CopyHandlesCount, CopyHandlesCount = meta.CopyHandlesCount,
MoveHandlesCount = meta.MoveHandlesCount MoveHandlesCount = meta.MoveHandlesCount
}; };
destination = destination.Slice(Unsafe.SizeOf<SpecialHeader>()); destination = destination[Unsafe.SizeOf<SpecialHeader>()..];
if (meta.SendPid) if (meta.SendPid)
{ {
destination = destination.Slice(sizeof(ulong)); destination = destination[sizeof(ulong)..];
} }
} }
@@ -133,68 +130,67 @@ namespace Ryujinx.Horizon.Sdk.Sf.Hipc
if (meta.CopyHandlesCount != 0) if (meta.CopyHandlesCount != 0)
{ {
copyHandles = MemoryMarshal.Cast<byte, int>(data).Slice(0, meta.CopyHandlesCount); copyHandles = MemoryMarshal.Cast<byte, int>(data)[..meta.CopyHandlesCount];
data = data.Slice(meta.CopyHandlesCount * sizeof(int)); data = data[(meta.CopyHandlesCount * sizeof(int))..];
} }
Span<int> moveHandles = Span<int>.Empty; Span<int> moveHandles = Span<int>.Empty;
if (meta.MoveHandlesCount != 0) if (meta.MoveHandlesCount != 0)
{ {
moveHandles = MemoryMarshal.Cast<byte, int>(data).Slice(0, meta.MoveHandlesCount); moveHandles = MemoryMarshal.Cast<byte, int>(data)[..meta.MoveHandlesCount];
data = data.Slice(meta.MoveHandlesCount * sizeof(int)); data = data[(meta.MoveHandlesCount * sizeof(int))..];
} }
Span<HipcStaticDescriptor> sendStatics = Span<HipcStaticDescriptor>.Empty; Span<HipcStaticDescriptor> sendStatics = Span<HipcStaticDescriptor>.Empty;
if (meta.SendStaticsCount != 0) if (meta.SendStaticsCount != 0)
{ {
sendStatics = MemoryMarshal.Cast<byte, HipcStaticDescriptor>(data).Slice(0, meta.SendStaticsCount); sendStatics = MemoryMarshal.Cast<byte, HipcStaticDescriptor>(data)[..meta.SendStaticsCount];
data = data.Slice(meta.SendStaticsCount * Unsafe.SizeOf<HipcStaticDescriptor>()); data = data[(meta.SendStaticsCount * Unsafe.SizeOf<HipcStaticDescriptor>())..];
} }
Span<HipcBufferDescriptor> sendBuffers = Span<HipcBufferDescriptor>.Empty; Span<HipcBufferDescriptor> sendBuffers = Span<HipcBufferDescriptor>.Empty;
if (meta.SendBuffersCount != 0) if (meta.SendBuffersCount != 0)
{ {
sendBuffers = MemoryMarshal.Cast<byte, HipcBufferDescriptor>(data).Slice(0, meta.SendBuffersCount); sendBuffers = MemoryMarshal.Cast<byte, HipcBufferDescriptor>(data)[..meta.SendBuffersCount];
data = data.Slice(meta.SendBuffersCount * Unsafe.SizeOf<HipcBufferDescriptor>()); data = data[(meta.SendBuffersCount * Unsafe.SizeOf<HipcBufferDescriptor>())..];
} }
Span<HipcBufferDescriptor> receiveBuffers = Span<HipcBufferDescriptor>.Empty; Span<HipcBufferDescriptor> receiveBuffers = Span<HipcBufferDescriptor>.Empty;
if (meta.ReceiveBuffersCount != 0) if (meta.ReceiveBuffersCount != 0)
{ {
receiveBuffers = MemoryMarshal.Cast<byte, HipcBufferDescriptor>(data).Slice(0, meta.ReceiveBuffersCount); receiveBuffers = MemoryMarshal.Cast<byte, HipcBufferDescriptor>(data)[..meta.ReceiveBuffersCount];
data = data.Slice(meta.ReceiveBuffersCount * Unsafe.SizeOf<HipcBufferDescriptor>()); data = data[(meta.ReceiveBuffersCount * Unsafe.SizeOf<HipcBufferDescriptor>())..];
} }
Span<HipcBufferDescriptor> exchangeBuffers = Span<HipcBufferDescriptor>.Empty; Span<HipcBufferDescriptor> exchangeBuffers = Span<HipcBufferDescriptor>.Empty;
if (meta.ExchangeBuffersCount != 0) if (meta.ExchangeBuffersCount != 0)
{ {
exchangeBuffers = MemoryMarshal.Cast<byte, HipcBufferDescriptor>(data).Slice(0, meta.ExchangeBuffersCount); exchangeBuffers = MemoryMarshal.Cast<byte, HipcBufferDescriptor>(data)[..meta.ExchangeBuffersCount];
data = data.Slice(meta.ExchangeBuffersCount * Unsafe.SizeOf<HipcBufferDescriptor>()); data = data[(meta.ExchangeBuffersCount * Unsafe.SizeOf<HipcBufferDescriptor>())..];
} }
Span<uint> dataWords = Span<uint>.Empty; Span<uint> dataWords = Span<uint>.Empty;
if (meta.DataWordsCount != 0) if (meta.DataWordsCount != 0)
{ {
int dataOffset = initialLength - data.Length; int dataOffset = initialLength - data.Length;
int dataOffsetAligned = BitUtils.AlignUp(dataOffset, 0x10); int dataOffsetAligned = BitUtils.AlignUp(dataOffset, 0x10);
int padding = (dataOffsetAligned - dataOffset) / sizeof(uint);
int padding = (dataOffsetAligned - dataOffset) / sizeof(uint); dataWords = MemoryMarshal.Cast<byte, uint>(data)[padding..meta.DataWordsCount];
dataWords = MemoryMarshal.Cast<byte, uint>(data).Slice(padding, meta.DataWordsCount - padding); data = data[(meta.DataWordsCount * sizeof(uint))..];
data = data.Slice(meta.DataWordsCount * sizeof(uint));
} }
Span<HipcReceiveListEntry> receiveList = Span<HipcReceiveListEntry>.Empty; Span<HipcReceiveListEntry> receiveList = Span<HipcReceiveListEntry>.Empty;
@@ -203,19 +199,19 @@ namespace Ryujinx.Horizon.Sdk.Sf.Hipc
{ {
int receiveListSize = meta.ReceiveStaticsCount == AutoReceiveStatic ? 1 : meta.ReceiveStaticsCount; int receiveListSize = meta.ReceiveStaticsCount == AutoReceiveStatic ? 1 : meta.ReceiveStaticsCount;
receiveList = MemoryMarshal.Cast<byte, HipcReceiveListEntry>(data).Slice(0, receiveListSize); receiveList = MemoryMarshal.Cast<byte, HipcReceiveListEntry>(data)[..receiveListSize];
} }
return new HipcMessageData() return new HipcMessageData()
{ {
SendStatics = sendStatics, SendStatics = sendStatics,
SendBuffers = sendBuffers, SendBuffers = sendBuffers,
ReceiveBuffers = receiveBuffers, ReceiveBuffers = receiveBuffers,
ExchangeBuffers = exchangeBuffers, ExchangeBuffers = exchangeBuffers,
DataWords = dataWords, DataWords = dataWords,
ReceiveList = receiveList, ReceiveList = receiveList,
CopyHandles = copyHandles, CopyHandles = copyHandles,
MoveHandles = moveHandles MoveHandles = moveHandles
}; };
} }
} }

View File

@@ -8,9 +8,9 @@ namespace Ryujinx.Horizon.Sdk.Sf.Hipc
public Span<HipcBufferDescriptor> SendBuffers; public Span<HipcBufferDescriptor> SendBuffers;
public Span<HipcBufferDescriptor> ReceiveBuffers; public Span<HipcBufferDescriptor> ReceiveBuffers;
public Span<HipcBufferDescriptor> ExchangeBuffers; public Span<HipcBufferDescriptor> ExchangeBuffers;
public Span<uint> DataWords; public Span<uint> DataWords;
public Span<HipcReceiveListEntry> ReceiveList; public Span<HipcReceiveListEntry> ReceiveList;
public Span<int> CopyHandles; public Span<int> CopyHandles;
public Span<int> MoveHandles; public Span<int> MoveHandles;
} }
} }

View File

@@ -2,15 +2,15 @@
{ {
struct HipcMetadata struct HipcMetadata
{ {
public int Type; public int Type;
public int SendStaticsCount; public int SendStaticsCount;
public int SendBuffersCount; public int SendBuffersCount;
public int ReceiveBuffersCount; public int ReceiveBuffersCount;
public int ExchangeBuffersCount; public int ExchangeBuffersCount;
public int DataWordsCount; public int DataWordsCount;
public int ReceiveStaticsCount; public int ReceiveStaticsCount;
public bool SendPid; public bool SendPid;
public int CopyHandlesCount; public int CopyHandlesCount;
public int MoveHandlesCount; public int MoveHandlesCount;
} }
} }

View File

@@ -8,7 +8,7 @@
public HipcReceiveListEntry(ulong address, ulong size) public HipcReceiveListEntry(ulong address, ulong size)
{ {
_addressLow = (uint)address; _addressLow = (uint)address;
_word1 = (ushort)(address >> 32) | (uint)(size << 16); _word1 = (ushort)(address >> 32) | (uint)(size << 16);
} }
} }
} }

View File

@@ -6,17 +6,14 @@ namespace Ryujinx.Horizon.Sdk.Sf.Hipc
{ {
public const int ModuleId = 11; public const int ModuleId = 11;
public static Result OutOfSessionMemory => new Result(ModuleId, 102); public static Result OutOfSessionMemory => new(ModuleId, 102);
public static Result OutOfSessions => new Result(ModuleId, 131); public static Result OutOfSessions => new(ModuleId, 131);
public static Result PointerBufferTooSmall => new Result(ModuleId, 141); public static Result PointerBufferTooSmall => new(ModuleId, 141);
public static Result OutOfDomains => new Result(ModuleId, 200); public static Result OutOfDomains => new(ModuleId, 200);
public static Result InvalidRequestSize => new(ModuleId, 402);
public static Result InvalidRequestSize => new Result(ModuleId, 402); public static Result UnknownCommandType => new(ModuleId, 403);
public static Result UnknownCommandType => new Result(ModuleId, 403); public static Result InvalidCmifRequest => new(ModuleId, 420);
public static Result TargetNotDomain => new(ModuleId, 491);
public static Result InvalidCmifRequest => new Result(ModuleId, 420); public static Result DomainObjectNotFound => new(ModuleId, 492);
public static Result TargetNotDomain => new Result(ModuleId, 491);
public static Result DomainObjectNotFound => new Result(ModuleId, 492);
} }
} }

View File

@@ -4,9 +4,9 @@
{ {
private readonly ulong _data; private readonly ulong _data;
public ulong Address => ((((_data >> 2) & 0x70) | ((_data >> 12) & 0xf)) << 32) | (_data >> 32); public ulong Address => ((((_data >> 2) & 0x70) | ((_data >> 12) & 0xf)) << 32) | (_data >> 32);
public ushort Size => (ushort)(_data >> 16); public ushort Size => (ushort)(_data >> 16);
public int ReceiveIndex => (int)(_data & 0xf); public int ReceiveIndex => (int)(_data & 0xf);
public HipcStaticDescriptor(ulong address, ushort size, int receiveIndex) public HipcStaticDescriptor(ulong address, ushort size, int receiveIndex)
{ {

View File

@@ -2,18 +2,18 @@
{ {
struct ManagerOptions struct ManagerOptions
{ {
public static ManagerOptions Default => new ManagerOptions(0, 0, 0, false); public static ManagerOptions Default => new(0, 0, 0, false);
public int PointerBufferSize { get; } public int PointerBufferSize { get; }
public int MaxDomains { get; } public int MaxDomains { get; }
public int MaxDomainObjects { get; } public int MaxDomainObjects { get; }
public bool CanDeferInvokeRequest { get; } public bool CanDeferInvokeRequest { get; }
public ManagerOptions(int pointerBufferSize, int maxDomains, int maxDomainObjects, bool canDeferInvokeRequest) public ManagerOptions(int pointerBufferSize, int maxDomains, int maxDomainObjects, bool canDeferInvokeRequest)
{ {
PointerBufferSize = pointerBufferSize; PointerBufferSize = pointerBufferSize;
MaxDomains = maxDomains; MaxDomains = maxDomains;
MaxDomainObjects = maxDomainObjects; MaxDomainObjects = maxDomainObjects;
CanDeferInvokeRequest = canDeferInvokeRequest; CanDeferInvokeRequest = canDeferInvokeRequest;
} }
} }

View File

@@ -6,22 +6,22 @@ namespace Ryujinx.Horizon.Sdk.Sf.Hipc
{ {
class Server : MultiWaitHolderOfHandle class Server : MultiWaitHolderOfHandle
{ {
public int PortIndex { get; } public int PortIndex { get; }
public int PortHandle { get; } public int PortHandle { get; }
public ServiceName Name { get; } public ServiceName Name { get; }
public bool Managed { get; } public bool Managed { get; }
public ServiceObjectHolder StaticObject { get; } public ServiceObjectHolder StaticObject { get; }
public Server( public Server(
int portIndex, int portIndex,
int portHandle, int portHandle,
ServiceName name, ServiceName name,
bool managed, bool managed,
ServiceObjectHolder staticHoder) : base(portHandle) ServiceObjectHolder staticHoder) : base(portHandle)
{ {
PortHandle = portHandle; PortHandle = portHandle;
Name = name; Name = name;
Managed = managed; Managed = managed;
if (staticHoder != null) if (staticHoder != null)
{ {

View File

@@ -15,7 +15,7 @@ namespace Ryujinx.Horizon.Sdk.Sf.Hipc
protected override Result DispatchManagerRequest(ServerSession session, Span<byte> inMessage, Span<byte> outMessage) protected override Result DispatchManagerRequest(ServerSession session, Span<byte> inMessage, Span<byte> outMessage)
{ {
HipcManager hipcManager = new HipcManager(this, session); HipcManager hipcManager = new(this, session);
return DispatchRequest(new ServiceObjectHolder(hipcManager), session, inMessage, outMessage); return DispatchRequest(new ServiceObjectHolder(hipcManager), session, inMessage, outMessage);
} }

View File

@@ -80,7 +80,7 @@ namespace Ryujinx.Horizon.Sdk.Sf.Hipc
return null; return null;
} }
ServerSession session = new ServerSession(sessionIndex, sessionHandle, obj); ServerSession session = new(sessionIndex, sessionHandle, obj);
_sessions.Add(session); _sessions.Add(session);
@@ -111,7 +111,7 @@ namespace Ryujinx.Horizon.Sdk.Sf.Hipc
{ {
lock (_resourceLock) lock (_resourceLock)
{ {
Server server = new Server(portIndex, portHandle, name, managed, staticHoder); Server server = new(portIndex, portHandle, name, managed, staticHoder);
_servers.Add(server); _servers.Add(server);

View File

@@ -26,7 +26,7 @@ namespace Ryujinx.Horizon.Sdk.Sf.Hipc
private enum UserDataTag private enum UserDataTag
{ {
Server = 1, Server = 1,
Session = 2 Session = 2
} }
@@ -36,16 +36,17 @@ namespace Ryujinx.Horizon.Sdk.Sf.Hipc
_canDeferInvokeRequest = options.CanDeferInvokeRequest; _canDeferInvokeRequest = options.CanDeferInvokeRequest;
_multiWait = new MultiWait(); _multiWait = new MultiWait();
_waitList = new MultiWait(); _waitList = new MultiWait();
_multiWaitSelectionLock = new object(); _multiWaitSelectionLock = new object();
_waitListLock = new object(); _waitListLock = new object();
_requestStopEvent = new Event(EventClearMode.ManualClear); _requestStopEvent = new Event(EventClearMode.ManualClear);
_notifyEvent = new Event(EventClearMode.ManualClear); _notifyEvent = new Event(EventClearMode.ManualClear);
_requestStopEventHolder = new MultiWaitHolderOfEvent(_requestStopEvent); _requestStopEventHolder = new MultiWaitHolderOfEvent(_requestStopEvent);
_multiWait.LinkMultiWaitHolder(_requestStopEventHolder); _multiWait.LinkMultiWaitHolder(_requestStopEventHolder);
_notifyEventHolder = new MultiWaitHolderOfEvent(_notifyEvent); _notifyEventHolder = new MultiWaitHolderOfEvent(_notifyEvent);
_multiWait.LinkMultiWaitHolder(_notifyEventHolder); _multiWait.LinkMultiWaitHolder(_notifyEventHolder);
} }
@@ -73,6 +74,7 @@ namespace Ryujinx.Horizon.Sdk.Sf.Hipc
private void RegisterServerImpl(int portIndex, ServiceObjectHolder staticHolder, int portHandle) private void RegisterServerImpl(int portIndex, ServiceObjectHolder staticHolder, int portHandle)
{ {
Server server = AllocateServer(portIndex, portHandle, ServiceName.Invalid, managed: false, staticHolder); Server server = AllocateServer(portIndex, portHandle, ServiceName.Invalid, managed: false, staticHolder);
RegisterServerImpl(server); RegisterServerImpl(server);
} }
@@ -86,6 +88,7 @@ namespace Ryujinx.Horizon.Sdk.Sf.Hipc
} }
Server server = AllocateServer(portIndex, portHandle, name, managed: true, staticHolder); Server server = AllocateServer(portIndex, portHandle, name, managed: true, staticHolder);
RegisterServerImpl(server); RegisterServerImpl(server);
return Result.Success; return Result.Success;
@@ -103,6 +106,11 @@ namespace Ryujinx.Horizon.Sdk.Sf.Hipc
throw new NotSupportedException(); throw new NotSupportedException();
} }
protected Result AcceptImpl(Server server, IServiceObject obj)
{
return AcceptSession(server.PortHandle, new ServiceObjectHolder(obj));
}
public void ServiceRequests() public void ServiceRequests()
{ {
while (WaitAndProcessRequestsImpl()); while (WaitAndProcessRequestsImpl());
@@ -175,7 +183,8 @@ namespace Ryujinx.Horizon.Sdk.Sf.Hipc
protected override void RegisterSessionToWaitList(ServerSession session) protected override void RegisterSessionToWaitList(ServerSession session)
{ {
session.HasReceived = false; session.HasReceived = false;
session.UserData = UserDataTag.Session; session.UserData = UserDataTag.Session;
RegisterToWaitList(session); RegisterToWaitList(session);
} }
@@ -198,15 +207,12 @@ namespace Ryujinx.Horizon.Sdk.Sf.Hipc
private Result Process(MultiWaitHolder holder) private Result Process(MultiWaitHolder holder)
{ {
switch ((UserDataTag)holder.UserData) return (UserDataTag)holder.UserData switch
{ {
case UserDataTag.Server: UserDataTag.Server => ProcessForServer(holder),
return ProcessForServer(holder); UserDataTag.Session => ProcessForSession(holder),
case UserDataTag.Session: _ => throw new NotImplementedException(((UserDataTag)holder.UserData).ToString())
return ProcessForSession(holder); };
default:
throw new NotImplementedException(((UserDataTag)holder.UserData).ToString());
}
} }
private Result ProcessForServer(MultiWaitHolder holder) private Result ProcessForServer(MultiWaitHolder holder)
@@ -259,6 +265,7 @@ namespace Ryujinx.Horizon.Sdk.Sf.Hipc
} }
session.HasReceived = true; session.HasReceived = true;
tlsMessage.Memory.Span.CopyTo(savedMessage.Memory.Span); tlsMessage.Memory.Span.CopyTo(savedMessage.Memory.Span);
} }
else else

View File

@@ -6,18 +6,18 @@ namespace Ryujinx.Horizon.Sdk.Sf.Hipc
class ServerSession : MultiWaitHolderOfHandle class ServerSession : MultiWaitHolderOfHandle
{ {
public ServiceObjectHolder ServiceObjectHolder { get; set; } public ServiceObjectHolder ServiceObjectHolder { get; set; }
public PointerAndSize PointerBuffer { get; set; } public PointerAndSize PointerBuffer { get; set; }
public PointerAndSize SavedMessage { get; set; } public PointerAndSize SavedMessage { get; set; }
public int SessionIndex { get; } public int SessionIndex { get; }
public int SessionHandle { get; } public int SessionHandle { get; }
public bool IsClosed { get; set; } public bool IsClosed { get; set; }
public bool HasReceived { get; set; } public bool HasReceived { get; set; }
public ServerSession(int index, int handle, ServiceObjectHolder obj) : base(handle) public ServerSession(int index, int handle, ServiceObjectHolder obj) : base(handle)
{ {
ServiceObjectHolder = obj; ServiceObjectHolder = obj;
SessionIndex = index; SessionIndex = index;
SessionHandle = handle; SessionHandle = handle;
} }
} }
} }

View File

@@ -75,9 +75,10 @@ namespace Ryujinx.Horizon.Sdk.Sf.Hipc
} }
session.PointerBuffer = GetSessionPointerBuffer(session); session.PointerBuffer = GetSessionPointerBuffer(session);
session.SavedMessage = GetSessionSavedMessageBuffer(session); session.SavedMessage = GetSessionSavedMessageBuffer(session);
RegisterSessionToWaitList(session); RegisterSessionToWaitList(session);
return Result.Success; return Result.Success;
} }
@@ -109,10 +110,10 @@ namespace Ryujinx.Horizon.Sdk.Sf.Hipc
} }
protected virtual Server AllocateServer( protected virtual Server AllocateServer(
int portIndex, int portIndex,
int portHandle, int portHandle,
ServiceName name, ServiceName name,
bool managed, bool managed,
ServiceObjectHolder staticHoder) ServiceObjectHolder staticHoder)
{ {
throw new NotSupportedException(); throw new NotSupportedException();
@@ -141,6 +142,7 @@ namespace Ryujinx.Horizon.Sdk.Sf.Hipc
protected void CloseSessionImpl(ServerSession session) protected void CloseSessionImpl(ServerSession session)
{ {
int sessionHandle = session.Handle; int sessionHandle = session.Handle;
Os.FinalizeMultiWaitHolder(session); Os.FinalizeMultiWaitHolder(session);
DestroySession(session); DestroySession(session);
HorizonStatic.Syscall.CloseHandle(sessionHandle).AbortOnFailure(); HorizonStatic.Syscall.CloseHandle(sessionHandle).AbortOnFailure();
@@ -156,6 +158,7 @@ namespace Ryujinx.Horizon.Sdk.Sf.Hipc
if (session.IsClosed || GetCmifCommandType(message) == CommandType.Close) if (session.IsClosed || GetCmifCommandType(message) == CommandType.Close)
{ {
CloseSessionImpl(session); CloseSessionImpl(session);
return Result.Success; return Result.Success;
} }
else else
@@ -165,6 +168,7 @@ namespace Ryujinx.Horizon.Sdk.Sf.Hipc
if (result.IsSuccess) if (result.IsSuccess)
{ {
RegisterSessionToWaitList(session); RegisterSessionToWaitList(session);
return Result.Success; return Result.Success;
} }
else if (SfResult.RequestContextChanged(result)) else if (SfResult.RequestContextChanged(result))
@@ -176,6 +180,7 @@ namespace Ryujinx.Horizon.Sdk.Sf.Hipc
Logger.Warning?.Print(LogClass.KernelIpc, $"Request processing returned error {result}"); Logger.Warning?.Print(LogClass.KernelIpc, $"Request processing returned error {result}");
CloseSessionImpl(session); CloseSessionImpl(session);
return Result.Success; return Result.Success;
} }
} }
@@ -197,8 +202,8 @@ namespace Ryujinx.Horizon.Sdk.Sf.Hipc
return DispatchManagerRequest(session, inMessage, outMessage); return DispatchManagerRequest(session, inMessage, outMessage);
default: default:
return HipcResult.UnknownCommandType; return HipcResult.UnknownCommandType;
}
} }
}
private static int GetInlineContext(CommandType commandType, ReadOnlySpan<byte> inMessage) private static int GetInlineContext(CommandType commandType, ReadOnlySpan<byte> inMessage)
{ {
@@ -231,7 +236,7 @@ namespace Ryujinx.Horizon.Sdk.Sf.Hipc
{ {
HipcMessageData messageData = HipcMessage.WriteMessage(message, new HipcMetadata() HipcMessageData messageData = HipcMessage.WriteMessage(message, new HipcMetadata()
{ {
Type = (int)CommandType.Invalid, Type = (int)CommandType.Invalid,
ReceiveStaticsCount = HipcMessage.AutoReceiveStatic ReceiveStaticsCount = HipcMessage.AutoReceiveStatic
}); });
@@ -271,9 +276,9 @@ namespace Ryujinx.Horizon.Sdk.Sf.Hipc
protected virtual Result DispatchRequest( protected virtual Result DispatchRequest(
ServiceObjectHolder objectHolder, ServiceObjectHolder objectHolder,
ServerSession session, ServerSession session,
Span<byte> inMessage, Span<byte> inMessage,
Span<byte> outMessage) Span<byte> outMessage)
{ {
HipcMessage request; HipcMessage request;
@@ -288,14 +293,14 @@ namespace Ryujinx.Horizon.Sdk.Sf.Hipc
var dispatchCtx = new ServiceDispatchContext() var dispatchCtx = new ServiceDispatchContext()
{ {
ServiceObject = objectHolder.ServiceObject, ServiceObject = objectHolder.ServiceObject,
Manager = this, Manager = this,
Session = session, Session = session,
HandlesToClose = new HandlesToClose(), HandlesToClose = new HandlesToClose(),
PointerBuffer = session.PointerBuffer, PointerBuffer = session.PointerBuffer,
InMessageBuffer = inMessage, InMessageBuffer = inMessage,
OutMessageBuffer = outMessage, OutMessageBuffer = outMessage,
Request = request Request = request
}; };
ReadOnlySpan<byte> inRawData = MemoryMarshal.Cast<uint, byte>(dispatchCtx.Request.Data.DataWords); ReadOnlySpan<byte> inRawData = MemoryMarshal.Cast<uint, byte>(dispatchCtx.Request.Data.DataWords);

View File

@@ -136,9 +136,9 @@ namespace Ryujinx.Horizon.Sdk.Sf
ulong pointerBufferTail = context.PointerBuffer.Address; ulong pointerBufferTail = context.PointerBuffer.Address;
ulong pointerBufferHead = pointerBufferTail + context.PointerBuffer.Size; ulong pointerBufferHead = pointerBufferTail + context.PointerBuffer.Size;
int sendMapAliasIndex = 0; int sendMapAliasIndex = 0;
int recvMapAliasIndex = 0; int recvMapAliasIndex = 0;
int sendPointerIndex = 0; int sendPointerIndex = 0;
int unfixedRecvPointerIndex = 0; int unfixedRecvPointerIndex = 0;
for (int i = 0; i < _args.Length; i++) for (int i = 0; i < _args.Length; i++)
@@ -188,8 +188,9 @@ namespace Ryujinx.Horizon.Sdk.Sf
if (flags.HasFlag(HipcBufferFlags.In)) if (flags.HasFlag(HipcBufferFlags.In))
{ {
var descriptor = context.Request.Data.SendStatics[sendPointerIndex++]; var descriptor = context.Request.Data.SendStatics[sendPointerIndex++];
ulong address = descriptor.Address; ulong address = descriptor.Address;
ulong size = descriptor.Size; ulong size = descriptor.Size;
_bufferRanges[i] = new PointerAndSize(address, size); _bufferRanges[i] = new PointerAndSize(address, size);
if (size != 0) if (size != 0)
@@ -207,13 +208,14 @@ namespace Ryujinx.Horizon.Sdk.Sf
} }
else else
{ {
var data = MemoryMarshal.Cast<uint, byte>(context.Request.Data.DataWords); var data = MemoryMarshal.Cast<uint, byte>(context.Request.Data.DataWords);
var recvPointerSizes = MemoryMarshal.Cast<byte, ushort>(data.Slice(runtimeMetadata.UnfixedOutPointerSizeOffset)); var recvPointerSizes = MemoryMarshal.Cast<byte, ushort>(data[runtimeMetadata.UnfixedOutPointerSizeOffset..]);
size = recvPointerSizes[unfixedRecvPointerIndex++]; size = recvPointerSizes[unfixedRecvPointerIndex++];
} }
pointerBufferHead = BitUtils.AlignDown(pointerBufferHead - size, 0x10UL); pointerBufferHead = BitUtils.AlignDown(pointerBufferHead - size, 0x10UL);
_bufferRanges[i] = new PointerAndSize(pointerBufferHead, size); _bufferRanges[i] = new PointerAndSize(pointerBufferHead, size);
} }
} }
} }
@@ -304,16 +306,17 @@ namespace Ryujinx.Horizon.Sdk.Sf
{ {
ref var meta = ref context.Request.Meta; ref var meta = ref context.Request.Meta;
bool requestValid = true; bool requestValid = true;
requestValid &= meta.SendPid == _hasInProcessIdHolder; requestValid &= meta.SendPid == _hasInProcessIdHolder;
requestValid &= meta.SendStaticsCount == _inPointerBuffersCount; requestValid &= meta.SendStaticsCount == _inPointerBuffersCount;
requestValid &= meta.SendBuffersCount == _inMapAliasBuffersCount; requestValid &= meta.SendBuffersCount == _inMapAliasBuffersCount;
requestValid &= meta.ReceiveBuffersCount == _outMapAliasBuffersCount; requestValid &= meta.ReceiveBuffersCount == _outMapAliasBuffersCount;
requestValid &= meta.ExchangeBuffersCount == 0; requestValid &= meta.ExchangeBuffersCount == 0;
requestValid &= meta.CopyHandlesCount == _inCopyHandlesCount; requestValid &= meta.CopyHandlesCount == _inCopyHandlesCount;
requestValid &= meta.MoveHandlesCount == _inMoveHandlesCount; requestValid &= meta.MoveHandlesCount == _inMoveHandlesCount;
int rawSizeInBytes = meta.DataWordsCount * sizeof(uint); int rawSizeInBytes = meta.DataWordsCount * sizeof(uint);
int commandRawSize = BitUtils.AlignUp(runtimeMetadata.UnfixedOutPointerSizeOffset + (OutUnfixedSizePointerBuffersCount * sizeof(ushort)), sizeof(uint)); int commandRawSize = BitUtils.AlignUp(runtimeMetadata.UnfixedOutPointerSizeOffset + (OutUnfixedSizePointerBuffersCount * sizeof(ushort)), sizeof(uint));
requestValid &= rawSizeInBytes >= commandRawSize; requestValid &= rawSizeInBytes >= commandRawSize;
return requestValid ? Result.Success : HipcResult.InvalidCmifRequest; return requestValid ? Result.Success : HipcResult.InvalidCmifRequest;
@@ -340,7 +343,7 @@ namespace Ryujinx.Horizon.Sdk.Sf
{ {
if (_args[i].Type == CommandArgType.InObject) if (_args[i].Type == CommandArgType.InObject)
{ {
int index = inObjectIndex++; int index = inObjectIndex++;
var inObject = inObjects[index]; var inObject = inObjects[index];
objects[index] = inObject?.ServiceObject; objects[index] = inObject?.ServiceObject;
@@ -365,6 +368,7 @@ namespace Ryujinx.Horizon.Sdk.Sf
_outCopyHandlesCount, _outCopyHandlesCount,
_outMoveHandlesCount + runtimeMetadata.OutObjectsCount); _outMoveHandlesCount + runtimeMetadata.OutObjectsCount);
outRawData = MemoryMarshal.Cast<uint, byte>(response.DataWords); outRawData = MemoryMarshal.Cast<uint, byte>(response.DataWords);
return response; return response;
} }
@@ -377,6 +381,7 @@ namespace Ryujinx.Horizon.Sdk.Sf
(BitUtils.AlignUp(rawDataSize, 4) + 0x10) / sizeof(uint), (BitUtils.AlignUp(rawDataSize, 4) + 0x10) / sizeof(uint),
0, 0,
0); 0);
outRawData = MemoryMarshal.Cast<uint, byte>(response.DataWords); outRawData = MemoryMarshal.Cast<uint, byte>(response.DataWords);
} }
@@ -410,6 +415,7 @@ namespace Ryujinx.Horizon.Sdk.Sf
if (obj == null) if (obj == null)
{ {
response.MoveHandles[index] = 0; response.MoveHandles[index] = 0;
return; return;
} }

View File

@@ -12,24 +12,22 @@ namespace Ryujinx.Horizon.Sdk.Sf
{ {
int argsCount = args.Length; int argsCount = args.Length;
int[] sizes = new int[argsCount]; int[] sizes = new int[argsCount];
int[] aligns = new int[argsCount]; int[] aligns = new int[argsCount];
int[] map = new int[argsCount]; int[] map = new int[argsCount];
for (int i = 0; i < argsCount; i++) for (int i = 0; i < argsCount; i++)
{ {
sizes[i] = args[i].ArgSize; sizes[i] = args[i].ArgSize;
aligns[i] = args[i].ArgAlignment; aligns[i] = args[i].ArgAlignment;
map[i] = i; map[i] = i;
} }
for (int i = 1; i < argsCount; i++) for (int i = 1; i < argsCount; i++)
{ {
for (int j = i; j > 0 && aligns[map[j - 1]] > aligns[map[j]]; j--) for (int j = i; j > 0 && aligns[map[j - 1]] > aligns[map[j]]; j--)
{ {
var temp = map[j - 1]; (map[j], map[j - 1]) = (map[j - 1], map[j]);
map[j - 1] = map[j];
map[j] = temp;
} }
} }
@@ -37,9 +35,9 @@ namespace Ryujinx.Horizon.Sdk.Sf
foreach (int i in map) foreach (int i in map)
{ {
offset = BitUtils.AlignUp(offset, aligns[i]); offset = BitUtils.AlignUp(offset, aligns[i]);
offsets[i] = offset; offsets[i] = offset;
offset += sizes[i]; offset += sizes[i];
} }
offsets[argsCount] = offset; offsets[argsCount] = offset;

View File

@@ -6,26 +6,22 @@ namespace Ryujinx.Horizon.Sdk.Sf
{ {
public const int ModuleId = 10; public const int ModuleId = 10;
public static Result NotSupported => new Result(ModuleId, 1); public static Result NotSupported => new(ModuleId, 1);
public static Result InvalidHeaderSize => new Result(ModuleId, 202); public static Result InvalidHeaderSize => new(ModuleId, 202);
public static Result InvalidInHeader => new Result(ModuleId, 211); public static Result InvalidInHeader => new(ModuleId, 211);
public static Result InvalidOutHeader => new Result(ModuleId, 212); public static Result InvalidOutHeader => new(ModuleId, 212);
public static Result UnknownCommandId => new Result(ModuleId, 221); public static Result UnknownCommandId => new(ModuleId, 221);
public static Result InvalidOutRawSize => new Result(ModuleId, 232); public static Result InvalidOutRawSize => new(ModuleId, 232);
public static Result InvalidInObjectsCount => new Result(ModuleId, 235); public static Result InvalidInObjectsCount => new(ModuleId, 235);
public static Result InvalidOutObjectsCount => new Result(ModuleId, 236); public static Result InvalidOutObjectsCount => new(ModuleId, 236);
public static Result InvalidInObject => new Result(ModuleId, 239); public static Result InvalidInObject => new(ModuleId, 239);
public static Result TargetNotFound => new(ModuleId, 261);
public static Result TargetNotFound => new Result(ModuleId, 261); public static Result OutOfDomainEntries => new(ModuleId, 301);
public static Result InvalidatedByUser => new(ModuleId, 802);
public static Result OutOfDomainEntries => new Result(ModuleId, 301); public static Result RequestDeferredByUser => new(ModuleId, 812);
public static Result InvalidatedByUser => new Result(ModuleId, 802);
public static Result RequestDeferredByUser => new Result(ModuleId, 812);
public static bool RequestContextChanged(Result result) => result.InRange(800, 899); public static bool RequestContextChanged(Result result) => result.InRange(800, 899);
public static bool Invalidated(Result result) => result.InRange(801, 809); public static bool Invalidated(Result result) => result.InRange(801, 809);
public static bool RequestDeferred(Result result) => result.InRange(811, 819);
public static bool RequestDeferred(Result result) => result.InRange(811, 819);
} }
} }

View File

@@ -0,0 +1,8 @@
using Ryujinx.Horizon.Sdk.Sf;
namespace Ryujinx.Horizon.Sdk.Sm
{
interface IManagerService : IServiceObject
{
}
}

View File

@@ -0,0 +1,13 @@
using Ryujinx.Horizon.Common;
using Ryujinx.Horizon.Sdk.Sf;
namespace Ryujinx.Horizon.Sdk.Sm
{
interface IUserService : IServiceObject
{
Result Initialize(ulong clientProcessId);
Result GetService(out int handle, ServiceName name);
Result RegisterService(out int handle, ServiceName name, int maxSessions, bool isLight);
Result UnregisterService(ServiceName name);
}
}

View File

@@ -7,16 +7,18 @@ namespace Ryujinx.Horizon.Sdk.Sm
{ {
class SmApi class SmApi
{ {
private const string SmName = "sm:";
private int _portHandle; private int _portHandle;
public Result Initialize() public Result Initialize()
{ {
Result result = HorizonStatic.Syscall.ConnectToNamedPort(out int portHandle, "sm:"); Result result = HorizonStatic.Syscall.ConnectToNamedPort(out int portHandle, SmName);
while (result == KernelResult.NotFound) while (result == KernelResult.NotFound)
{ {
HorizonStatic.Syscall.SleepThread(50000000L); HorizonStatic.Syscall.SleepThread(50000000L);
result = HorizonStatic.Syscall.ConnectToNamedPort(out portHandle, "sm:"); result = HorizonStatic.Syscall.ConnectToNamedPort(out portHandle, SmName);
} }
if (result.IsFailure) if (result.IsFailure)
@@ -33,7 +35,7 @@ namespace Ryujinx.Horizon.Sdk.Sm
{ {
Span<byte> data = stackalloc byte[8]; Span<byte> data = stackalloc byte[8];
SpanWriter writer = new SpanWriter(data); SpanWriter writer = new(data);
writer.Write(0UL); writer.Write(0UL);
@@ -44,7 +46,7 @@ namespace Ryujinx.Horizon.Sdk.Sm
{ {
Span<byte> data = stackalloc byte[8]; Span<byte> data = stackalloc byte[8];
SpanWriter writer = new SpanWriter(data); SpanWriter writer = new(data);
writer.Write(name); writer.Write(name);
@@ -53,10 +55,12 @@ namespace Ryujinx.Horizon.Sdk.Sm
if (result.IsFailure) if (result.IsFailure)
{ {
handle = 0; handle = 0;
return result; return result;
} }
handle = response.MoveHandles[0]; handle = response.MoveHandles[0];
return Result.Success; return Result.Success;
} }
@@ -64,7 +68,7 @@ namespace Ryujinx.Horizon.Sdk.Sm
{ {
Span<byte> data = stackalloc byte[16]; Span<byte> data = stackalloc byte[16];
SpanWriter writer = new SpanWriter(data); SpanWriter writer = new(data);
writer.Write(name); writer.Write(name);
writer.Write(isLight ? 1 : 0); writer.Write(isLight ? 1 : 0);
@@ -75,10 +79,12 @@ namespace Ryujinx.Horizon.Sdk.Sm
if (result.IsFailure) if (result.IsFailure)
{ {
handle = 0; handle = 0;
return result; return result;
} }
handle = response.MoveHandles[0]; handle = response.MoveHandles[0];
return Result.Success; return Result.Success;
} }
@@ -86,7 +92,7 @@ namespace Ryujinx.Horizon.Sdk.Sm
{ {
Span<byte> data = stackalloc byte[8]; Span<byte> data = stackalloc byte[8];
SpanWriter writer = new SpanWriter(data); SpanWriter writer = new(data);
writer.Write(name); writer.Write(name);
@@ -97,7 +103,7 @@ namespace Ryujinx.Horizon.Sdk.Sm
{ {
Span<byte> data = stackalloc byte[8]; Span<byte> data = stackalloc byte[8];
SpanWriter writer = new SpanWriter(data); SpanWriter writer = new(data);
writer.Write(0UL); writer.Write(0UL);

View File

@@ -6,20 +6,22 @@ namespace Ryujinx.Horizon
{ {
public struct ServiceEntry public struct ServiceEntry
{ {
private readonly Action _entrypoint; private readonly Action<ServiceTable> _entrypoint;
private readonly HorizonOptions _options; private readonly ServiceTable _serviceTable;
private readonly HorizonOptions _options;
internal ServiceEntry(Action entrypoint, HorizonOptions options) internal ServiceEntry(Action<ServiceTable> entrypoint, ServiceTable serviceTable, HorizonOptions options)
{ {
_entrypoint = entrypoint; _entrypoint = entrypoint;
_options = options; _serviceTable = serviceTable;
_options = options;
} }
public void Start(ISyscallApi syscallApi, IVirtualMemoryManager addressSpace, IThreadContext threadContext) public void Start(ISyscallApi syscallApi, IVirtualMemoryManager addressSpace, IThreadContext threadContext)
{ {
HorizonStatic.Register(_options, syscallApi, addressSpace, threadContext, (int)threadContext.GetX(1)); HorizonStatic.Register(_options, syscallApi, addressSpace, threadContext, (int)threadContext.GetX(1));
_entrypoint(); _entrypoint(_serviceTable);
} }
} }
} }

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