Compare commits

..

5 Commits

Author SHA1 Message Date
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
RMED24
cbaa845f5d Include a start.sh file with correct launch options (#4013)
* Include reference to start.sh to be bundled

* Add start.sh

* Fix silly mistake I made on windows-x64

* ... I cannot read properly

* Make same changes for avalonia csproj

* Remove notice from start.sh

Co-authored-by: Mary-nyan <thog@protonmail.com>

* Update Ryujinx/Ryujinx.csproj

Co-authored-by: Mary-nyan <thog@protonmail.com>

* Update Ryujinx.Ava/Ryujinx.Ava.csproj

Co-authored-by: Mary-nyan <thog@protonmail.com>

* Update distribution/linux/start.sh

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

* Update distribution/linux/start.sh

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

* Update Ryujinx.Ava/Ryujinx.Ava.csproj

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

* Update Ryujinx.csproj

* Update Ryujinx.Ava.csproj

* Rename start.sh to Ryujinx.sh

* Update Ryujinx.csproj

* Update Ryujinx.Ava.csproj

* Update Ryujinx.Ava.csproj

* Update Ryujinx.Ava/Ryujinx.Ava.csproj

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

* Add `GDK_BACKEND` variable

* Update Ryujinx.Ava.csproj

* Update Program.cs

* Update Program.cs

* Update Ryujinx.sh

* Update Program.cs

* linux: Register mime types on launch

* Add DOTNET_EnableAlternateStackCheck=1 to desktop file

* linux: Add exclusion for RegisterMimeTypes for flathub builds

* Update logo path

* Cleanup Ryujinx.sh

* Fix typo in ReleaseInformation

* gha: Fix permissions for linux release binaries

* ava: Rename output assembly to Ryujinx

* Update mime database after installing new types

Wait until logging is available before registering mime types

* Copy mime types to output directory

Co-authored-by: Mary-nyan <thog@protonmail.com>
Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>
2023-01-07 09:06:13 +01:00
Isaac Marovitz
9e2681f2d7 Ava GUI: AboutWindow Refactor (#4196)
* Start `AboutWindow` refactor

* Redesign

* Update logos + ViewModel

* Fix GTK

* Cleanup usings

* Fix mismatched font size

* Update LocaleKeys

* Block scoped namespace

* Fix appearence on German

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

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

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

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

* Move version number up

* Move see all contributors button left

* Add a couple `\n`

* Tooltips

* Layout fix

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

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

Co-authored-by: Ac_K <Acoustik666@gmail.com>
2023-01-07 01:29:18 +01:00
120 changed files with 1672 additions and 1196 deletions

View File

@@ -69,6 +69,12 @@ jobs:
- 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
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
uses: actions/upload-artifact@v3
with:

View File

@@ -38,19 +38,21 @@ jobs:
shell: bash
- name: Configure for release
run: |
sed -r --in-place 's/\%\%RYUJINX_BUILD_VERSION\%\%/${{ steps.version_info.outputs.build_version }}/g;' Ryujinx.Common/ReleaseInformations.cs
sed -r --in-place 's/\%\%RYUJINX_BUILD_GIT_HASH\%\%/${{ steps.version_info.outputs.git_short_hash }}/g;' Ryujinx.Common/ReleaseInformations.cs
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_NAME\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_NAME }}/g;' Ryujinx.Common/ReleaseInformations.cs
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_OWNER\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/g;' Ryujinx.Common/ReleaseInformations.cs
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_REPO\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/g;' Ryujinx.Common/ReleaseInformations.cs
sed -r --in-place 's/\%\%RYUJINX_BUILD_VERSION\%\%/${{ steps.version_info.outputs.build_version }}/g;' Ryujinx.Common/ReleaseInformation.cs
sed -r --in-place 's/\%\%RYUJINX_BUILD_GIT_HASH\%\%/${{ steps.version_info.outputs.git_short_hash }}/g;' Ryujinx.Common/ReleaseInformation.cs
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_NAME\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_NAME }}/g;' Ryujinx.Common/ReleaseInformation.cs
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_OWNER\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/g;' Ryujinx.Common/ReleaseInformation.cs
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_REPO\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/g;' Ryujinx.Common/ReleaseInformation.cs
shell: bash
- name: Create output dir
run: "mkdir release_output"
- name: Publish Windows
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_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
mv ./publish_windows_ava/publish/Ryujinx.Ava.exe ./publish_windows_ava/publish/Ryujinx.exe
- name: Packing Windows builds
run: |
pushd publish_windows
@@ -71,19 +73,28 @@ 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_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
mv ./publish_linux_ava/publish/Ryujinx.Ava ./publish_linux_ava/publish/Ryujinx
- name: Packing Linux builds
run: |
pushd publish_linux
tar -czvf ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-linux_x64.tar.gz publish
tar --exclude "publish/Ryujinx" -cvf ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-linux_x64.tar publish
python3 ../distribution/misc/add_tar_exec.py ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-linux_x64.tar "publish/Ryujinx" "publish/Ryujinx"
gzip -9 < ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-linux_x64.tar > ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-linux_x64.tar.gz
rm ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-linux_x64.tar
popd
pushd publish_linux_sdl2_headless
tar -czvf ../release_output/sdl2-ryujinx-headless-${{ steps.version_info.outputs.build_version }}-linux_x64.tar.gz publish
tar --exclude "publish/Ryujinx.Headless.SDL2" -cvf ../release_output/sdl2-ryujinx-headless-${{ steps.version_info.outputs.build_version }}-linux_x64.tar publish
python3 ../distribution/misc/add_tar_exec.py ../release_output/sdl2-ryujinx-headless-${{ steps.version_info.outputs.build_version }}-linux_x64.tar "publish/Ryujinx.Headless.SDL2" "publish/Ryujinx.Headless.SDL2"
gzip -9 < ../release_output/sdl2-ryujinx-headless-${{ steps.version_info.outputs.build_version }}-linux_x64.tar > ../release_output/sdl2-ryujinx-headless-${{ steps.version_info.outputs.build_version }}-linux_x64.tar.gz
rm ../release_output/sdl2-ryujinx-headless-${{ steps.version_info.outputs.build_version }}-linux_x64.tar
popd
pushd publish_linux_ava
tar -czvf ../release_output/test-ava-ryujinx-${{ steps.version_info.outputs.build_version }}-linux_x64.tar.gz publish
tar --exclude "publish/Ryujinx" -cvf ../release_output/test-ava-ryujinx-${{ steps.version_info.outputs.build_version }}-linux_x64.tar publish
python3 ../distribution/misc/add_tar_exec.py ../release_output/test-ava-ryujinx-${{ steps.version_info.outputs.build_version }}-linux_x64.tar "publish/Ryujinx" "publish/Ryujinx"
gzip -9 < ../release_output/test-ava-ryujinx-${{ steps.version_info.outputs.build_version }}-linux_x64.tar > ../release_output/test-ava-ryujinx-${{ steps.version_info.outputs.build_version }}-linux_x64.tar.gz
rm ../release_output/test-ava-ryujinx-${{ steps.version_info.outputs.build_version }}-linux_x64.tar
popd
shell: bash

View File

@@ -95,7 +95,7 @@ namespace Ryujinx.Modules
{
using (HttpClient jsonClient = ConstructHttpClient())
{
string buildInfoURL = $"{GitHubApiURL}/repos/{ReleaseInformations.ReleaseChannelOwner}/{ReleaseInformations.ReleaseChannelRepo}/releases/latest";
string buildInfoURL = $"{GitHubApiURL}/repos/{ReleaseInformation.ReleaseChannelOwner}/{ReleaseInformation.ReleaseChannelRepo}/releases/latest";
string fetchedJson = await jsonClient.GetStringAsync(buildInfoURL);
JObject jsonRoot = JObject.Parse(fetchedJson);
@@ -625,7 +625,7 @@ namespace Ryujinx.Modules
return false;
}
if (Program.Version.Contains("dirty") || !ReleaseInformations.IsValid())
if (Program.Version.Contains("dirty") || !ReleaseInformation.IsValid())
{
if (showWarnings)
{
@@ -640,7 +640,7 @@ namespace Ryujinx.Modules
#else
if (showWarnings)
{
if (ReleaseInformations.IsFlatHubBuild())
if (ReleaseInformation.IsFlatHubBuild())
{
ContentDialogHelper.CreateWarningDialog(LocaleManager.Instance[LocaleKeys.UpdaterDisabledWarningTitle], LocaleManager.Instance[LocaleKeys.DialogUpdaterFlatpakNotSupportedMessage]);
}

View File

@@ -13,8 +13,10 @@ using Ryujinx.Ui.Common;
using Ryujinx.Ui.Common.Configuration;
using Ryujinx.Ui.Common.Helper;
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Threading.Tasks;
namespace Ryujinx.Ava
@@ -32,9 +34,51 @@ namespace Ryujinx.Ava
private const uint MB_ICONWARNING = 0x30;
[SupportedOSPlatform("linux")]
static void RegisterMimeTypes()
{
if (ReleaseInformation.IsFlatHubBuild())
{
return;
}
string mimeDbPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".local", "share", "mime");
if (!File.Exists(Path.Combine(mimeDbPath, "packages", "Ryujinx.xml")))
{
string mimeTypesFile = Path.Combine(ReleaseInformation.GetBaseApplicationDirectory(), "mime", "Ryujinx.xml");
using Process mimeProcess = new();
mimeProcess.StartInfo.FileName = "xdg-mime";
mimeProcess.StartInfo.Arguments = $"install --novendor --mode user {mimeTypesFile}";
mimeProcess.Start();
mimeProcess.WaitForExit();
if (mimeProcess.ExitCode != 0)
{
Logger.Error?.PrintMsg(LogClass.Application, $"Unable to install mime types. Make sure xdg-utils is installed. Process exited with code: {mimeProcess.ExitCode}");
return;
}
using Process updateMimeProcess = new();
updateMimeProcess.StartInfo.FileName = "update-mime-database";
updateMimeProcess.StartInfo.Arguments = mimeDbPath;
updateMimeProcess.Start();
updateMimeProcess.WaitForExit();
if (updateMimeProcess.ExitCode != 0)
{
Logger.Error?.PrintMsg(LogClass.Application, $"Could not update local mime database. Process exited with code: {updateMimeProcess.ExitCode}");
}
}
}
public static void Main(string[] args)
{
Version = ReleaseInformations.GetVersion();
Version = ReleaseInformation.GetVersion();
if (OperatingSystem.IsWindows() && !OperatingSystem.IsWindowsVersionAtLeast(10, 0, 17134))
{
@@ -93,6 +137,12 @@ namespace Ryujinx.Ava
// Initialize the logger system.
LoggerModule.Initialize();
// Register mime types on linux.
if (OperatingSystem.IsLinux())
{
RegisterMimeTypes();
}
// Initialize Discord integration.
DiscordIntegrationModule.Initialize();

View File

@@ -76,6 +76,16 @@
</Content>
</ItemGroup>
<ItemGroup Condition="'$(RuntimeIdentifier)' == 'linux-x64'">
<Content Include="..\distribution\linux\Ryujinx.sh">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="..\distribution\linux\mime\Ryujinx.xml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<TargetPath>mime\Ryujinx.xml</TargetPath>
</Content>
</ItemGroup>
<ItemGroup>
<AvaloniaResource Include="Ui\**\*.xaml">
<SubType>Designer</SubType>

View File

@@ -0,0 +1,136 @@
using Avalonia;
using Avalonia.Media.Imaging;
using Avalonia.Threading;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Common.Utilities;
using Ryujinx.Ui.Common.Configuration;
using System;
using System.Net.Http;
using System.Net.NetworkInformation;
using System.Threading.Tasks;
namespace Ryujinx.Ava.UI.ViewModels
{
public class AboutWindowViewModel : BaseModel
{
private Bitmap _githubLogo;
private Bitmap _discordLogo;
private Bitmap _patreonLogo;
private Bitmap _twitterLogo;
private string _version;
private string _supporters;
public Bitmap GithubLogo
{
get => _githubLogo;
set
{
_githubLogo = value;
OnPropertyChanged();
}
}
public Bitmap DiscordLogo
{
get => _discordLogo;
set
{
_discordLogo = value;
OnPropertyChanged();
}
}
public Bitmap PatreonLogo
{
get => _patreonLogo;
set
{
_patreonLogo = value;
OnPropertyChanged();
}
}
public Bitmap TwitterLogo
{
get => _twitterLogo;
set
{
_twitterLogo = value;
OnPropertyChanged();
}
}
public string Supporters
{
get => _supporters;
set
{
_supporters = value;
OnPropertyChanged();
}
}
public string Version
{
get => _version;
set
{
_version = value;
OnPropertyChanged();
}
}
public string Developers
{
get => string.Format(LocaleManager.Instance[LocaleKeys.AboutPageDeveloperListMore], "gdkchan, Ac_K, marysaka, rip in peri peri, LDj3SNuD, emmaus, Thealexbarney, GoffyDude, TSRBerry, IsaacMarovitz");
}
public AboutWindowViewModel()
{
Version = Program.Version;
var assets = AvaloniaLocator.Current.GetService<Avalonia.Platform.IAssetLoader>();
if (ConfigurationState.Instance.Ui.BaseStyle.Value == "Light")
{
GithubLogo = new Bitmap(assets.Open(new Uri("resm:Ryujinx.Ui.Common.Resources.Logo_GitHub_Light.png?assembly=Ryujinx.Ui.Common")));
DiscordLogo = new Bitmap(assets.Open(new Uri("resm:Ryujinx.Ui.Common.Resources.Logo_Discord_Light.png?assembly=Ryujinx.Ui.Common")));
PatreonLogo = new Bitmap(assets.Open(new Uri("resm:Ryujinx.Ui.Common.Resources.Logo_Patreon_Light.png?assembly=Ryujinx.Ui.Common")));
TwitterLogo = new Bitmap(assets.Open(new Uri("resm:Ryujinx.Ui.Common.Resources.Logo_Twitter_Light.png?assembly=Ryujinx.Ui.Common")));
}
else
{
GithubLogo = new Bitmap(assets.Open(new Uri("resm:Ryujinx.Ui.Common.Resources.Logo_GitHub_Dark.png?assembly=Ryujinx.Ui.Common")));
DiscordLogo = new Bitmap(assets.Open(new Uri("resm:Ryujinx.Ui.Common.Resources.Logo_Discord_Dark.png?assembly=Ryujinx.Ui.Common")));
PatreonLogo = new Bitmap(assets.Open(new Uri("resm:Ryujinx.Ui.Common.Resources.Logo_Patreon_Dark.png?assembly=Ryujinx.Ui.Common")));
TwitterLogo = new Bitmap(assets.Open(new Uri("resm:Ryujinx.Ui.Common.Resources.Logo_Twitter_Dark.png?assembly=Ryujinx.Ui.Common")));
}
Dispatcher.UIThread.InvokeAsync(DownloadPatronsJson);
}
private async Task DownloadPatronsJson()
{
if (!NetworkInterface.GetIsNetworkAvailable())
{
Supporters = LocaleManager.Instance[LocaleKeys.ConnectionError];
return;
}
HttpClient httpClient = new();
try
{
string patreonJsonString = await httpClient.GetStringAsync("https://patreon.ryujinx.org/");
Supporters = string.Join(", ", JsonHelper.Deserialize<string[]>(patreonJsonString)) + "\n\n";
}
catch
{
Supporters = LocaleManager.Instance[LocaleKeys.ApiError];
}
}
}
}

View File

@@ -940,7 +940,7 @@ namespace Ryujinx.Ava.UI.ViewModels
public static void OpenLogsFolder()
{
string logPath = Path.Combine(ReleaseInformations.GetBaseApplicationDirectory(), "Logs");
string logPath = Path.Combine(ReleaseInformation.GetBaseApplicationDirectory(), "Logs");
new DirectoryInfo(logPath).Create();
@@ -1018,7 +1018,7 @@ namespace Ryujinx.Ava.UI.ViewModels
public async void OpenAboutWindow()
{
await new AboutWindow().ShowDialog(_owner);
await AboutWindow.Show();
}
public void ChangeLanguage(object obj)

View File

@@ -1,282 +1,253 @@
<window:StyleableWindow
x:Class="Ryujinx.Ava.UI.Windows.AboutWindow"
<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:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:flex="clr-namespace:Avalonia.Flexbox;assembly=Avalonia.Flexbox"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:viewModel="clr-namespace:Ryujinx.Ava.UI.ViewModels"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:window="clr-namespace:Ryujinx.Ava.UI.Windows"
Width="850"
Height="550"
MinWidth="500"
MinHeight="550"
d:DesignHeight="350"
d:DesignWidth="400"
CanResize="False"
SizeToContent="Width"
WindowStartupLocation="CenterOwner"
mc:Ignorable="d"
d:DesignHeight="260"
d:DesignWidth="550"
Height="260"
Width="550"
x:Class="Ryujinx.Ava.UI.Windows.AboutWindow"
x:DataType="viewModel:AboutWindowViewModel"
x:CompileBindings="True"
Margin="0 -12 0 0"
Focusable="True">
<Design.DataContext>
<viewModel:AboutWindowViewModel />
</Design.DataContext>
<Grid
Margin="15"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid
Grid.Row="1"
Grid.Column="0"
Margin="20"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition />
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<StackPanel
Grid.Row="0"
Spacing="10"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Image
Grid.Row="0"
Grid.RowSpan="3"
Grid.Column="0"
Height="110"
MinWidth="50"
Margin="5,10,20,10"
Height="80"
Source="resm:Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png?assembly=Ryujinx.Ui.Common" />
<flex:FlexPanel
Grid.Column="2"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Direction="Column"
JustifyContent="SpaceAround"
RowSpacing="2">
<TextBlock
Grid.Row="0"
Grid.Column="1"
Margin="0,20,0,0"
FontSize="35"
FontSize="28"
FontWeight="Bold"
Text="Ryujinx"
TextAlignment="Center" />
TextAlignment="Left" />
<TextBlock
Grid.Row="1"
Grid.Column="1"
Margin="0,0,0,0"
FontSize="16"
Text="(REE-YOU-JINX)"
TextAlignment="Center" />
<Button
Grid.Row="2"
Grid.Column="1"
Margin="0"
HorizontalAlignment="Center"
Background="Transparent"
Click="Button_OnClick"
Tag="https://www.ryujinx.org/">
<TextBlock
Text="www.ryujinx.org"
TextAlignment="Center"
TextDecorations="Underline"
ToolTip.Tip="{locale:Locale AboutUrlTooltipMessage}" />
</Button>
TextAlignment="Left" />
</flex:FlexPanel>
</Grid>
<TextBlock
Grid.Row="1"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{Binding Version}"
TextAlignment="Center" />
<TextBlock
TextAlignment="Center"
FontSize="10"
LineHeight="12" />
</StackPanel>
<StackPanel
Grid.Row="2"
Margin="20"
Spacing="10"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<TextBlock
HorizontalAlignment="Center"
MaxLines="2"
Width="200"
Text="{locale:Locale AboutDisclaimerMessage}"
TextAlignment="Center" />
TextAlignment="Center"
TextWrapping="Wrap"
FontSize="10"
LineHeight="12" />
<TextBlock
Name="AmiiboLabel"
Grid.Row="3"
Margin="20"
HorizontalAlignment="Center"
MaxLines="2"
Width="200"
PointerPressed="AmiiboLabel_OnPointerPressed"
Text="{locale:Locale AboutAmiiboDisclaimerMessage}"
TextAlignment="Center" />
TextAlignment="Center"
TextWrapping="Wrap"
FontSize="10"
LineHeight="12" />
<StackPanel
Grid.Row="4"
HorizontalAlignment="Center"
Orientation="Horizontal"
Spacing="10">
<StackPanel Orientation="Vertical" ToolTip.Tip="{locale:Locale AboutPatreonUrlTooltipMessage}">
<Button
Height="65"
MaxHeight="30"
MaxWidth="30"
MinHeight="30"
MinWidth="30"
Padding="8"
CornerRadius="15"
Background="Transparent"
Click="Button_OnClick"
Tag="https://www.patreon.com/ryujinx">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Image Source="resm:Ryujinx.Ui.Common.Resources.Logo_Patreon.png?assembly=Ryujinx.Ui.Common" />
<TextBlock
Grid.Row="1"
Margin="0,5,0,0"
HorizontalAlignment="Center"
Text="Patreon" />
</Grid>
Tag="https://www.patreon.com/ryujinx"
ToolTip.Tip="{locale:Locale AboutPatreonUrlTooltipMessage}">
<Image Source="{Binding PatreonLogo}" />
</Button>
</StackPanel>
<StackPanel Orientation="Vertical" ToolTip.Tip="{locale:Locale AboutGithubUrlTooltipMessage}">
<Button
Height="65"
MaxHeight="30"
MaxWidth="30"
MinHeight="30"
MinWidth="30"
Padding="8"
CornerRadius="15"
Background="Transparent"
Click="Button_OnClick"
Tag="https://github.com/Ryujinx/Ryujinx">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Image Source="resm:Ryujinx.Ui.Common.Resources.Logo_GitHub.png?assembly=Ryujinx.Ui.Common" />
<TextBlock
Grid.Row="1"
Margin="0,5,0,0"
HorizontalAlignment="Center"
Text="GitHub" />
</Grid>
Tag="https://github.com/Ryujinx/Ryujinx"
ToolTip.Tip="{locale:Locale AboutGithubUrlTooltipMessage}">
<Image Source="{Binding GithubLogo}" />
</Button>
</StackPanel>
<StackPanel Orientation="Vertical" ToolTip.Tip="{locale:Locale AboutDiscordUrlTooltipMessage}">
<Button
Height="65"
MaxHeight="30"
MaxWidth="30"
MinHeight="30"
MinWidth="30"
Padding="8"
CornerRadius="15"
Background="Transparent"
Click="Button_OnClick"
Tag="https://discordapp.com/invite/N2FmfVc">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Image Source="resm:Ryujinx.Ui.Common.Resources.Logo_Discord.png?assembly=Ryujinx.Ui.Common" />
<TextBlock
Grid.Row="1"
Margin="0,5,0,0"
HorizontalAlignment="Center"
Text="Discord" />
</Grid>
Tag="https://discordapp.com/invite/N2FmfVc"
ToolTip.Tip="{locale:Locale AboutDiscordUrlTooltipMessage}">
<Image Source="{Binding DiscordLogo}" />
</Button>
</StackPanel>
<StackPanel Orientation="Vertical" ToolTip.Tip="{locale:Locale AboutTwitterUrlTooltipMessage}">
<Button
Height="65"
MaxHeight="30"
MaxWidth="30"
MinHeight="30"
MinWidth="30"
Padding="8"
CornerRadius="15"
Background="Transparent"
Click="Button_OnClick"
Tag="https://twitter.com/RyujinxEmu">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Image Source="resm:Ryujinx.Ui.Common.Resources.Logo_Twitter.png?assembly=Ryujinx.Ui.Common" />
<TextBlock
Grid.Row="1"
Margin="0,5,0,0"
HorizontalAlignment="Center"
Text="Twitter" />
</Grid>
Tag="https://twitter.com/RyujinxEmu"
ToolTip.Tip="{locale:Locale AboutTwitterUrlTooltipMessage}">
<Image Source="{Binding TwitterLogo}" />
</Button>
<Button
MaxHeight="30"
MaxWidth="30"
MinHeight="30"
MinWidth="30"
Padding="8"
CornerRadius="15"
Background="Transparent"
Click="Button_OnClick"
Tag="https://www.ryujinx.org"
ToolTip.Tip="{locale:Locale AboutUrlTooltipMessage}">
<ui:SymbolIcon
Symbol="Link"
Foreground="{DynamicResource ThemeForegroundColor}" />
</Button>
</StackPanel>
</StackPanel>
</Grid>
<Border
Grid.Row="1"
Grid.Column="1"
Width="2"
Margin="5"
Width="1"
VerticalAlignment="Stretch"
BorderBrush="White"
BorderThickness="1,0,0,0">
<Separator Width="0" />
</Border>
BorderBrush="{DynamicResource ThemeControlBorderColor}"
BorderThickness="1,0,0,0"
Margin="20 0"/>
<Grid
Grid.Row="1"
Grid.Column="2"
Margin="20"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<StackPanel
Grid.Row="0"
Margin="0 10 0 0"
Spacing="2">
<TextBlock
FontWeight="Bold"
Text="{locale:Locale AboutRyujinxAboutTitle}"
TextDecorations="Underline" />
FontSize="15"
Text="{locale:Locale AboutRyujinxAboutTitle}" />
<TextBlock
Grid.Row="1"
Margin="20,5,5,5"
LineHeight="20"
FontSize="10"
TextWrapping="Wrap"
Text="{locale:Locale AboutRyujinxAboutContent}" />
</StackPanel>
<StackPanel
Grid.Row="1"
Margin="0 10 0 0"
Spacing="2">
<TextBlock
Grid.Row="2"
Margin="0,10,0,0"
FontWeight="Bold"
Text="{locale:Locale AboutRyujinxMaintainersTitle}"
TextDecorations="Underline" />
FontSize="15"
Text="{locale:Locale AboutRyujinxMaintainersTitle}" />
<TextBlock
Grid.Row="3"
Margin="20,5,5,5"
LineHeight="20"
FontSize="10"
TextWrapping="Wrap"
Text="{Binding Developers}" />
<Button
Grid.Row="4"
HorizontalAlignment="Right"
HorizontalAlignment="Left"
Background="Transparent"
Click="Button_OnClick"
Padding="5"
Tag="https://github.com/Ryujinx/Ryujinx/graphs/contributors?type=a">
<TextBlock
FontSize="10"
Text="{locale:Locale AboutRyujinxContributorsButtonHeader}"
TextAlignment="Right"
TextDecorations="Underline"
ToolTip.Tip="{locale:Locale AboutRyujinxMaintainersContentTooltipMessage}" />
</Button>
</StackPanel>
<StackPanel
Grid.Row="2"
Margin="0 10 0 0"
Spacing="2">
<TextBlock
Grid.Row="5"
Margin="0,0,0,0"
FontWeight="Bold"
Text="{locale:Locale AboutRyujinxSupprtersTitle}"
TextDecorations="Underline" />
<Border
Grid.Row="6"
Width="460"
Height="200"
Margin="20,5"
Padding="5"
VerticalAlignment="Stretch"
BorderBrush="White"
BorderThickness="1">
FontSize="15"
Text="{locale:Locale AboutRyujinxSupprtersTitle}" />
<ScrollViewer
VerticalScrollBarVisibility="Visible"
HorizontalScrollBarVisibility="Disabled"
Height="70">
<TextBlock
Name="SupportersTextBlock"
VerticalAlignment="Top"
Text="{Binding Supporters}"
TextWrapping="Wrap" />
</Border>
FontSize="10"
TextWrapping="Wrap"
Text="{Binding Supporters}" />
</ScrollViewer>
</StackPanel>
</Grid>
</Grid>
</window:StyleableWindow>
</UserControl>

View File

@@ -1,38 +1,48 @@
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Threading;
using Avalonia.Styling;
using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Common.Utilities;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ui.Common.Helper;
using System.Net.Http;
using System.Net.NetworkInformation;
using System.Threading.Tasks;
using Button = Avalonia.Controls.Button;
namespace Ryujinx.Ava.UI.Windows
{
public partial class AboutWindow : StyleableWindow
public partial class AboutWindow : UserControl
{
public AboutWindow()
{
if (Program.PreviewerDetached)
{
Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance[LocaleKeys.MenuBarHelpAbout];
}
Version = Program.Version;
DataContext = this;
DataContext = new AboutWindowViewModel();
InitializeComponent();
_ = DownloadPatronsJson();
}
public string Supporters { get; set; }
public string Version { get; set; }
public static async Task Show()
{
var content = new AboutWindow();
public string Developers => string.Format(LocaleManager.Instance[LocaleKeys.AboutPageDeveloperListMore], "gdkchan, Ac_K, Thog, rip in peri peri, LDj3SNuD, emmaus, Thealexbarney, Xpl0itR, GoffyDude, »jD«");
ContentDialog contentDialog = new()
{
PrimaryButtonText = "",
SecondaryButtonText = "",
CloseButtonText = LocaleManager.Instance[LocaleKeys.UserProfilesClose],
Content = content
};
Style closeButton = new(x => x.Name("CloseButton"));
closeButton.Setters.Add(new Setter(WidthProperty, 80d));
Style closeButtonParent = new(x => x.Name("CommandSpace"));
closeButtonParent.Setters.Add(new Setter(HorizontalAlignmentProperty, Avalonia.Layout.HorizontalAlignment.Right));
contentDialog.Styles.Add(closeButton);
contentDialog.Styles.Add(closeButtonParent);
await contentDialog.ShowAsync();
}
private void Button_OnClick(object sender, RoutedEventArgs e)
{
@@ -42,31 +52,6 @@ namespace Ryujinx.Ava.UI.Windows
}
}
private async Task DownloadPatronsJson()
{
if (!NetworkInterface.GetIsNetworkAvailable())
{
Supporters = LocaleManager.Instance[LocaleKeys.ConnectionError];
return;
}
HttpClient httpClient = new();
try
{
string patreonJsonString = await httpClient.GetStringAsync("https://patreon.ryujinx.org/");
Supporters = string.Join(", ", JsonHelper.Deserialize<string[]>(patreonJsonString));
}
catch
{
Supporters = LocaleManager.Instance[LocaleKeys.ApiError];
}
await Dispatcher.UIThread.InvokeAsync(() => SupportersTextBlock.Text = Supporters);
}
private void AmiiboLabel_OnPointerPressed(object sender, PointerPressedEventArgs e)
{
if (sender is TextBlock)

View File

@@ -343,6 +343,7 @@
Margin="5,0,5,0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
VerticalContentAlignment="Center"
DockPanel.Dock="Right"
KeyUp="SearchBox_OnKeyUp"
Text="{Binding SearchText}"

View File

@@ -29,7 +29,7 @@ namespace Ryujinx.Common.Logging
files[i].Delete();
}
string version = ReleaseInformations.GetVersion();
string version = ReleaseInformation.GetVersion();
// Get path for the current time
path = Path.Combine(logDir.FullName, $"Ryujinx_{version}_{DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss")}.log");

View File

@@ -5,7 +5,7 @@ using System.Reflection;
namespace Ryujinx.Common
{
// DO NOT EDIT, filled by CI
public static class ReleaseInformations
public static class ReleaseInformation
{
private const string FlatHubChannelOwner = "flathub";

View File

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

View File

@@ -182,6 +182,8 @@ namespace Ryujinx.HLE.HOS
byte[] arguments = null,
params IExecutable[] executables)
{
context.Device.System.ServiceTable.WaitServicesReady();
LibHac.Result rc = metaData.GetNpdm(out var npdm);
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;
}
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);

View File

@@ -59,7 +59,7 @@ namespace Ryujinx.Headless.SDL2
static void Main(string[] args)
{
Version = ReleaseInformations.GetVersion();
Version = ReleaseInformation.GetVersion();
Console.Title = $"Ryujinx Console {Version} (Headless SDL2)";
@@ -419,7 +419,7 @@ namespace Ryujinx.Headless.SDL2
if ((bool)option.EnableFileLog)
{
Logger.AddTarget(new AsyncLogTargetWrapper(
new FileLogTarget(ReleaseInformations.GetBaseApplicationDirectory(), "file"),
new FileLogTarget(ReleaseInformation.GetBaseApplicationDirectory(), "file"),
1000,
AsyncLogTargetOverflowAction.Block
));

View File

@@ -123,6 +123,12 @@ namespace Ryujinx.Horizon.Generators.Hipc
{
string[] args = new string[method.ParameterList.Parameters.Count];
if (args.Length == 0)
{
generator.AppendLine($"{{ {commandId}, new CommandHandler({method.Identifier.Text}, Array.Empty<CommandArg>()) }},");
}
else
{
int index = 0;
foreach (var parameter in method.ParameterList.Parameters)
@@ -163,6 +169,7 @@ namespace Ryujinx.Horizon.Generators.Hipc
generator.AppendLine($"{{ {commandId}, new CommandHandler({method.Identifier.Text}, {string.Join(", ", args)}) }},");
}
}
}
generator.LeaveScope(";");
generator.LeaveScope();

View File

@@ -3,10 +3,12 @@ namespace Ryujinx.Horizon
public struct HorizonOptions
{
public bool IgnoreMissingServices { get; }
public bool ThrowOnInvalidCommandIds { get; }
public HorizonOptions(bool ignoreMissingServices)
{
IgnoreMissingServices = ignoreMissingServices;
ThrowOnInvalidCommandIds = true;
}
}
}

View File

@@ -2,6 +2,6 @@
{
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.Text;
namespace Ryujinx.Horizon.LogManager
namespace Ryujinx.Horizon.LogManager.Ipc
{
partial class LmLogger : IServiceObject
partial class LmLogger : ILmLogger
{
private readonly LmLog _log;
private readonly ulong _clientProcessId;
private readonly LogService _log;
private readonly ulong _pid;
public LmLogger(LmLog log, ulong clientProcessId)
public LmLogger(LogService log, ulong pid)
{
_log = log;
_clientProcessId = clientProcessId;
_pid = pid;
}
[CmifCommand(0)]
public Result Log([Buffer(HipcBufferFlags.In | HipcBufferFlags.AutoSelect)] Span<byte> message)
{
if (!SetProcessId(message, _clientProcessId))
if (!SetProcessId(message, _pid))
{
return Result.Success;
}
@@ -35,7 +35,7 @@ namespace Ryujinx.Horizon.LogManager
return Result.Success;
}
[CmifCommand(1)]
[CmifCommand(1)] // 3.0.0+
public Result SetDestination(LogDestination destination)
{
_log.LogDestination = destination;
@@ -48,7 +48,6 @@ namespace Ryujinx.Horizon.LogManager
ref LogPacketHeader header = ref MemoryMarshal.Cast<byte, LogPacketHeader>(message)[0];
uint expectedMessageSize = (uint)Unsafe.SizeOf<LogPacketHeader>() + header.PayloadSize;
if (expectedMessageSize != (uint)message.Length)
{
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)
{
SpanReader reader = new SpanReader(message);
SpanReader reader = new(message);
LogPacketHeader header = reader.Read<LogPacketHeader>();
StringBuilder builder = new();
StringBuilder sb = new StringBuilder();
sb.AppendLine($"Guest Log:\n Log level: {header.Severity}");
builder.AppendLine($"Guest Log:\n Log level: {header.Severity}");
while (reader.Length > 0)
{
@@ -78,7 +75,7 @@ namespace Ryujinx.Horizon.LogManager
LogDataChunkKey field = (LogDataChunkKey)type;
string fieldStr = string.Empty;
string fieldStr;
if (field == LogDataChunkKey.Start)
{
@@ -111,10 +108,10 @@ namespace Ryujinx.Horizon.LogManager
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)

View File

@@ -2,16 +2,17 @@
using Ryujinx.Horizon.Sdk.Lm;
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;
[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;
}

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.Sm;
namespace Ryujinx.Horizon.LogManager
{
@@ -11,34 +11,23 @@ namespace Ryujinx.Horizon.LogManager
private const int PointerBufferSize = 0x400;
private const int MaxDomains = 31;
private const int MaxDomainObjects = 61;
private const int MaxPortsCount = 1;
private static readonly ManagerOptions _logManagerOptions = new ManagerOptions(
PointerBufferSize,
MaxDomains,
MaxDomainObjects,
false);
private static readonly ServiceName _logServiceName = ServiceName.Encode("lm");
private static readonly ManagerOptions _logManagerOptions = new(PointerBufferSize, MaxDomains, MaxDomainObjects, false);
private SmApi _sm;
private ServerManager _serverManager;
private LmLog _logServiceObject;
public void Initialize()
{
HeapAllocator allocator = new HeapAllocator();
HeapAllocator allocator = new();
_sm = new SmApi();
_sm.Initialize().AbortOnFailure();
_serverManager = new ServerManager(allocator, _sm, MaxPortsCount, _logManagerOptions, LogMaxSessionsCount);
_logServiceObject = new LmLog();
_serverManager.RegisterObjectForServer(_logServiceObject, _logServiceName, LogMaxSessionsCount);
_serverManager.RegisterObjectForServer(new LogService(), ServiceName.Encode("lm"), LogMaxSessionsCount);
}
public void ServiceRequests()

View File

@@ -2,11 +2,14 @@
{
class LmMain : IService
{
public static void Main()
public static void Main(ServiceTable serviceTable)
{
LmIpcServer ipcServer = new LmIpcServer();
LmIpcServer ipcServer = new();
ipcServer.Initialize();
serviceTable.SignalServiceReady();
ipcServer.ServiceRequests();
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
{
Admin = -1,
User = 1,
System = 2,
Manager = 6
Manager = 6,
Debug = unchecked((int)0x80000006)
}
}

View File

@@ -11,4 +11,8 @@
<ProjectReference Include="..\Ryujinx.Memory\Ryujinx.Memory.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="LibHac" />
</ItemGroup>
</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

@@ -29,6 +29,7 @@ namespace Ryujinx.Horizon.Sdk
if (result.IsFailure)
{
response = default;
return result;
}

View File

@@ -22,14 +22,17 @@ namespace Ryujinx.Horizon.Sdk.Sf.Cmif
totalSize += Unsafe.SizeOf<CmifInHeader>() + format.DataSize;
totalSize = (totalSize + 1) & ~1;
int outPointerSizeTableOffset = totalSize;
int outPointerSizeTableSize = format.OutAutoBuffersCount + format.OutPointersCount;
totalSize += sizeof(ushort) * outPointerSizeTableSize;
int rawDataSizeInWords = (totalSize + sizeof(uint) - 1) / sizeof(uint);
CmifRequest request = new CmifRequest();
request.Hipc = HipcMessage.WriteMessage(output, new HipcMetadata()
CmifRequest request = new()
{
Hipc = HipcMessage.WriteMessage(output, new HipcMetadata()
{
Type = format.Context != 0 ? (int)CommandType.RequestWithContext : (int)CommandType.Request,
SendStaticsCount = format.InAutoBuffersCount + format.InPointersCount,
@@ -41,7 +44,8 @@ namespace Ryujinx.Horizon.Sdk.Sf.Cmif
SendPid = format.SendPid,
CopyHandlesCount = format.HandlesCount,
MoveHandlesCount = 0
});
})
};
Span<uint> data = request.Hipc.DataWords;
@@ -61,9 +65,9 @@ namespace Ryujinx.Horizon.Sdk.Sf.Cmif
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];
@@ -76,11 +80,12 @@ namespace Ryujinx.Horizon.Sdk.Sf.Cmif
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);
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.ServerPointerSize = format.ServerPointerSize;
@@ -89,15 +94,15 @@ namespace Ryujinx.Horizon.Sdk.Sf.Cmif
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<uint> objects = Span<uint>.Empty;
if (isDomain)
{
data = data.Slice(Unsafe.SizeOf<CmifDomainOutHeader>());
objects = MemoryMarshal.Cast<byte, uint>(data.Slice(Unsafe.SizeOf<CmifOutHeader>() + size));
data = data[Unsafe.SizeOf<CmifDomainOutHeader>()..];
objects = MemoryMarshal.Cast<byte, uint>(data[(Unsafe.SizeOf<CmifOutHeader>() + size)..]);
}
CmifOutHeader header = MemoryMarshal.Cast<byte, CmifOutHeader>(data)[0];
@@ -105,18 +110,20 @@ namespace Ryujinx.Horizon.Sdk.Sf.Cmif
if (header.Magic != CmifOutHeaderMagic)
{
response = default;
return SfResult.InvalidOutHeader;
}
if (header.Result.IsFailure)
{
response = default;
return header.Result;
}
response = new CmifResponse()
{
Data = data.Slice(Unsafe.SizeOf<CmifOutHeader>()),
Data = data[Unsafe.SizeOf<CmifOutHeader>()..],
Objects = objects,
CopyHandles = responseMessage.Data.CopyHandles,
MoveHandles = responseMessage.Data.MoveHandles

View File

@@ -21,7 +21,7 @@ namespace Ryujinx.Horizon.Sdk.Sf.Cmif
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;
@@ -39,7 +39,7 @@ namespace Ryujinx.Horizon.Sdk.Sf.Cmif
return SfResult.InvalidHeaderSize;
}
ReadOnlySpan<byte> inMessageRawData = inDomainRawData.Slice(0, inHeader.DataSize);
ReadOnlySpan<byte> inMessageRawData = inDomainRawData[..inHeader.DataSize];
if (inHeader.ObjectsCount > DomainServiceObjectProcessor.MaximumObjects)
{

View File

@@ -63,7 +63,7 @@ namespace Ryujinx.Horizon.Sdk.Sf.Cmif
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)
{
@@ -92,7 +92,7 @@ namespace Ryujinx.Horizon.Sdk.Sf.Cmif
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;
return response;
@@ -107,9 +107,9 @@ namespace Ryujinx.Horizon.Sdk.Sf.Cmif
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)
@@ -129,7 +129,7 @@ namespace Ryujinx.Horizon.Sdk.Sf.Cmif
_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++)
{

View File

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

View File

@@ -8,6 +8,7 @@
public byte OutHeadersSize { get; }
public byte InObjectsCount { get; }
public byte OutObjectsCount { get; }
public int UnfixedOutPointerSizeOffset => InDataSize + InHeadersSize + 0x10;
public ServerMessageRuntimeMetadata(

View File

@@ -39,17 +39,21 @@ namespace Ryujinx.Horizon.Sdk.Sf.Cmif
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 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);
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;
}
else if (HorizonStatic.Options.ThrowOnInvalidCommandIds)
{
throw new NotImplementedException($"{objectName} command ID: {commandId} is not implemented");
}
return SfResult.UnknownCommandId;
}
@@ -72,6 +76,7 @@ namespace Ryujinx.Horizon.Sdk.Sf.Cmif
if (outHeader.IsEmpty)
{
commandResult.AbortOnSuccess();
return commandResult;
}
@@ -80,11 +85,10 @@ namespace Ryujinx.Horizon.Sdk.Sf.Cmif
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);
outRawData = MemoryMarshal.Cast<uint, byte>(response.DataWords);
return response;
}
}
}

View File

@@ -50,8 +50,8 @@ namespace Ryujinx.Horizon.Sdk.Sf
public static void GetCmifOutHeaderPointer(ref Span<CmifOutHeader> outHeader, ref Span<byte> outRawData)
{
outHeader = MemoryMarshal.Cast<byte, CmifOutHeader>(outRawData).Slice(0, 1);
outRawData = outRawData.Slice(Unsafe.SizeOf<CmifOutHeader>());
outHeader = MemoryMarshal.Cast<byte, CmifOutHeader>(outRawData)[..1];
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
{
var writableRegion = GetWritableRegion(bufferRange);
return ref MemoryMarshal.Cast<byte, T>(writableRegion.Memory.Span)[0];
}

View File

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

View File

@@ -20,7 +20,7 @@ namespace Ryujinx.Horizon.Sdk.Sf.Hipc
Header header = MemoryMarshal.Cast<byte, Header>(data)[0];
data = data.Slice(Unsafe.SizeOf<Header>());
data = data[Unsafe.SizeOf<Header>()..];
int receiveStaticsCount = 0;
ulong pid = 0;
@@ -42,14 +42,12 @@ namespace Ryujinx.Horizon.Sdk.Sf.Hipc
if (header.HasSpecialHeader)
{
specialHeader = MemoryMarshal.Cast<byte, SpecialHeader>(data)[0];
data = data.Slice(Unsafe.SizeOf<SpecialHeader>());
data = data[Unsafe.SizeOf<SpecialHeader>()..];
if (specialHeader.SendPid)
{
pid = MemoryMarshal.Cast<byte, ulong>(data)[0];
data = data.Slice(sizeof(ulong));
data = data[sizeof(ulong)..];
}
}
@@ -90,7 +88,6 @@ namespace Ryujinx.Horizon.Sdk.Sf.Hipc
public static HipcMessageData WriteMessage(Span<byte> destination, HipcMetadata meta)
{
int initialLength = destination.Length;
bool hasSpecialHeader = meta.SendPid || meta.CopyHandlesCount != 0 || meta.MoveHandlesCount != 0;
MemoryMarshal.Cast<byte, Header>(destination)[0] = new Header()
@@ -105,7 +102,7 @@ namespace Ryujinx.Horizon.Sdk.Sf.Hipc
HasSpecialHeader = hasSpecialHeader
};
destination = destination.Slice(Unsafe.SizeOf<Header>());
destination = destination[Unsafe.SizeOf<Header>()..];
if (hasSpecialHeader)
{
@@ -116,11 +113,11 @@ namespace Ryujinx.Horizon.Sdk.Sf.Hipc
MoveHandlesCount = meta.MoveHandlesCount
};
destination = destination.Slice(Unsafe.SizeOf<SpecialHeader>());
destination = destination[Unsafe.SizeOf<SpecialHeader>()..];
if (meta.SendPid)
{
destination = destination.Slice(sizeof(ulong));
destination = destination[sizeof(ulong)..];
}
}
@@ -133,54 +130,54 @@ namespace Ryujinx.Horizon.Sdk.Sf.Hipc
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;
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;
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;
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;
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;
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;
@@ -189,12 +186,11 @@ namespace Ryujinx.Horizon.Sdk.Sf.Hipc
{
int dataOffset = initialLength - data.Length;
int dataOffsetAligned = BitUtils.AlignUp(dataOffset, 0x10);
int padding = (dataOffsetAligned - dataOffset) / sizeof(uint);
dataWords = MemoryMarshal.Cast<byte, uint>(data).Slice(padding, meta.DataWordsCount - padding);
dataWords = MemoryMarshal.Cast<byte, uint>(data)[padding..meta.DataWordsCount];
data = data.Slice(meta.DataWordsCount * sizeof(uint));
data = data[(meta.DataWordsCount * sizeof(uint))..];
}
Span<HipcReceiveListEntry> receiveList = Span<HipcReceiveListEntry>.Empty;
@@ -203,7 +199,7 @@ namespace Ryujinx.Horizon.Sdk.Sf.Hipc
{
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()

View File

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

View File

@@ -2,7 +2,7 @@
{
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 MaxDomains { get; }

View File

@@ -15,7 +15,7 @@ namespace Ryujinx.Horizon.Sdk.Sf.Hipc
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);
}

View File

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

View File

@@ -46,6 +46,7 @@ namespace Ryujinx.Horizon.Sdk.Sf.Hipc
_requestStopEventHolder = new MultiWaitHolderOfEvent(_requestStopEvent);
_multiWait.LinkMultiWaitHolder(_requestStopEventHolder);
_notifyEventHolder = new MultiWaitHolderOfEvent(_notifyEvent);
_multiWait.LinkMultiWaitHolder(_notifyEventHolder);
}
@@ -73,6 +74,7 @@ namespace Ryujinx.Horizon.Sdk.Sf.Hipc
private void RegisterServerImpl(int portIndex, ServiceObjectHolder staticHolder, int portHandle)
{
Server server = AllocateServer(portIndex, portHandle, ServiceName.Invalid, managed: false, staticHolder);
RegisterServerImpl(server);
}
@@ -86,6 +88,7 @@ namespace Ryujinx.Horizon.Sdk.Sf.Hipc
}
Server server = AllocateServer(portIndex, portHandle, name, managed: true, staticHolder);
RegisterServerImpl(server);
return Result.Success;
@@ -103,6 +106,11 @@ namespace Ryujinx.Horizon.Sdk.Sf.Hipc
throw new NotSupportedException();
}
protected Result AcceptImpl(Server server, IServiceObject obj)
{
return AcceptSession(server.PortHandle, new ServiceObjectHolder(obj));
}
public void ServiceRequests()
{
while (WaitAndProcessRequestsImpl());
@@ -176,6 +184,7 @@ namespace Ryujinx.Horizon.Sdk.Sf.Hipc
{
session.HasReceived = false;
session.UserData = UserDataTag.Session;
RegisterToWaitList(session);
}
@@ -198,15 +207,12 @@ namespace Ryujinx.Horizon.Sdk.Sf.Hipc
private Result Process(MultiWaitHolder holder)
{
switch ((UserDataTag)holder.UserData)
return (UserDataTag)holder.UserData switch
{
case UserDataTag.Server:
return ProcessForServer(holder);
case UserDataTag.Session:
return ProcessForSession(holder);
default:
throw new NotImplementedException(((UserDataTag)holder.UserData).ToString());
}
UserDataTag.Server => ProcessForServer(holder),
UserDataTag.Session => ProcessForSession(holder),
_ => throw new NotImplementedException(((UserDataTag)holder.UserData).ToString())
};
}
private Result ProcessForServer(MultiWaitHolder holder)
@@ -259,6 +265,7 @@ namespace Ryujinx.Horizon.Sdk.Sf.Hipc
}
session.HasReceived = true;
tlsMessage.Memory.Span.CopyTo(savedMessage.Memory.Span);
}
else

View File

@@ -78,6 +78,7 @@ namespace Ryujinx.Horizon.Sdk.Sf.Hipc
session.SavedMessage = GetSessionSavedMessageBuffer(session);
RegisterSessionToWaitList(session);
return Result.Success;
}
@@ -141,6 +142,7 @@ namespace Ryujinx.Horizon.Sdk.Sf.Hipc
protected void CloseSessionImpl(ServerSession session)
{
int sessionHandle = session.Handle;
Os.FinalizeMultiWaitHolder(session);
DestroySession(session);
HorizonStatic.Syscall.CloseHandle(sessionHandle).AbortOnFailure();
@@ -156,6 +158,7 @@ namespace Ryujinx.Horizon.Sdk.Sf.Hipc
if (session.IsClosed || GetCmifCommandType(message) == CommandType.Close)
{
CloseSessionImpl(session);
return Result.Success;
}
else
@@ -165,6 +168,7 @@ namespace Ryujinx.Horizon.Sdk.Sf.Hipc
if (result.IsSuccess)
{
RegisterSessionToWaitList(session);
return Result.Success;
}
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}");
CloseSessionImpl(session);
return Result.Success;
}
}

View File

@@ -190,6 +190,7 @@ namespace Ryujinx.Horizon.Sdk.Sf
var descriptor = context.Request.Data.SendStatics[sendPointerIndex++];
ulong address = descriptor.Address;
ulong size = descriptor.Size;
_bufferRanges[i] = new PointerAndSize(address, size);
if (size != 0)
@@ -208,7 +209,8 @@ namespace Ryujinx.Horizon.Sdk.Sf
else
{
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++];
}
@@ -314,6 +316,7 @@ namespace Ryujinx.Horizon.Sdk.Sf
int rawSizeInBytes = meta.DataWordsCount * sizeof(uint);
int commandRawSize = BitUtils.AlignUp(runtimeMetadata.UnfixedOutPointerSizeOffset + (OutUnfixedSizePointerBuffersCount * sizeof(ushort)), sizeof(uint));
requestValid &= rawSizeInBytes >= commandRawSize;
return requestValid ? Result.Success : HipcResult.InvalidCmifRequest;
@@ -365,6 +368,7 @@ namespace Ryujinx.Horizon.Sdk.Sf
_outCopyHandlesCount,
_outMoveHandlesCount + runtimeMetadata.OutObjectsCount);
outRawData = MemoryMarshal.Cast<uint, byte>(response.DataWords);
return response;
}
@@ -377,6 +381,7 @@ namespace Ryujinx.Horizon.Sdk.Sf
(BitUtils.AlignUp(rawDataSize, 4) + 0x10) / sizeof(uint),
0,
0);
outRawData = MemoryMarshal.Cast<uint, byte>(response.DataWords);
}
@@ -410,6 +415,7 @@ namespace Ryujinx.Horizon.Sdk.Sf
if (obj == null)
{
response.MoveHandles[index] = 0;
return;
}

View File

@@ -27,9 +27,7 @@ namespace Ryujinx.Horizon.Sdk.Sf
{
for (int j = i; j > 0 && aligns[map[j - 1]] > aligns[map[j]]; j--)
{
var temp = map[j - 1];
map[j - 1] = map[j];
map[j] = temp;
(map[j], map[j - 1]) = (map[j - 1], map[j]);
}
}

View File

@@ -6,26 +6,22 @@ namespace Ryujinx.Horizon.Sdk.Sf
{
public const int ModuleId = 10;
public static Result NotSupported => new Result(ModuleId, 1);
public static Result InvalidHeaderSize => new Result(ModuleId, 202);
public static Result InvalidInHeader => new Result(ModuleId, 211);
public static Result InvalidOutHeader => new Result(ModuleId, 212);
public static Result UnknownCommandId => new Result(ModuleId, 221);
public static Result InvalidOutRawSize => new Result(ModuleId, 232);
public static Result InvalidInObjectsCount => new Result(ModuleId, 235);
public static Result InvalidOutObjectsCount => new Result(ModuleId, 236);
public static Result InvalidInObject => new Result(ModuleId, 239);
public static Result TargetNotFound => new Result(ModuleId, 261);
public static Result OutOfDomainEntries => new Result(ModuleId, 301);
public static Result InvalidatedByUser => new Result(ModuleId, 802);
public static Result RequestDeferredByUser => new Result(ModuleId, 812);
public static Result NotSupported => new(ModuleId, 1);
public static Result InvalidHeaderSize => new(ModuleId, 202);
public static Result InvalidInHeader => new(ModuleId, 211);
public static Result InvalidOutHeader => new(ModuleId, 212);
public static Result UnknownCommandId => new(ModuleId, 221);
public static Result InvalidOutRawSize => new(ModuleId, 232);
public static Result InvalidInObjectsCount => new(ModuleId, 235);
public static Result InvalidOutObjectsCount => new(ModuleId, 236);
public static Result InvalidInObject => new(ModuleId, 239);
public static Result TargetNotFound => new(ModuleId, 261);
public static Result OutOfDomainEntries => new(ModuleId, 301);
public static Result InvalidatedByUser => new(ModuleId, 802);
public static Result RequestDeferredByUser => new(ModuleId, 812);
public static bool RequestContextChanged(Result result) => result.InRange(800, 899);
public static bool Invalidated(Result result) => result.InRange(801, 809);
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
{
private const string SmName = "sm:";
private int _portHandle;
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)
{
HorizonStatic.Syscall.SleepThread(50000000L);
result = HorizonStatic.Syscall.ConnectToNamedPort(out portHandle, "sm:");
result = HorizonStatic.Syscall.ConnectToNamedPort(out portHandle, SmName);
}
if (result.IsFailure)
@@ -33,7 +35,7 @@ namespace Ryujinx.Horizon.Sdk.Sm
{
Span<byte> data = stackalloc byte[8];
SpanWriter writer = new SpanWriter(data);
SpanWriter writer = new(data);
writer.Write(0UL);
@@ -44,7 +46,7 @@ namespace Ryujinx.Horizon.Sdk.Sm
{
Span<byte> data = stackalloc byte[8];
SpanWriter writer = new SpanWriter(data);
SpanWriter writer = new(data);
writer.Write(name);
@@ -53,10 +55,12 @@ namespace Ryujinx.Horizon.Sdk.Sm
if (result.IsFailure)
{
handle = 0;
return result;
}
handle = response.MoveHandles[0];
return Result.Success;
}
@@ -64,7 +68,7 @@ namespace Ryujinx.Horizon.Sdk.Sm
{
Span<byte> data = stackalloc byte[16];
SpanWriter writer = new SpanWriter(data);
SpanWriter writer = new(data);
writer.Write(name);
writer.Write(isLight ? 1 : 0);
@@ -75,10 +79,12 @@ namespace Ryujinx.Horizon.Sdk.Sm
if (result.IsFailure)
{
handle = 0;
return result;
}
handle = response.MoveHandles[0];
return Result.Success;
}
@@ -86,7 +92,7 @@ namespace Ryujinx.Horizon.Sdk.Sm
{
Span<byte> data = stackalloc byte[8];
SpanWriter writer = new SpanWriter(data);
SpanWriter writer = new(data);
writer.Write(name);
@@ -97,7 +103,7 @@ namespace Ryujinx.Horizon.Sdk.Sm
{
Span<byte> data = stackalloc byte[8];
SpanWriter writer = new SpanWriter(data);
SpanWriter writer = new(data);
writer.Write(0UL);

View File

@@ -6,12 +6,14 @@ namespace Ryujinx.Horizon
{
public struct ServiceEntry
{
private readonly Action _entrypoint;
private readonly Action<ServiceTable> _entrypoint;
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;
_serviceTable = serviceTable;
_options = options;
}
@@ -19,7 +21,7 @@ namespace Ryujinx.Horizon
{
HorizonStatic.Register(_options, syscallApi, addressSpace, threadContext, (int)threadContext.GetX(1));
_entrypoint();
_entrypoint(_serviceTable);
}
}
}

View File

@@ -1,22 +1,58 @@
using Ryujinx.Horizon.LogManager;
using Ryujinx.Horizon.Prepo;
using System.Collections.Generic;
using System.Threading;
namespace Ryujinx.Horizon
{
public static class ServiceTable
public class ServiceTable
{
public static IEnumerable<ServiceEntry> GetServices(HorizonOptions options)
private int _readyServices;
private int _totalServices;
private readonly ManualResetEvent _servicesReadyEvent = new(false);
public IEnumerable<ServiceEntry> GetServices(HorizonOptions options)
{
List<ServiceEntry> entries = new List<ServiceEntry>();
List<ServiceEntry> entries = new();
void RegisterService<T>() where T : IService
{
entries.Add(new ServiceEntry(T.Main, options));
entries.Add(new ServiceEntry(T.Main, this, options));
}
RegisterService<LmMain>();
RegisterService<PrepoMain>();
_totalServices = entries.Count;
return entries;
}
internal void SignalServiceReady()
{
if (Interlocked.Increment(ref _readyServices) == _totalServices)
{
_servicesReadyEvent.Set();
}
}
public void WaitServicesReady()
{
_servicesReadyEvent.WaitOne();
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
_servicesReadyEvent.Dispose();
}
}
public void Dispose()
{
Dispose(true);
}
}
}

View File

@@ -126,20 +126,19 @@ namespace Ryujinx.Horizon.Sm.Impl
// TODO: Validation with GetProcessInfo etc.
int serviceIndex = GetServiceInfo(name);
if (serviceIndex < 0)
{
return SmResult.NotRegistered;
}
ref var serviceInfo = ref _services[serviceIndex];
if (serviceInfo.OwnerProcessId != processId)
{
return SmResult.NotAllowed;
}
serviceInfo.Free();
return Result.Success;
}

View File

@@ -0,0 +1,8 @@
using Ryujinx.Horizon.Sdk.Sm;
namespace Ryujinx.Horizon.Sm.Ipc
{
partial class ManagerService : IManagerService
{
}
}

View File

@@ -3,9 +3,9 @@ using Ryujinx.Horizon.Sdk.Sf;
using Ryujinx.Horizon.Sdk.Sm;
using Ryujinx.Horizon.Sm.Impl;
namespace Ryujinx.Horizon.Sm
namespace Ryujinx.Horizon.Sm.Ipc
{
partial class UserService : IServiceObject
partial class UserService : IUserService
{
private readonly ServiceManager _serviceManager;

View File

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

View File

@@ -1,29 +1,33 @@
using Ryujinx.Horizon.Sdk.Sf.Hipc;
using Ryujinx.Horizon.Prepo.Types;
using Ryujinx.Horizon.Prepo;
using Ryujinx.Horizon.Sdk.Sf.Hipc;
using Ryujinx.Horizon.Sdk.Sm;
using Ryujinx.Horizon.Sm.Impl;
using Ryujinx.Horizon.Sm.Types;
namespace Ryujinx.Horizon.Sm
{
public class SmMain
{
private enum PortIndex
{
User,
Manager
}
private const int SmMaxSessionsCount = 64;
private const int SmmMaxSessionsCount = 1;
private const int SmTotalMaxSessionsCount = SmMaxSessionsCount + SmmMaxSessionsCount;
private const int MaxPortsCount = 2;
private readonly ServerManager _serverManager = new ServerManager(null, null, MaxPortsCount, ManagerOptions.Default, 0);
private readonly ServiceManager _serviceManager = new ServiceManager();
private SmServerManager _serverManager;
private readonly ServiceManager _serviceManager = new();
public void Main()
{
HorizonStatic.Syscall.ManageNamedPort(out int smHandle, "sm:", 64).AbortOnFailure();
HorizonStatic.Syscall.ManageNamedPort(out int smHandle, "sm:", SmMaxSessionsCount).AbortOnFailure();
_serverManager.RegisterServer((int)PortIndex.User, smHandle);
_serviceManager.RegisterServiceForSelf(out int smmHandle, ServiceName.Encode("sm:m"), 1).AbortOnFailure();
_serverManager.RegisterServer((int)PortIndex.Manager, smmHandle);
_serverManager = new SmServerManager(_serviceManager, null, null, MaxPortsCount, ManagerOptions.Default, SmTotalMaxSessionsCount);
_serverManager.RegisterServer((int)SmPortIndex.User, smHandle);
_serviceManager.RegisterServiceForSelf(out int smmHandle, ServiceName.Encode("sm:m"), SmmMaxSessionsCount).AbortOnFailure();
_serverManager.RegisterServer((int)SmPortIndex.Manager, smmHandle);
_serverManager.ServiceRequests();
}
}

View File

@@ -6,14 +6,14 @@ namespace Ryujinx.Horizon.Sm
{
private const int ModuleId = 21;
public static Result OutOfProcess => new Result(ModuleId, 1);
public static Result InvalidClient => new Result(ModuleId, 2);
public static Result OutOfSessions => new Result(ModuleId, 3);
public static Result AlreadyRegistered => new Result(ModuleId, 4);
public static Result OutOfServices => new Result(ModuleId, 5);
public static Result InvalidServiceName => new Result(ModuleId, 6);
public static Result NotRegistered => new Result(ModuleId, 7);
public static Result NotAllowed => new Result(ModuleId, 8);
public static Result TooLargeAccessControl => new Result(ModuleId, 9);
public static Result OutOfProcess => new(ModuleId, 1);
public static Result InvalidClient => new(ModuleId, 2);
public static Result OutOfSessions => new(ModuleId, 3);
public static Result AlreadyRegistered => new(ModuleId, 4);
public static Result OutOfServices => new(ModuleId, 5);
public static Result InvalidServiceName => new(ModuleId, 6);
public static Result NotRegistered => new(ModuleId, 7);
public static Result NotAllowed => new(ModuleId, 8);
public static Result TooLargeAccessControl => new(ModuleId, 9);
}
}

View File

@@ -0,0 +1,30 @@
using Ryujinx.Horizon.Common;
using Ryujinx.Horizon.Sdk.Sf.Hipc;
using Ryujinx.Horizon.Sdk.Sm;
using Ryujinx.Horizon.Sm.Impl;
using Ryujinx.Horizon.Sm.Ipc;
using Ryujinx.Horizon.Sm.Types;
using System;
namespace Ryujinx.Horizon.Sm
{
class SmServerManager : ServerManager
{
private readonly ServiceManager _serviceManager;
public SmServerManager(ServiceManager serviceManager, HeapAllocator allocator, SmApi sm, int maxPorts, ManagerOptions options, int maxSessions) : base(allocator, sm, maxPorts, options, maxSessions)
{
_serviceManager = serviceManager;
}
protected override Result OnNeedsToAccept(int portIndex, Server server)
{
return (SmPortIndex)portIndex switch
{
SmPortIndex.User => AcceptImpl(server, new UserService(_serviceManager)),
SmPortIndex.Manager => AcceptImpl(server, new ManagerService()),
_ => throw new ArgumentOutOfRangeException(nameof(portIndex)),
};
}
}
}

View File

@@ -0,0 +1,8 @@
namespace Ryujinx.Horizon.Sm.Types
{
enum SmPortIndex
{
User,
Manager
}
}

View File

@@ -91,7 +91,7 @@ namespace Ryujinx.SDL2.Common
SDL_EventState(SDL_EventType.SDL_CONTROLLERSENSORUPDATE, SDL_DISABLE);
string gamepadDbPath = Path.Combine(ReleaseInformations.GetBaseApplicationDirectory(), "SDL_GameControllerDB.txt");
string gamepadDbPath = Path.Combine(ReleaseInformation.GetBaseApplicationDirectory(), "SDL_GameControllerDB.txt");
if (File.Exists(gamepadDbPath))
{

View File

@@ -80,7 +80,7 @@ namespace Ryujinx.Ui.Common.Configuration
if (e.NewValue)
{
Logger.AddTarget(new AsyncLogTargetWrapper(
new FileLogTarget(ReleaseInformations.GetBaseApplicationDirectory(), "file"),
new FileLogTarget(ReleaseInformation.GetBaseApplicationDirectory(), "file"),
1000,
AsyncLogTargetOverflowAction.Block
));

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View File

@@ -34,11 +34,15 @@
<EmbeddedResource Include="Resources\Icon_NSP.png" />
<EmbeddedResource Include="Resources\Icon_XCI.png" />
<EmbeddedResource Include="Resources\Logo_Amiibo.png" />
<EmbeddedResource Include="Resources\Logo_Discord.png" />
<EmbeddedResource Include="Resources\Logo_GitHub.png" />
<EmbeddedResource Include="Resources\Logo_Patreon.png" />
<EmbeddedResource Include="Resources\Logo_Ryujinx.png" />
<EmbeddedResource Include="Resources\Logo_Twitter.png" />
<EmbeddedResource Include="Resources\Logo_Discord_Dark.png" />
<EmbeddedResource Include="Resources\Logo_Discord_Light.png" />
<EmbeddedResource Include="Resources\Logo_GitHub_Dark.png" />
<EmbeddedResource Include="Resources\Logo_GitHub_Light.png" />
<EmbeddedResource Include="Resources\Logo_Patreon_Dark.png" />
<EmbeddedResource Include="Resources\Logo_Patreon_Light.png" />
<EmbeddedResource Include="Resources\Logo_Twitter_Dark.png" />
<EmbeddedResource Include="Resources\Logo_Twitter_Light.png" />
</ItemGroup>
<ItemGroup>

View File

@@ -103,7 +103,7 @@ namespace Ryujinx.Modules
{
using (HttpClient jsonClient = ConstructHttpClient())
{
string buildInfoURL = $"{GitHubApiURL}/repos/{ReleaseInformations.ReleaseChannelOwner}/{ReleaseInformations.ReleaseChannelRepo}/releases/latest";
string buildInfoURL = $"{GitHubApiURL}/repos/{ReleaseInformation.ReleaseChannelOwner}/{ReleaseInformation.ReleaseChannelRepo}/releases/latest";
// Fetch latest build information
string fetchedJson = await jsonClient.GetStringAsync(buildInfoURL);
@@ -556,7 +556,7 @@ namespace Ryujinx.Modules
return false;
}
if (Program.Version.Contains("dirty") || !ReleaseInformations.IsValid())
if (Program.Version.Contains("dirty") || !ReleaseInformation.IsValid())
{
if (showWarnings)
{
@@ -570,7 +570,7 @@ namespace Ryujinx.Modules
#else
if (showWarnings)
{
if (ReleaseInformations.IsFlatHubBuild())
if (ReleaseInformation.IsFlatHubBuild())
{
GtkDialog.CreateWarningDialog("Updater Disabled!", "Please update Ryujinx via FlatHub.");
}

View File

@@ -18,6 +18,7 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Threading.Tasks;
namespace Ryujinx
@@ -72,9 +73,51 @@ namespace Ryujinx
}
}
[SupportedOSPlatform("linux")]
static void RegisterMimeTypes()
{
if (ReleaseInformation.IsFlatHubBuild())
{
return;
}
string mimeDbPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".local", "share", "mime");
if (!File.Exists(Path.Combine(mimeDbPath, "packages", "Ryujinx.xml")))
{
string mimeTypesFile = Path.Combine(ReleaseInformation.GetBaseApplicationDirectory(), "mime", "Ryujinx.xml");
using Process mimeProcess = new();
mimeProcess.StartInfo.FileName = "xdg-mime";
mimeProcess.StartInfo.Arguments = $"install --novendor --mode user {mimeTypesFile}";
mimeProcess.Start();
mimeProcess.WaitForExit();
if (mimeProcess.ExitCode != 0)
{
Logger.Error?.PrintMsg(LogClass.Application, $"Unable to install mime types. Make sure xdg-utils is installed. Process exited with code: {mimeProcess.ExitCode}");
return;
}
using Process updateMimeProcess = new();
updateMimeProcess.StartInfo.FileName = "update-mime-database";
updateMimeProcess.StartInfo.Arguments = mimeDbPath;
updateMimeProcess.Start();
updateMimeProcess.WaitForExit();
if (updateMimeProcess.ExitCode != 0)
{
Logger.Error?.PrintMsg(LogClass.Application, $"Could not update local mime database. Process exited with code: {updateMimeProcess.ExitCode}");
}
}
}
static void Main(string[] args)
{
Version = ReleaseInformations.GetVersion();
Version = ReleaseInformation.GetVersion();
if (OperatingSystem.IsWindows() && !OperatingSystem.IsWindowsVersionAtLeast(10, 0, 17134))
{
@@ -101,6 +144,8 @@ namespace Ryujinx
if (OperatingSystem.IsLinux())
{
XInitThreads();
Environment.SetEnvironmentVariable("GDK_BACKEND", "x11");
setenv("GDK_BACKEND", "x11", 1);
}
if (OperatingSystem.IsMacOS())
@@ -144,6 +189,12 @@ namespace Ryujinx
// Initialize the logger system.
LoggerModule.Initialize();
// Register mime types on linux.
if (OperatingSystem.IsLinux())
{
RegisterMimeTypes();
}
// Initialize Discord integration.
DiscordIntegrationModule.Initialize();

View File

@@ -62,6 +62,16 @@
</Content>
</ItemGroup>
<ItemGroup Condition="'$(RuntimeIdentifier)' == 'linux-x64'">
<Content Include="..\distribution\linux\Ryujinx.sh">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="..\distribution\linux\mime\Ryujinx.xml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<TargetPath>mime\Ryujinx.xml</TargetPath>
</Content>
</ItemGroup>
<!-- Due to .net core 3.1 embedded resource loading -->
<PropertyGroup>
<EmbeddedResourceUseDependentUponConvention>false</EmbeddedResourceUseDependentUponConvention>

View File

@@ -1294,7 +1294,7 @@ namespace Ryujinx.Ui
private void OpenLogsFolder_Pressed(object sender, EventArgs args)
{
string logPath = System.IO.Path.Combine(ReleaseInformations.GetBaseApplicationDirectory(), "Logs");
string logPath = System.IO.Path.Combine(ReleaseInformation.GetBaseApplicationDirectory(), "Logs");
new DirectoryInfo(logPath).Create();

View File

@@ -206,7 +206,7 @@ namespace Ryujinx.Ui.Windows
//
// _patreonLogo
//
_patreonLogo = new Image(new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Ui.Common.Resources.Logo_Patreon.png", 30, 30))
_patreonLogo = new Image(new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Ui.Common.Resources.Logo_Patreon_Light.png", 30, 30))
{
Margin = 10
};
@@ -236,7 +236,7 @@ namespace Ryujinx.Ui.Windows
//
// _githubLogo
//
_githubLogo = new Image(new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Ui.Common.Resources.Logo_GitHub.png", 30, 30))
_githubLogo = new Image(new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Ui.Common.Resources.Logo_GitHub_Light.png", 30, 30))
{
Margin = 10
};
@@ -266,7 +266,7 @@ namespace Ryujinx.Ui.Windows
//
// _discordLogo
//
_discordLogo = new Image(new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Ui.Common.Resources.Logo_Discord.png", 30, 30))
_discordLogo = new Image(new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Ui.Common.Resources.Logo_Discord_Light.png", 30, 30))
{
Margin = 10
};
@@ -296,7 +296,7 @@ namespace Ryujinx.Ui.Windows
//
// _twitterLogo
//
_twitterLogo = new Image(new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Ui.Common.Resources.Logo_Twitter.png", 30, 30))
_twitterLogo = new Image(new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Ui.Common.Resources.Logo_Twitter_Light.png", 30, 30))
{
Margin = 10
};

View File

@@ -1,13 +1,13 @@
[Desktop Entry]
Version=1.0
Name=Ryujinx
Comment=A Nintendo Switch Emulator
Type=Application
Icon=Ryujinx
Exec=env DOTNET_EnableAlternateStackCheck=1 Ryujinx %f
Comment=A Nintendo Switch Emulator
GenericName=Nintendo Switch Emulator
Icon=ryujinx
Terminal=false
Exec=Ryujinx %f
Categories=Game;Emulator;GTK;
Categories=Game;Emulator;
MimeType=application/x-nx-nca;application/x-nx-nro;application/x-nx-nso;application/x-nx-nsp;application/x-nx-xci;
Keywords=Switch;Nintendo;Emulator;
StartupWMClass=Ryujinx

View File

@@ -0,0 +1,5 @@
#!/bin/sh
SCRIPT_DIR=$(dirname $(realpath $0))
env DOTNET_EnableAlternateStackCheck=1 "$SCRIPT_DIR/Ryujinx" "$@"

View File

@@ -2,22 +2,32 @@
<mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info">
<mime-type type="application/x-nx-nca">
<comment>Nintendo Content Archive</comment>
<acronym>NCA</acronym>
<glob pattern="*.nca"/>
<magic><match value="NCA" type="string" offset="512"/></magic>
</mime-type>
<mime-type type="application/x-nx-nro">
<comment>Nintendo Relocatable Object</comment>
<acronym>NRO</acronym>
<glob pattern="*.nro"/>
<magic><match value="NRO0" type="string" offset="16"/></magic>
</mime-type>
<mime-type type="application/x-nx-nso">
<comment>Nintendo Shared Object</comment>
<acronym>NSO</acronym>
<glob pattern="*.nso"/>
<magic><match value="NSO0" type="string" offset="0"/></magic>
</mime-type>
<mime-type type="application/x-nx-nsp">
<comment>Nintendo Submission Package</comment>
<acronym>NSP</acronym>
<glob pattern="*.nsp"/>
<magic><match value="PFS0" type="string" offset="0"/></magic>
</mime-type>
<mime-type type="application/x-nx-xci">
<comment>Nintendo Switch Cartridge</comment>
<acronym>XCI</acronym>
<glob pattern="*.xci"/>
<magic><match value="HEAD" type="string" offset="4352"/></magic>
</mime-type>
</mime-info>

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB