This commit is contained in:
Evan Husted 2024-12-18 08:49:23 +01:00 committed by GitHub
commit 0cb8b4feef
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 639 additions and 499 deletions

View file

@ -64,14 +64,9 @@ jobs:
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.platform.name }}" -o ./publish -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx --self-contained
if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13'
- name: Publish Ryujinx.Headless.SDL2
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.platform.name }}" -o ./publish_sdl2_headless -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx.Headless.SDL2 --self-contained
if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13'
- name: Set executable bit
run: |
chmod +x ./publish/Ryujinx ./publish/Ryujinx.sh
chmod +x ./publish_sdl2_headless/Ryujinx.Headless.SDL2 ./publish_sdl2_headless/Ryujinx.sh
if: github.event_name == 'pull_request' && matrix.platform.os == 'ubuntu-latest'
- name: Build AppImage
@ -119,13 +114,6 @@ jobs:
name: ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.platform.zip_os_name }}-AppImage
path: publish_appimage
- name: Upload Ryujinx.Headless.SDL2 artifact
uses: actions/upload-artifact@v4
with:
name: nogui-ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.platform.zip_os_name }}
path: publish_sdl2_headless
if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13'
build_macos:
name: macOS Universal (${{ matrix.configuration }})
runs-on: ubuntu-latest
@ -171,20 +159,9 @@ jobs:
run: |
./distribution/macos/create_macos_build_ava.sh . publish_tmp publish ./distribution/macos/entitlements.xml "${{ env.RYUJINX_BASE_VERSION }}" "${{ steps.git_short_hash.outputs.result }}" "${{ matrix.configuration }}" "-p:ExtraDefineConstants=DISABLE_UPDATER"
- name: Publish macOS Ryujinx.Headless.SDL2
run: |
./distribution/macos/create_macos_build_headless.sh . publish_tmp_headless publish_headless ./distribution/macos/entitlements.xml "${{ env.RYUJINX_BASE_VERSION }}" "${{ steps.git_short_hash.outputs.result }}" "${{ matrix.configuration }}" "-p:ExtraDefineConstants=DISABLE_UPDATER"
- name: Upload Ryujinx artifact
uses: actions/upload-artifact@v4
with:
name: ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-macos_universal
path: "publish/*.tar.gz"
if: github.event_name == 'pull_request'
- name: Upload Ryujinx.Headless.SDL2 artifact
uses: actions/upload-artifact@v4
with:
name: nogui-ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-macos_universal
path: "publish_headless/*.tar.gz"
if: github.event_name == 'pull_request'

View file

@ -116,7 +116,6 @@ jobs:
- name: Publish
run: |
dotnet publish -c Release -r "${{ matrix.platform.name }}" -o ./publish_ava/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx --self-contained
dotnet publish -c Release -r "${{ matrix.platform.name }}" -o ./publish_sdl2_headless/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx.Headless.SDL2 --self-contained
- name: Packing Windows builds
if: matrix.platform.os == 'windows-latest'
@ -125,11 +124,6 @@ jobs:
rm publish/libarmeilleure-jitsupport.dylib
7z a ../release_output/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip publish
popd
pushd publish_sdl2_headless
rm publish/libarmeilleure-jitsupport.dylib
7z a ../release_output/nogui-ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip publish
popd
shell: bash
- name: Packing Linux builds
@ -140,12 +134,6 @@ jobs:
chmod +x publish/Ryujinx.sh publish/Ryujinx
tar -czvf ../release_output/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz publish
popd
pushd publish_sdl2_headless
rm publish/libarmeilleure-jitsupport.dylib
chmod +x publish/Ryujinx.sh publish/Ryujinx.Headless.SDL2
tar -czvf ../release_output/nogui-ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz publish
popd
shell: bash
#- name: Build AppImage (Linux)
@ -191,7 +179,7 @@ jobs:
with:
name: ${{ steps.version_info.outputs.build_version }}
artifacts: "release_output/*.tar.gz,release_output/*.zip"
#artifacts: "release_output/*.tar.gz,release_output/*.zip/*AppImage*"
#artifacts: "release_output/*.tar.gz,release_output/*.zip,release_output/*AppImage*"
tag: ${{ steps.version_info.outputs.build_version }}
body: |
# Canary builds:
@ -262,15 +250,11 @@ jobs:
run: |
./distribution/macos/create_macos_build_ava.sh . publish_tmp_ava publish_ava ./distribution/macos/entitlements.xml "${{ steps.version_info.outputs.build_version }}" "${{ steps.version_info.outputs.git_short_hash }}" Release 1
- name: Publish macOS Ryujinx.Headless.SDL2
run: |
./distribution/macos/create_macos_build_headless.sh . publish_tmp_headless publish_headless ./distribution/macos/entitlements.xml "${{ steps.version_info.outputs.build_version }}" "${{ steps.version_info.outputs.git_short_hash }}" Release 1
- name: Pushing new release
uses: ncipollo/release-action@v1
with:
name: "Canary ${{ steps.version_info.outputs.build_version }}"
artifacts: "publish_ava/*.tar.gz, publish_headless/*.tar.gz"
artifacts: "publish_ava/*.tar.gz"
tag: ${{ steps.version_info.outputs.build_version }}
body: |
# Canary builds:

View file

@ -38,20 +38,15 @@ jobs:
return core.error(`No artifacts found`);
}
let body = `Download the artifacts for this pull request:\n`;
let hidden_headless_artifacts = `\n\n <details><summary>GUI-less</summary>\n`;
let hidden_debug_artifacts = `\n\n <details><summary>Only for Developers</summary>\n`;
for (const art of artifacts) {
if(art.name.includes('Debug')) {
hidden_debug_artifacts += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
} else if(art.name.includes('nogui-ryujinx')) {
hidden_headless_artifacts += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
} else {
body += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
}
}
hidden_headless_artifacts += `\n</details>`;
hidden_debug_artifacts += `\n</details>`;
body += hidden_headless_artifacts;
body += hidden_debug_artifacts;
const {data: comments} = await github.rest.issues.listComments({repo, owner, issue_number});

View file

@ -112,7 +112,6 @@ jobs:
- name: Publish
run: |
dotnet publish -c Release -r "${{ matrix.platform.name }}" -o ./publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx --self-contained
dotnet publish -c Release -r "${{ matrix.platform.name }}" -o ./publish_sdl2_headless -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx.Headless.SDL2 --self-contained
- name: Packing Windows builds
if: matrix.platform.os == 'windows-latest'
@ -121,11 +120,6 @@ jobs:
rm libarmeilleure-jitsupport.dylib
7z a ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip ../publish
popd
pushd publish_sdl2_headless
rm libarmeilleure-jitsupport.dylib
7z a ../release_output/nogui-ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip ../publish
popd
shell: bash
- name: Build AppImage (Linux)
@ -172,11 +166,6 @@ jobs:
chmod +x Ryujinx.sh Ryujinx
tar -czvf ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz ../publish
popd
pushd publish_sdl2_headless
chmod +x Ryujinx.sh Ryujinx.Headless.SDL2
tar -czvf ../release_output/nogui-ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz ../publish
popd
shell: bash
- name: Pushing new release
@ -251,15 +240,11 @@ jobs:
run: |
./distribution/macos/create_macos_build_ava.sh . publish_tmp_ava publish ./distribution/macos/entitlements.xml "${{ steps.version_info.outputs.build_version }}" "${{ steps.version_info.outputs.git_short_hash }}" Release 0
- name: Publish macOS Ryujinx.Headless.SDL2
run: |
./distribution/macos/create_macos_build_headless.sh . publish_tmp_headless publish_headless ./distribution/macos/entitlements.xml "${{ steps.version_info.outputs.build_version }}" "${{ steps.version_info.outputs.git_short_hash }}" Release 0
- name: Pushing new release
uses: ncipollo/release-action@v1
with:
name: ${{ steps.version_info.outputs.build_version }}
artifacts: "publish/*.tar.gz, publish_headless/*.tar.gz"
artifacts: "publish/*.tar.gz"
tag: ${{ steps.version_info.outputs.build_version }}
body: |
# Regular builds:

View file

@ -57,8 +57,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.SDL2.Common", "src\
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Audio.Backends.SDL2", "src\Ryujinx.Audio.Backends.SDL2\Ryujinx.Audio.Backends.SDL2.csproj", "{D99A395A-8569-4DB0-B336-900647890052}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Headless.SDL2", "src\Ryujinx.Headless.SDL2\Ryujinx.Headless.SDL2.csproj", "{390DC343-5CB4-4C79-A5DD-E3ED235E4C49}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Nvdec.FFmpeg", "src\Ryujinx.Graphics.Nvdec.FFmpeg\Ryujinx.Graphics.Nvdec.FFmpeg.csproj", "{BEE1C184-C9A4-410B-8DFC-FB74D5C93AEB}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx", "src\Ryujinx\Ryujinx.csproj", "{7C1B2721-13DA-4B62-B046-C626605ECCE6}"
@ -204,10 +202,6 @@ Global
{D99A395A-8569-4DB0-B336-900647890052}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D99A395A-8569-4DB0-B336-900647890052}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D99A395A-8569-4DB0-B336-900647890052}.Release|Any CPU.Build.0 = Release|Any CPU
{390DC343-5CB4-4C79-A5DD-E3ED235E4C49}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{390DC343-5CB4-4C79-A5DD-E3ED235E4C49}.Debug|Any CPU.Build.0 = Debug|Any CPU
{390DC343-5CB4-4C79-A5DD-E3ED235E4C49}.Release|Any CPU.ActiveCfg = Release|Any CPU
{390DC343-5CB4-4C79-A5DD-E3ED235E4C49}.Release|Any CPU.Build.0 = Release|Any CPU
{BEE1C184-C9A4-410B-8DFC-FB74D5C93AEB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BEE1C184-C9A4-410B-8DFC-FB74D5C93AEB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BEE1C184-C9A4-410B-8DFC-FB74D5C93AEB}.Release|Any CPU.ActiveCfg = Release|Any CPU

View file

@ -4,6 +4,7 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
@ -158,20 +159,15 @@ namespace Ryujinx.Common.Logging
}
private static ILogTarget GetTarget(string targetName)
{
foreach (var target in _logTargets)
{
if (target.Name.Equals(targetName))
{
return target;
}
}
return null;
}
=> _logTargets.FirstOrDefault(target => target.Name.Equals(targetName));
public static void AddTarget(ILogTarget target)
{
if (_logTargets.Any(t => t.Name == target.Name))
{
return;
}
_logTargets.Add(target);
Updated += target.Log;

View file

@ -27,11 +27,7 @@ namespace Ryujinx.Common.Logging.Targets
private readonly int _overflowTimeout;
string ILogTarget.Name { get => _target.Name; }
public AsyncLogTargetWrapper(ILogTarget target)
: this(target, -1)
{ }
string ILogTarget.Name => _target.Name;
public AsyncLogTargetWrapper(ILogTarget target, int queueLimit = -1, AsyncLogTargetOverflowAction overflowAction = AsyncLogTargetOverflowAction.Block)
{

View file

@ -1,3 +1,4 @@
using Gommon;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities;
@ -6,12 +7,13 @@ using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace Ryujinx.HLE.HOS.Services.Account.Acc
{
class AccountSaveDataManager
public class AccountSaveDataManager
{
private readonly string _profilesJsonPath = Path.Join(AppDataManager.BaseDirPath, "system", "Profiles.json");
private static readonly string _profilesJsonPath = Path.Join(AppDataManager.BaseDirPath, "system", "Profiles.json");
private static readonly ProfilesJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions());
@ -49,6 +51,16 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
}
}
public static Optional<UserProfile> GetLastUsedUser()
{
ProfilesJson profilesJson = JsonHelper.DeserializeFromFile(_profilesJsonPath, _serializerContext.ProfilesJson);
return profilesJson.Profiles
.FindFirst(profile => profile.AccountState == AccountState.Open)
.Convert(profileJson => new UserProfile(new UserId(profileJson.UserId), profileJson.Name,
profileJson.Image, profileJson.LastModifiedTimestamp));
}
public void Save(ConcurrentDictionary<string, UserProfile> profiles)
{
ProfilesJson profilesJson = new()

View file

@ -1,73 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<RuntimeIdentifiers>win-x64;osx-x64;linux-x64</RuntimeIdentifiers>
<OutputType>Exe</OutputType>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Version>1.0.0-dirty</Version>
<DefineConstants Condition=" '$(ExtraDefineConstants)' != '' ">$(DefineConstants);$(ExtraDefineConstants)</DefineConstants>
<SigningCertificate Condition=" '$(SigningCertificate)' == '' ">-</SigningCertificate>
<TieredPGO>true</TieredPGO>
<DefaultItemExcludes>$(DefaultItemExcludes);._*</DefaultItemExcludes>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="OpenTK.Core" />
<PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" />
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent" Condition="$([MSBuild]::IsOSPlatform('OSX'))">
<Exec Command="codesign --entitlements '$(ProjectDir)..\..\distribution\macos\entitlements.xml' -f -s $(SigningCertificate) '$(TargetDir)$(TargetName)'" />
</Target>
<ItemGroup>
<ProjectReference Include="..\Ryujinx.Graphics.Vulkan\Ryujinx.Graphics.Vulkan.csproj" />
<ProjectReference Include="..\Ryujinx.Input\Ryujinx.Input.csproj" />
<ProjectReference Include="..\Ryujinx.Input.SDL2\Ryujinx.Input.SDL2.csproj" />
<ProjectReference Include="..\Ryujinx.Audio.Backends.SDL2\Ryujinx.Audio.Backends.SDL2.csproj" />
<ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
<ProjectReference Include="..\Ryujinx.HLE\Ryujinx.HLE.csproj" />
<ProjectReference Include="..\ARMeilleure\ARMeilleure.csproj" />
<ProjectReference Include="..\Ryujinx.Graphics.OpenGL\Ryujinx.Graphics.OpenGL.csproj" />
<ProjectReference Include="..\Ryujinx.Graphics.Gpu\Ryujinx.Graphics.Gpu.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="CommandLineParser" />
<PackageReference Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'linux-arm64' AND '$(RuntimeIdentifier)' != 'win-x64'" />
</ItemGroup>
<ItemGroup>
<Content Include="..\..\distribution\legal\THIRDPARTY.md">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<TargetPath>THIRDPARTY.md</TargetPath>
</Content>
<Content Include="..\..\LICENSE.txt">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<TargetPath>LICENSE.txt</TargetPath>
</Content>
</ItemGroup>
<ItemGroup Condition="'$(RuntimeIdentifier)' == 'linux-x64' OR '$(RuntimeIdentifier)' == 'linux-arm64' OR ('$(RuntimeIdentifier)' == '' AND $([MSBuild]::IsOSPlatform('Linux')))">
<Content Include="..\..\distribution\linux\Ryujinx.sh">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Ryujinx.bmp" />
</ItemGroup>
<!-- Due to .net core 3.1 embedded resource loading -->
<PropertyGroup>
<EmbeddedResourceUseDependentUponConvention>false</EmbeddedResourceUseDependentUponConvention>
<ApplicationIcon>..\Ryujinx\Ryujinx.ico</ApplicationIcon>
</PropertyGroup>
<PropertyGroup Condition="'$(RuntimeIdentifier)' != ''">
<PublishSingleFile>true</PublishSingleFile>
<PublishTrimmed>true</PublishTrimmed>
<TrimMode>partial</TrimMode>
</PropertyGroup>
</Project>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

View file

@ -1,4 +1,5 @@
using Ryujinx.Common.Utilities;
using Ryujinx.HLE.HOS.SystemState;
using System.Text.Json.Serialization;
namespace Ryujinx.UI.Common.Configuration.System

View file

@ -132,7 +132,7 @@ namespace Ryujinx.UI.Common.Helper
if (uninstall)
{
// If the types don't already exist, there's nothing to do and we can call this operation successful.
// If the types don't already exist, there's nothing to do, and we can call this operation successful.
if (!AreMimeTypesRegisteredWindows())
{
return true;

View file

@ -2,7 +2,7 @@ using Ryujinx.HLE.UI;
using System.Threading;
using System.Threading.Tasks;
namespace Ryujinx.Headless.SDL2
namespace Ryujinx.Headless
{
/// <summary>
/// Headless text processing class, right now there is no way to forward the input to it.

View file

@ -1,6 +1,6 @@
using Ryujinx.HLE.UI;
namespace Ryujinx.Headless.SDL2
namespace Ryujinx.Headless
{
internal class HeadlessHostUiTheme : IHostUITheme
{

View file

@ -0,0 +1,361 @@
using DiscordRPC;
using LibHac.Tools.FsSystem;
using Ryujinx.Audio.Backends.SDL2;
using Ryujinx.Ava;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Common.Configuration.Hid.Controller;
using Ryujinx.Common.Configuration.Hid.Controller.Motion;
using Ryujinx.Common.Configuration.Hid.Keyboard;
using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.GAL.Multithreading;
using Ryujinx.Graphics.OpenGL;
using Ryujinx.Graphics.Vulkan;
using Ryujinx.HLE;
using Ryujinx.Input;
using Ryujinx.UI.Common;
using Ryujinx.UI.Common.Configuration;
using Silk.NET.Vulkan;
using System;
using System.IO;
using System.Text.Json;
using System.Threading.Tasks;
using ConfigGamepadInputId = Ryujinx.Common.Configuration.Hid.Controller.GamepadInputId;
using ConfigStickInputId = Ryujinx.Common.Configuration.Hid.Controller.StickInputId;
using Key = Ryujinx.Common.Configuration.Hid.Key;
namespace Ryujinx.Headless
{
public partial class HeadlessRyujinx
{
public static void Initialize()
{
// Ensure Discord presence timestamp begins at the absolute start of when Ryujinx is launched
DiscordIntegrationModule.StartedAt = Timestamps.Now;
// Delete backup files after updating.
Task.Run(Updater.CleanupUpdate);
// Hook unhandled exception and process exit events.
AppDomain.CurrentDomain.UnhandledException += (sender, e)
=> Program.ProcessUnhandledException(sender, e.ExceptionObject as Exception, e.IsTerminating);
AppDomain.CurrentDomain.ProcessExit += (_, _) => Program.Exit();
// Initialize the configuration.
ConfigurationState.Initialize();
// Initialize Discord integration.
DiscordIntegrationModule.Initialize();
// Logging system information.
Program.PrintSystemInfo();
}
private static InputConfig HandlePlayerConfiguration(string inputProfileName, string inputId, PlayerIndex index)
{
if (inputId == null)
{
if (index == PlayerIndex.Player1)
{
Logger.Info?.Print(LogClass.Application, $"{index} not configured, defaulting to default keyboard.");
// Default to keyboard
inputId = "0";
}
else
{
Logger.Info?.Print(LogClass.Application, $"{index} not configured");
return null;
}
}
IGamepad gamepad = _inputManager.KeyboardDriver.GetGamepad(inputId);
bool isKeyboard = true;
if (gamepad == null)
{
gamepad = _inputManager.GamepadDriver.GetGamepad(inputId);
isKeyboard = false;
if (gamepad == null)
{
Logger.Error?.Print(LogClass.Application, $"{index} gamepad not found (\"{inputId}\")");
return null;
}
}
string gamepadName = gamepad.Name;
gamepad.Dispose();
InputConfig config;
if (inputProfileName == null || inputProfileName.Equals("default"))
{
if (isKeyboard)
{
config = new StandardKeyboardInputConfig
{
Version = InputConfig.CurrentVersion,
Backend = InputBackendType.WindowKeyboard,
Id = null,
ControllerType = ControllerType.JoyconPair,
LeftJoycon = new LeftJoyconCommonConfig<Key>
{
DpadUp = Key.Up,
DpadDown = Key.Down,
DpadLeft = Key.Left,
DpadRight = Key.Right,
ButtonMinus = Key.Minus,
ButtonL = Key.E,
ButtonZl = Key.Q,
ButtonSl = Key.Unbound,
ButtonSr = Key.Unbound,
},
LeftJoyconStick = new JoyconConfigKeyboardStick<Key>
{
StickUp = Key.W,
StickDown = Key.S,
StickLeft = Key.A,
StickRight = Key.D,
StickButton = Key.F,
},
RightJoycon = new RightJoyconCommonConfig<Key>
{
ButtonA = Key.Z,
ButtonB = Key.X,
ButtonX = Key.C,
ButtonY = Key.V,
ButtonPlus = Key.Plus,
ButtonR = Key.U,
ButtonZr = Key.O,
ButtonSl = Key.Unbound,
ButtonSr = Key.Unbound,
},
RightJoyconStick = new JoyconConfigKeyboardStick<Key>
{
StickUp = Key.I,
StickDown = Key.K,
StickLeft = Key.J,
StickRight = Key.L,
StickButton = Key.H,
},
};
}
else
{
bool isNintendoStyle = gamepadName.Contains("Nintendo");
config = new StandardControllerInputConfig
{
Version = InputConfig.CurrentVersion,
Backend = InputBackendType.GamepadSDL2,
Id = null,
ControllerType = ControllerType.JoyconPair,
DeadzoneLeft = 0.1f,
DeadzoneRight = 0.1f,
RangeLeft = 1.0f,
RangeRight = 1.0f,
TriggerThreshold = 0.5f,
LeftJoycon = new LeftJoyconCommonConfig<ConfigGamepadInputId>
{
DpadUp = ConfigGamepadInputId.DpadUp,
DpadDown = ConfigGamepadInputId.DpadDown,
DpadLeft = ConfigGamepadInputId.DpadLeft,
DpadRight = ConfigGamepadInputId.DpadRight,
ButtonMinus = ConfigGamepadInputId.Minus,
ButtonL = ConfigGamepadInputId.LeftShoulder,
ButtonZl = ConfigGamepadInputId.LeftTrigger,
ButtonSl = ConfigGamepadInputId.Unbound,
ButtonSr = ConfigGamepadInputId.Unbound,
},
LeftJoyconStick = new JoyconConfigControllerStick<ConfigGamepadInputId, ConfigStickInputId>
{
Joystick = ConfigStickInputId.Left,
StickButton = ConfigGamepadInputId.LeftStick,
InvertStickX = false,
InvertStickY = false,
Rotate90CW = false,
},
RightJoycon = new RightJoyconCommonConfig<ConfigGamepadInputId>
{
ButtonA = isNintendoStyle ? ConfigGamepadInputId.A : ConfigGamepadInputId.B,
ButtonB = isNintendoStyle ? ConfigGamepadInputId.B : ConfigGamepadInputId.A,
ButtonX = isNintendoStyle ? ConfigGamepadInputId.X : ConfigGamepadInputId.Y,
ButtonY = isNintendoStyle ? ConfigGamepadInputId.Y : ConfigGamepadInputId.X,
ButtonPlus = ConfigGamepadInputId.Plus,
ButtonR = ConfigGamepadInputId.RightShoulder,
ButtonZr = ConfigGamepadInputId.RightTrigger,
ButtonSl = ConfigGamepadInputId.Unbound,
ButtonSr = ConfigGamepadInputId.Unbound,
},
RightJoyconStick = new JoyconConfigControllerStick<ConfigGamepadInputId, ConfigStickInputId>
{
Joystick = ConfigStickInputId.Right,
StickButton = ConfigGamepadInputId.RightStick,
InvertStickX = false,
InvertStickY = false,
Rotate90CW = false,
},
Motion = new StandardMotionConfigController
{
MotionBackend = MotionInputBackendType.GamepadDriver,
EnableMotion = true,
Sensitivity = 100,
GyroDeadzone = 1,
},
Rumble = new RumbleConfigController
{
StrongRumble = 1f,
WeakRumble = 1f,
EnableRumble = false,
},
};
}
}
else
{
string profileBasePath;
if (isKeyboard)
{
profileBasePath = Path.Combine(AppDataManager.ProfilesDirPath, "keyboard");
}
else
{
profileBasePath = Path.Combine(AppDataManager.ProfilesDirPath, "controller");
}
string path = Path.Combine(profileBasePath, inputProfileName + ".json");
if (!File.Exists(path))
{
Logger.Error?.Print(LogClass.Application, $"Input profile \"{inputProfileName}\" not found for \"{inputId}\"");
return null;
}
try
{
config = JsonHelper.DeserializeFromFile(path, _serializerContext.InputConfig);
}
catch (JsonException)
{
Logger.Error?.Print(LogClass.Application, $"Input profile \"{inputProfileName}\" parsing failed for \"{inputId}\"");
return null;
}
}
config.Id = inputId;
config.PlayerIndex = index;
string inputTypeName = isKeyboard ? "Keyboard" : "Gamepad";
Logger.Info?.Print(LogClass.Application, $"{config.PlayerIndex} configured with {inputTypeName} \"{config.Id}\"");
// If both stick ranges are 0 (usually indicative of an outdated profile load) then both sticks will be set to 1.0.
if (config is StandardControllerInputConfig controllerConfig)
{
if (controllerConfig.RangeLeft <= 0.0f && controllerConfig.RangeRight <= 0.0f)
{
controllerConfig.RangeLeft = 1.0f;
controllerConfig.RangeRight = 1.0f;
Logger.Info?.Print(LogClass.Application, $"{config.PlayerIndex} stick range reset. Save the profile now to update your configuration");
}
}
return config;
}
private static IRenderer CreateRenderer(Options options, WindowBase window)
{
if (options.GraphicsBackend == GraphicsBackend.Vulkan && window is VulkanWindow vulkanWindow)
{
string preferredGpuId = string.Empty;
Vk api = Vk.GetApi();
if (!string.IsNullOrEmpty(options.PreferredGPUVendor))
{
string preferredGpuVendor = options.PreferredGPUVendor.ToLowerInvariant();
var devices = VulkanRenderer.GetPhysicalDevices(api);
foreach (var device in devices)
{
if (device.Vendor.ToLowerInvariant() == preferredGpuVendor)
{
preferredGpuId = device.Id;
break;
}
}
}
return new VulkanRenderer(
api,
(instance, vk) => new SurfaceKHR((ulong)(vulkanWindow.CreateWindowSurface(instance.Handle))),
vulkanWindow.GetRequiredInstanceExtensions,
preferredGpuId);
}
return new OpenGLRenderer();
}
private static Switch InitializeEmulationContext(WindowBase window, IRenderer renderer, Options options)
{
BackendThreading threadingMode = options.BackendThreading;
bool threadedGAL = threadingMode == BackendThreading.On || (threadingMode == BackendThreading.Auto && renderer.PreferThreading);
if (threadedGAL)
{
renderer = new ThreadedRenderer(renderer);
}
HLEConfiguration configuration = new(_virtualFileSystem,
_libHacHorizonManager,
_contentManager,
_accountManager,
_userChannelPersistence,
renderer,
new SDL2HardwareDeviceDriver(),
options.DramSize,
window,
options.SystemLanguage,
options.SystemRegion,
options.VSyncMode,
!options.DisableDockedMode,
!options.DisablePTC,
options.EnableInternetAccess,
!options.DisableFsIntegrityChecks ? IntegrityCheckLevel.ErrorOnInvalid : IntegrityCheckLevel.None,
options.FsGlobalAccessLogMode,
options.SystemTimeOffset,
options.SystemTimeZone,
options.MemoryManagerMode,
options.IgnoreMissingServices,
options.AspectRatio,
options.AudioVolume,
options.UseHypervisor ?? true,
options.MultiplayerLanInterfaceId,
Common.Configuration.Multiplayer.MultiplayerMode.Disabled,
false,
string.Empty,
string.Empty,
options.CustomVSyncInterval);
return new Switch(configuration);
}
}
}

View file

@ -1,13 +1,9 @@
using CommandLine;
using Gommon;
using LibHac.Tools.FsSystem;
using Ryujinx.Audio.Backends.SDL2;
using Ryujinx.Ava;
using Ryujinx.Common;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Common.Configuration.Hid.Controller;
using Ryujinx.Common.Configuration.Hid.Controller.Motion;
using Ryujinx.Common.Configuration.Hid.Keyboard;
using Ryujinx.Common.GraphicsDriver;
using Ryujinx.Common.Logging;
using Ryujinx.Common.Logging.Targets;
@ -15,14 +11,9 @@ using Ryujinx.Common.SystemInterop;
using Ryujinx.Common.Utilities;
using Ryujinx.Cpu;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.GAL.Multithreading;
using Ryujinx.Graphics.Gpu;
using Ryujinx.Graphics.Gpu.Shader;
using Ryujinx.Graphics.OpenGL;
using Ryujinx.Graphics.Vulkan;
using Ryujinx.Graphics.Vulkan.MoltenVK;
using Ryujinx.Headless.SDL2.OpenGL;
using Ryujinx.Headless.SDL2.Vulkan;
using Ryujinx.HLE;
using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS;
@ -31,22 +22,16 @@ using Ryujinx.Input;
using Ryujinx.Input.HLE;
using Ryujinx.Input.SDL2;
using Ryujinx.SDL2.Common;
using Silk.NET.Vulkan;
using Ryujinx.UI.Common.Configuration;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.Json;
using System.Threading;
using ConfigGamepadInputId = Ryujinx.Common.Configuration.Hid.Controller.GamepadInputId;
using ConfigStickInputId = Ryujinx.Common.Configuration.Hid.Controller.StickInputId;
using Key = Ryujinx.Common.Configuration.Hid.Key;
namespace Ryujinx.Headless.SDL2
namespace Ryujinx.Headless
{
class Program
public partial class HeadlessRyujinx
{
public static string Version { get; private set; }
private static VirtualFileSystem _virtualFileSystem;
private static ContentManager _contentManager;
private static AccountManager _accountManager;
@ -56,20 +41,18 @@ namespace Ryujinx.Headless.SDL2
private static Switch _emulationContext;
private static WindowBase _window;
private static WindowsMultimediaTimerResolution _windowsMultimediaTimerResolution;
private static List<InputConfig> _inputConfiguration;
private static List<InputConfig> _inputConfiguration = [];
private static bool _enableKeyboard;
private static bool _enableMouse;
private static readonly InputConfigJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions());
static void Main(string[] args)
public static void Entrypoint(string[] args)
{
Version = ReleaseInformation.Version;
// Make process DPI aware for proper window sizing on high-res screens.
ForceDpiAware.Windows();
Console.Title = $"Ryujinx Console {Version} (Headless SDL2)";
Console.Title = $"Ryujinx Console {Program.Version} (Headless)";
if (OperatingSystem.IsMacOS() || OperatingSystem.IsLinux())
{
@ -97,7 +80,7 @@ namespace Ryujinx.Headless.SDL2
}
Parser.Default.ParseArguments<Options>(args)
.WithParsed(Load)
.WithParsed(options => Load(args, options))
.WithNotParsed(errors =>
{
Logger.Error?.PrintMsg(LogClass.Application, "Error parsing command-line arguments:");
@ -106,238 +89,80 @@ namespace Ryujinx.Headless.SDL2
});
}
private static InputConfig HandlePlayerConfiguration(string inputProfileName, string inputId, PlayerIndex index)
public static void ReloadConfig(string customConfigPath = null)
{
if (inputId == null)
{
if (index == PlayerIndex.Player1)
{
Logger.Info?.Print(LogClass.Application, $"{index} not configured, defaulting to default keyboard.");
string localConfigurationPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ReleaseInformation.ConfigName);
string appDataConfigurationPath = Path.Combine(AppDataManager.BaseDirPath, ReleaseInformation.ConfigName);
// Default to keyboard
inputId = "0";
string configurationPath = null;
// Now load the configuration as the other subsystems are now registered
if (customConfigPath != null && File.Exists(customConfigPath))
{
configurationPath = customConfigPath;
}
else if (File.Exists(localConfigurationPath))
{
configurationPath = localConfigurationPath;
}
else if (File.Exists(appDataConfigurationPath))
{
configurationPath = appDataConfigurationPath;
}
if (configurationPath == null)
{
// No configuration, we load the default values and save it to disk
configurationPath = appDataConfigurationPath;
Logger.Notice.Print(LogClass.Application, $"No configuration file found. Saving default configuration to: {configurationPath}");
ConfigurationState.Instance.LoadDefault();
ConfigurationState.Instance.ToFileFormat().SaveConfig(configurationPath);
}
else
{
Logger.Info?.Print(LogClass.Application, $"{index} not configured");
Logger.Notice.Print(LogClass.Application, $"Loading configuration from: {configurationPath}");
return null;
}
}
IGamepad gamepad = _inputManager.KeyboardDriver.GetGamepad(inputId);
bool isKeyboard = true;
if (gamepad == null)
if (ConfigurationFileFormat.TryLoad(configurationPath, out ConfigurationFileFormat configurationFileFormat))
{
gamepad = _inputManager.GamepadDriver.GetGamepad(inputId);
isKeyboard = false;
if (gamepad == null)
{
Logger.Error?.Print(LogClass.Application, $"{index} gamepad not found (\"{inputId}\")");
return null;
}
}
string gamepadName = gamepad.Name;
gamepad.Dispose();
InputConfig config;
if (inputProfileName == null || inputProfileName.Equals("default"))
{
if (isKeyboard)
{
config = new StandardKeyboardInputConfig
{
Version = InputConfig.CurrentVersion,
Backend = InputBackendType.WindowKeyboard,
Id = null,
ControllerType = ControllerType.JoyconPair,
LeftJoycon = new LeftJoyconCommonConfig<Key>
{
DpadUp = Key.Up,
DpadDown = Key.Down,
DpadLeft = Key.Left,
DpadRight = Key.Right,
ButtonMinus = Key.Minus,
ButtonL = Key.E,
ButtonZl = Key.Q,
ButtonSl = Key.Unbound,
ButtonSr = Key.Unbound,
},
LeftJoyconStick = new JoyconConfigKeyboardStick<Key>
{
StickUp = Key.W,
StickDown = Key.S,
StickLeft = Key.A,
StickRight = Key.D,
StickButton = Key.F,
},
RightJoycon = new RightJoyconCommonConfig<Key>
{
ButtonA = Key.Z,
ButtonB = Key.X,
ButtonX = Key.C,
ButtonY = Key.V,
ButtonPlus = Key.Plus,
ButtonR = Key.U,
ButtonZr = Key.O,
ButtonSl = Key.Unbound,
ButtonSr = Key.Unbound,
},
RightJoyconStick = new JoyconConfigKeyboardStick<Key>
{
StickUp = Key.I,
StickDown = Key.K,
StickLeft = Key.J,
StickRight = Key.L,
StickButton = Key.H,
},
};
ConfigurationState.Instance.Load(configurationFileFormat, configurationPath);
}
else
{
bool isNintendoStyle = gamepadName.Contains("Nintendo");
Logger.Warning?.PrintMsg(LogClass.Application, $"Failed to load config! Loading the default config instead.\nFailed config location: {configurationPath}");
config = new StandardControllerInputConfig
{
Version = InputConfig.CurrentVersion,
Backend = InputBackendType.GamepadSDL2,
Id = null,
ControllerType = ControllerType.JoyconPair,
DeadzoneLeft = 0.1f,
DeadzoneRight = 0.1f,
RangeLeft = 1.0f,
RangeRight = 1.0f,
TriggerThreshold = 0.5f,
LeftJoycon = new LeftJoyconCommonConfig<ConfigGamepadInputId>
{
DpadUp = ConfigGamepadInputId.DpadUp,
DpadDown = ConfigGamepadInputId.DpadDown,
DpadLeft = ConfigGamepadInputId.DpadLeft,
DpadRight = ConfigGamepadInputId.DpadRight,
ButtonMinus = ConfigGamepadInputId.Minus,
ButtonL = ConfigGamepadInputId.LeftShoulder,
ButtonZl = ConfigGamepadInputId.LeftTrigger,
ButtonSl = ConfigGamepadInputId.Unbound,
ButtonSr = ConfigGamepadInputId.Unbound,
},
LeftJoyconStick = new JoyconConfigControllerStick<ConfigGamepadInputId, ConfigStickInputId>
{
Joystick = ConfigStickInputId.Left,
StickButton = ConfigGamepadInputId.LeftStick,
InvertStickX = false,
InvertStickY = false,
Rotate90CW = false,
},
RightJoycon = new RightJoyconCommonConfig<ConfigGamepadInputId>
{
ButtonA = isNintendoStyle ? ConfigGamepadInputId.A : ConfigGamepadInputId.B,
ButtonB = isNintendoStyle ? ConfigGamepadInputId.B : ConfigGamepadInputId.A,
ButtonX = isNintendoStyle ? ConfigGamepadInputId.X : ConfigGamepadInputId.Y,
ButtonY = isNintendoStyle ? ConfigGamepadInputId.Y : ConfigGamepadInputId.X,
ButtonPlus = ConfigGamepadInputId.Plus,
ButtonR = ConfigGamepadInputId.RightShoulder,
ButtonZr = ConfigGamepadInputId.RightTrigger,
ButtonSl = ConfigGamepadInputId.Unbound,
ButtonSr = ConfigGamepadInputId.Unbound,
},
RightJoyconStick = new JoyconConfigControllerStick<ConfigGamepadInputId, ConfigStickInputId>
{
Joystick = ConfigStickInputId.Right,
StickButton = ConfigGamepadInputId.RightStick,
InvertStickX = false,
InvertStickY = false,
Rotate90CW = false,
},
Motion = new StandardMotionConfigController
{
MotionBackend = MotionInputBackendType.GamepadDriver,
EnableMotion = true,
Sensitivity = 100,
GyroDeadzone = 1,
},
Rumble = new RumbleConfigController
{
StrongRumble = 1f,
WeakRumble = 1f,
EnableRumble = false,
},
};
ConfigurationState.Instance.LoadDefault();
}
}
else
{
string profileBasePath;
if (isKeyboard)
{
profileBasePath = Path.Combine(AppDataManager.ProfilesDirPath, "keyboard");
}
else
{
profileBasePath = Path.Combine(AppDataManager.ProfilesDirPath, "controller");
}
string path = Path.Combine(profileBasePath, inputProfileName + ".json");
if (!File.Exists(path))
{
Logger.Error?.Print(LogClass.Application, $"Input profile \"{inputProfileName}\" not found for \"{inputId}\"");
return null;
}
try
{
config = JsonHelper.DeserializeFromFile(path, _serializerContext.InputConfig);
}
catch (JsonException)
{
Logger.Error?.Print(LogClass.Application, $"Input profile \"{inputProfileName}\" parsing failed for \"{inputId}\"");
return null;
}
}
config.Id = inputId;
config.PlayerIndex = index;
string inputTypeName = isKeyboard ? "Keyboard" : "Gamepad";
Logger.Info?.Print(LogClass.Application, $"{config.PlayerIndex} configured with {inputTypeName} \"{config.Id}\"");
// If both stick ranges are 0 (usually indicative of an outdated profile load) then both sticks will be set to 1.0.
if (config is StandardControllerInputConfig controllerConfig)
static void Load(string[] originalArgs, Options option)
{
if (controllerConfig.RangeLeft <= 0.0f && controllerConfig.RangeRight <= 0.0f)
{
controllerConfig.RangeLeft = 1.0f;
controllerConfig.RangeRight = 1.0f;
Initialize();
Logger.Info?.Print(LogClass.Application, $"{config.PlayerIndex} stick range reset. Save the profile now to update your configuration");
}
bool useLastUsedProfile = false;
if (option.InheritConfig)
{
option.InheritMainConfig(originalArgs, ConfigurationState.Instance, out useLastUsedProfile);
}
return config;
}
static void Load(Options option)
{
AppDataManager.Initialize(option.BaseDataDir);
if (useLastUsedProfile && AccountSaveDataManager.GetLastUsedUser().TryGet(out var profile))
option.UserProfile = profile.Name;
// Check if keys exists.
if (!File.Exists(Path.Combine(AppDataManager.KeysDirPath, "prod.keys")))
{
if (!(AppDataManager.Mode == AppDataManager.LaunchMode.UserProfile && File.Exists(Path.Combine(AppDataManager.KeysDirPathUser, "prod.keys"))))
{
Logger.Error?.Print(LogClass.Application, "Keys not found");
}
}
ReloadConfig();
_virtualFileSystem = VirtualFileSystem.CreateInstance();
_libHacHorizonManager = new LibHacHorizonManager();
@ -352,7 +177,7 @@ namespace Ryujinx.Headless.SDL2
_inputManager = new InputManager(new SDL2KeyboardDriver(), new SDL2GamepadDriver());
GraphicsConfig.EnableShaderCache = true;
GraphicsConfig.EnableShaderCache = !option.DisableShaderCache;
if (OperatingSystem.IsMacOS())
{
@ -363,15 +188,13 @@ namespace Ryujinx.Headless.SDL2
}
}
IGamepad gamepad;
if (option.ListInputIds)
{
Logger.Info?.Print(LogClass.Application, "Input Ids:");
foreach (string id in _inputManager.KeyboardDriver.GamepadsIds)
{
gamepad = _inputManager.KeyboardDriver.GetGamepad(id);
IGamepad gamepad = _inputManager.KeyboardDriver.GetGamepad(id);
Logger.Info?.Print(LogClass.Application, $"- {id} (\"{gamepad.Name}\")");
@ -380,7 +203,7 @@ namespace Ryujinx.Headless.SDL2
foreach (string id in _inputManager.GamepadDriver.GamepadsIds)
{
gamepad = _inputManager.GamepadDriver.GetGamepad(id);
IGamepad gamepad = _inputManager.GamepadDriver.GetGamepad(id);
Logger.Info?.Print(LogClass.Application, $"- {id} (\"{gamepad.Name}\")");
@ -397,7 +220,7 @@ namespace Ryujinx.Headless.SDL2
return;
}
_inputConfiguration = new List<InputConfig>();
_inputConfiguration ??= [];
_enableKeyboard = option.EnableKeyboard;
_enableMouse = option.EnableMouse;
@ -421,6 +244,7 @@ namespace Ryujinx.Headless.SDL2
LoadPlayerConfiguration(option.InputProfile8Name, option.InputId8, PlayerIndex.Player8);
LoadPlayerConfiguration(option.InputProfileHandheldName, option.InputIdHandheld, PlayerIndex.Handheld);
if (_inputConfiguration.Count == 0)
{
return;
@ -431,7 +255,7 @@ namespace Ryujinx.Headless.SDL2
Logger.SetEnable(LogLevel.Stub, !option.LoggingDisableStub);
Logger.SetEnable(LogLevel.Info, !option.LoggingDisableInfo);
Logger.SetEnable(LogLevel.Warning, !option.LoggingDisableWarning);
Logger.SetEnable(LogLevel.Error, option.LoggingEnableError);
Logger.SetEnable(LogLevel.Error, !option.LoggingDisableError);
Logger.SetEnable(LogLevel.Trace, option.LoggingEnableTrace);
Logger.SetEnable(LogLevel.Guest, !option.LoggingDisableGuest);
Logger.SetEnable(LogLevel.AccessLog, option.LoggingEnableFsAccessLog);
@ -515,83 +339,6 @@ namespace Ryujinx.Headless.SDL2
: new OpenGLWindow(_inputManager, options.LoggingGraphicsDebugLevel, options.AspectRatio, options.EnableMouse, options.HideCursorMode, options.IgnoreControllerApplet);
}
private static IRenderer CreateRenderer(Options options, WindowBase window)
{
if (options.GraphicsBackend == GraphicsBackend.Vulkan && window is VulkanWindow vulkanWindow)
{
string preferredGpuId = string.Empty;
Vk api = Vk.GetApi();
if (!string.IsNullOrEmpty(options.PreferredGPUVendor))
{
string preferredGpuVendor = options.PreferredGPUVendor.ToLowerInvariant();
var devices = VulkanRenderer.GetPhysicalDevices(api);
foreach (var device in devices)
{
if (device.Vendor.ToLowerInvariant() == preferredGpuVendor)
{
preferredGpuId = device.Id;
break;
}
}
}
return new VulkanRenderer(
api,
(instance, vk) => new SurfaceKHR((ulong)(vulkanWindow.CreateWindowSurface(instance.Handle))),
vulkanWindow.GetRequiredInstanceExtensions,
preferredGpuId);
}
return new OpenGLRenderer();
}
private static Switch InitializeEmulationContext(WindowBase window, IRenderer renderer, Options options)
{
BackendThreading threadingMode = options.BackendThreading;
bool threadedGAL = threadingMode == BackendThreading.On || (threadingMode == BackendThreading.Auto && renderer.PreferThreading);
if (threadedGAL)
{
renderer = new ThreadedRenderer(renderer);
}
HLEConfiguration configuration = new(_virtualFileSystem,
_libHacHorizonManager,
_contentManager,
_accountManager,
_userChannelPersistence,
renderer,
new SDL2HardwareDeviceDriver(),
options.DramSize,
window,
options.SystemLanguage,
options.SystemRegion,
options.VSyncMode,
!options.DisableDockedMode,
!options.DisablePTC,
options.EnableInternetAccess,
!options.DisableFsIntegrityChecks ? IntegrityCheckLevel.ErrorOnInvalid : IntegrityCheckLevel.None,
options.FsGlobalAccessLogMode,
options.SystemTimeOffset,
options.SystemTimeZone,
options.MemoryManagerMode,
options.IgnoreMissingServices,
options.AspectRatio,
options.AudioVolume,
options.UseHypervisor ?? true,
options.MultiplayerLanInterfaceId,
Common.Configuration.Multiplayer.MultiplayerMode.Disabled,
false,
string.Empty,
string.Empty,
options.CustomVSyncInterval);
return new Switch(configuration);
}
private static void ExecutionEntrypoint()
{
if (OperatingSystem.IsWindows())

View file

@ -7,7 +7,7 @@ using Ryujinx.Input.HLE;
using System;
using static SDL2.SDL;
namespace Ryujinx.Headless.SDL2.OpenGL
namespace Ryujinx.Headless
{
class OpenGLWindow : WindowBase
{

View file

@ -1,14 +1,169 @@
using CommandLine;
using Gommon;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Configuration.Hid;
using Ryujinx.HLE;
using Ryujinx.HLE.HOS.Services.Account.Acc;
using Ryujinx.HLE.HOS.SystemState;
using Ryujinx.UI.Common.Configuration;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace Ryujinx.Headless.SDL2
namespace Ryujinx.Headless
{
public class Options
{
public void InheritMainConfig(string[] originalArgs, ConfigurationState configurationState, out bool needsProfileSet)
{
needsProfileSet = NeedsOverride(nameof(UserProfile));
if (NeedsOverride(nameof(IsFullscreen)))
IsFullscreen = configurationState.UI.StartFullscreen;
if (NeedsOverride(nameof(EnableKeyboard)))
EnableKeyboard = configurationState.Hid.EnableKeyboard;
if (NeedsOverride(nameof(EnableMouse)))
EnableMouse = configurationState.Hid.EnableMouse;
if (NeedsOverride(nameof(HideCursorMode)))
HideCursorMode = configurationState.HideCursor;
if (NeedsOverride(nameof(DisablePTC)))
DisablePTC = !configurationState.System.EnablePtc;
if (NeedsOverride(nameof(EnableInternetAccess)))
EnableInternetAccess = configurationState.System.EnableInternetAccess;
if (NeedsOverride(nameof(DisableFsIntegrityChecks)))
DisableFsIntegrityChecks = configurationState.System.EnableFsIntegrityChecks;
if (NeedsOverride(nameof(FsGlobalAccessLogMode)))
FsGlobalAccessLogMode = configurationState.System.FsGlobalAccessLogMode;
if (NeedsOverride(nameof(VSyncMode)))
VSyncMode = configurationState.Graphics.VSyncMode;
if (NeedsOverride(nameof(CustomVSyncInterval)))
CustomVSyncInterval = configurationState.Graphics.CustomVSyncInterval;
if (NeedsOverride(nameof(DisableShaderCache)))
DisableShaderCache = !configurationState.Graphics.EnableShaderCache;
if (NeedsOverride(nameof(EnableTextureRecompression)))
EnableTextureRecompression = configurationState.Graphics.EnableTextureRecompression;
if (NeedsOverride(nameof(DisableDockedMode)))
DisableDockedMode = !configurationState.System.EnableDockedMode;
if (NeedsOverride(nameof(SystemLanguage)))
SystemLanguage = (SystemLanguage)(int)configurationState.System.Language.Value;
if (NeedsOverride(nameof(SystemRegion)))
SystemRegion = (RegionCode)(int)configurationState.System.Region.Value;
if (NeedsOverride(nameof(SystemTimeZone)))
SystemTimeZone = configurationState.System.TimeZone;
if (NeedsOverride(nameof(SystemTimeOffset)))
SystemTimeOffset = configurationState.System.SystemTimeOffset;
if (NeedsOverride(nameof(MemoryManagerMode)))
MemoryManagerMode = configurationState.System.MemoryManagerMode;
if (NeedsOverride(nameof(AudioVolume)))
AudioVolume = configurationState.System.AudioVolume;
if (NeedsOverride(nameof(UseHypervisor)) && OperatingSystem.IsMacOS())
UseHypervisor = configurationState.System.UseHypervisor;
if (NeedsOverride(nameof(MultiplayerLanInterfaceId)))
MultiplayerLanInterfaceId = configurationState.Multiplayer.LanInterfaceId;
if (NeedsOverride(nameof(DisableFileLog)))
DisableFileLog = !configurationState.Logger.EnableFileLog;
if (NeedsOverride(nameof(LoggingEnableDebug)))
LoggingEnableDebug = configurationState.Logger.EnableDebug;
if (NeedsOverride(nameof(LoggingDisableStub)))
LoggingDisableStub = !configurationState.Logger.EnableStub;
if (NeedsOverride(nameof(LoggingDisableInfo)))
LoggingDisableInfo = !configurationState.Logger.EnableInfo;
if (NeedsOverride(nameof(LoggingDisableWarning)))
LoggingDisableWarning = !configurationState.Logger.EnableWarn;
if (NeedsOverride(nameof(LoggingDisableError)))
LoggingDisableError = !configurationState.Logger.EnableError;
if (NeedsOverride(nameof(LoggingEnableTrace)))
LoggingEnableTrace = configurationState.Logger.EnableTrace;
if (NeedsOverride(nameof(LoggingDisableGuest)))
LoggingDisableGuest = !configurationState.Logger.EnableGuest;
if (NeedsOverride(nameof(LoggingEnableFsAccessLog)))
LoggingEnableFsAccessLog = configurationState.Logger.EnableFsAccessLog;
if (NeedsOverride(nameof(LoggingGraphicsDebugLevel)))
LoggingGraphicsDebugLevel = configurationState.Logger.GraphicsDebugLevel;
if (NeedsOverride(nameof(ResScale)))
ResScale = configurationState.Graphics.ResScale;
if (NeedsOverride(nameof(MaxAnisotropy)))
MaxAnisotropy = configurationState.Graphics.MaxAnisotropy;
if (NeedsOverride(nameof(AspectRatio)))
AspectRatio = configurationState.Graphics.AspectRatio;
if (NeedsOverride(nameof(BackendThreading)))
BackendThreading = configurationState.Graphics.BackendThreading;
if (NeedsOverride(nameof(DisableMacroHLE)))
DisableMacroHLE = !configurationState.Graphics.EnableMacroHLE;
if (NeedsOverride(nameof(GraphicsShadersDumpPath)))
GraphicsShadersDumpPath = configurationState.Graphics.ShadersDumpPath;
if (NeedsOverride(nameof(GraphicsBackend)))
GraphicsBackend = configurationState.Graphics.GraphicsBackend;
if (NeedsOverride(nameof(AntiAliasing)))
AntiAliasing = configurationState.Graphics.AntiAliasing;
if (NeedsOverride(nameof(ScalingFilter)))
ScalingFilter = configurationState.Graphics.ScalingFilter;
if (NeedsOverride(nameof(ScalingFilterLevel)))
ScalingFilterLevel = configurationState.Graphics.ScalingFilterLevel;
if (NeedsOverride(nameof(DramSize)))
DramSize = configurationState.System.DramSize;
if (NeedsOverride(nameof(IgnoreMissingServices)))
IgnoreMissingServices = configurationState.System.IgnoreMissingServices;
if (NeedsOverride(nameof(IgnoreControllerApplet)))
IgnoreControllerApplet = configurationState.IgnoreApplet;
return;
bool NeedsOverride(string argKey) => originalArgs.None(arg => arg.TrimStart('-').EqualsIgnoreCase(OptionName(argKey)));
string OptionName(string propertyName) =>
typeof(Options)!.GetProperty(propertyName)!.GetCustomAttribute<OptionAttribute>()!.LongName;
}
// General
[Option("use-main-config", Required = false, Default = false, HelpText = "Use the settings from what was configured via the UI.")]
public bool InheritConfig { get; set; }
[Option("root-data-dir", Required = false, HelpText = "Set the custom folder path for Ryujinx data.")]
public string BaseDataDir { get; set; }
@ -172,7 +327,7 @@ namespace Ryujinx.Headless.SDL2
public bool LoggingDisableWarning { get; set; }
[Option("disable-error-logs", Required = false, HelpText = "Disables printing error log messages.")]
public bool LoggingEnableError { get; set; }
public bool LoggingDisableError { get; set; }
[Option("enable-trace-logs", Required = false, Default = false, HelpText = "Enables printing trace log messages.")]
public bool LoggingEnableTrace { get; set; }

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

View file

@ -1,6 +1,6 @@
using System;
namespace Ryujinx.Headless.SDL2
namespace Ryujinx.Headless
{
class StatusUpdatedEventArgs(
string vSyncMode,

View file

@ -6,7 +6,7 @@ using System;
using System.Runtime.InteropServices;
using static SDL2.SDL;
namespace Ryujinx.Headless.SDL2.Vulkan
namespace Ryujinx.Headless
{
class VulkanWindow : WindowBase
{

View file

@ -1,5 +1,6 @@
using Humanizer;
using LibHac.Tools.Fs;
using Ryujinx.Ava;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Common.Logging;
@ -26,7 +27,7 @@ using AntiAliasing = Ryujinx.Common.Configuration.AntiAliasing;
using ScalingFilter = Ryujinx.Common.Configuration.ScalingFilter;
using Switch = Ryujinx.HLE.Switch;
namespace Ryujinx.Headless.SDL2
namespace Ryujinx.Headless
{
abstract partial class WindowBase : IHostUIHandler, IDisposable
{
@ -136,7 +137,7 @@ namespace Ryujinx.Headless.SDL2
private void SetWindowIcon()
{
Stream iconStream = typeof(WindowBase).Assembly.GetManifestResourceStream("Ryujinx.Headless.SDL2.Ryujinx.bmp");
Stream iconStream = typeof(Program).Assembly.GetManifestResourceStream("HeadlessLogo");
byte[] iconBytes = new byte[iconStream!.Length];
if (iconStream.Read(iconBytes, 0, iconBytes.Length) != iconBytes.Length)
@ -318,7 +319,7 @@ namespace Ryujinx.Headless.SDL2
Device.VSyncMode.ToString(),
dockedMode,
Device.Configuration.AspectRatio.ToText(),
$"Game: {Device.Statistics.GetGameFrameRate():00.00} FPS ({Device.Statistics.GetGameFrameTime():00.00} ms)",
$"{Device.Statistics.GetGameFrameRate():00.00} FPS ({Device.Statistics.GetGameFrameTime():00.00} ms)",
$"FIFO: {Device.Statistics.GetFifoPercent():0.00} %",
$"GPU: {_gpuDriverName}"));

View file

@ -14,6 +14,7 @@ using Ryujinx.Common.GraphicsDriver;
using Ryujinx.Common.Logging;
using Ryujinx.Common.SystemInterop;
using Ryujinx.Graphics.Vulkan.MoltenVK;
using Ryujinx.Headless;
using Ryujinx.SDL2.Common;
using Ryujinx.UI.App.Common;
using Ryujinx.UI.Common;
@ -53,6 +54,12 @@ namespace Ryujinx.Ava
PreviewerDetached = true;
if (args.Length > 0 && args[0] is "--no-gui" or "nogui")
{
HeadlessRyujinx.Entrypoint(args[1..]);
return 0;
}
Initialize(args);
LoggerAdapter.Register();
@ -222,7 +229,7 @@ namespace Ryujinx.Ava
UseHardwareAcceleration = CommandLineState.OverrideHardwareAcceleration.Value;
}
private static void PrintSystemInfo()
internal static void PrintSystemInfo()
{
Logger.Notice.Print(LogClass.Application, $"{App.FullAppName} Version: {Version}");
SystemInfo.Gather().Print();
@ -239,7 +246,7 @@ namespace Ryujinx.Ava
: $"Launch Mode: {AppDataManager.Mode}");
}
private static void ProcessUnhandledException(object sender, Exception ex, bool isTerminating)
internal static void ProcessUnhandledException(object sender, Exception ex, bool isTerminating)
{
Logger.Log log = Logger.Error ?? Logger.Notice;
string message = $"Unhandled exception caught: {ex}";
@ -254,7 +261,7 @@ namespace Ryujinx.Ava
Exit();
}
public static void Exit()
internal static void Exit()
{
DiscordIntegrationModule.Exit();

View file

@ -43,6 +43,7 @@
<PackageReference Include="Avalonia.Markup.Xaml.Loader" />
<PackageReference Include="Avalonia.Svg" />
<PackageReference Include="Avalonia.Svg.Skia" />
<PackageReference Include="CommandLineParser" />
<PackageReference Include="DynamicData" />
<PackageReference Include="FluentAvaloniaUI" />
<PackageReference Include="Projektanker.Icons.Avalonia" />
@ -162,6 +163,7 @@
<EmbeddedResource Include="Assets\Icons\Controller_JoyConPair.svg" />
<EmbeddedResource Include="Assets\Icons\Controller_JoyConRight.svg" />
<EmbeddedResource Include="Assets\Icons\Controller_ProCon.svg" />
<EmbeddedResource Include="Headless\Ryujinx.bmp" LogicalName="HeadlessLogo" />
</ItemGroup>
<ItemGroup>
<AdditionalFiles Include="Assets\Locales\en_US.json" />