Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4c646721d6 | |||
| d3bc3a1081 | |||
| c69881a0a2 | |||
| 1bc0159139 | |||
| 0733b7d0a1 | |||
| 9df1366fa1 | |||
| c73b5bdf46 | |||
| 9754d247b5 | |||
| 267e9f6350 | |||
| d7b3dd12d1 | |||
| c9b2a6b1f1 | |||
| 17233d30da | |||
| 2bf48f57d2 | |||
| 412d4065b8 | |||
| e6644626fc | |||
| 0bacdb8765 | |||
| 0ca4d6e921 | |||
| f0aa7eedf6 | |||
| 41acc4b1f3 | |||
| 7aede70ba9 | |||
| a0a4f78cff | |||
| 16a60fdf12 | |||
| 4d7350fc6e | |||
| b05eab21a2 | |||
| ff667a5c84 | |||
| 2f540dc88c | |||
| 3cb996bf5c | |||
| 852823104f |
@@ -21,7 +21,7 @@ env:
|
|||||||
DOTNET_CLI_TELEMETRY_OPTOUT: 1
|
DOTNET_CLI_TELEMETRY_OPTOUT: 1
|
||||||
RYUJINX_BASE_VERSION: "1.2"
|
RYUJINX_BASE_VERSION: "1.2"
|
||||||
RYUJINX_TARGET_RELEASE_CHANNEL_NAME: "canary"
|
RYUJINX_TARGET_RELEASE_CHANNEL_NAME: "canary"
|
||||||
RYUJINX_TARGET_RELEASE_CHANNEL_OWNER: "GreemDev"
|
RYUJINX_TARGET_RELEASE_CHANNEL_OWNER: "Hydra-NX"
|
||||||
RYUJINX_TARGET_RELEASE_CHANNEL_SOURCE_REPO: "Ryujinx"
|
RYUJINX_TARGET_RELEASE_CHANNEL_SOURCE_REPO: "Ryujinx"
|
||||||
RYUJINX_TARGET_RELEASE_CHANNEL_REPO: "Ryujinx-Canary"
|
RYUJINX_TARGET_RELEASE_CHANNEL_REPO: "Ryujinx-Canary"
|
||||||
RELEASE: 1
|
RELEASE: 1
|
||||||
|
|||||||
+12
-5
@@ -80,11 +80,16 @@ EndProject
|
|||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Horizon.Kernel.Generators", "src\Ryujinx.Horizon.Kernel.Generators\Ryujinx.Horizon.Kernel.Generators.csproj", "{7F55A45D-4E1D-4A36-ADD3-87F29A285AA2}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Horizon.Kernel.Generators", "src\Ryujinx.Horizon.Kernel.Generators\Ryujinx.Horizon.Kernel.Generators.csproj", "{7F55A45D-4E1D-4A36-ADD3-87F29A285AA2}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.HLE.Generators", "src\Ryujinx.HLE.Generators\Ryujinx.HLE.Generators.csproj", "{B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.HLE.Generators", "src\Ryujinx.HLE.Generators\Ryujinx.HLE.Generators.csproj", "{B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}"
|
||||||
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ryujinx.Graphics.Metal", "src\Ryujinx.Graphics.Metal\Ryujinx.Graphics.Metal.csproj", "{C08931FA-1191-417A-864F-3882D93E683B}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ryujinx.Graphics.Metal", "src\Ryujinx.Graphics.Metal\Ryujinx.Graphics.Metal.csproj", "{C08931FA-1191-417A-864F-3882D93E683B}"
|
||||||
ProjectSection(ProjectDependencies) = postProject
|
ProjectSection(ProjectDependencies) = postProject
|
||||||
{A602AE97-91A5-4608-8DF1-EBF4ED7A0B9E} = {A602AE97-91A5-4608-8DF1-EBF4ED7A0B9E}
|
{A602AE97-91A5-4608-8DF1-EBF4ED7A0B9E} = {A602AE97-91A5-4608-8DF1-EBF4ED7A0B9E}
|
||||||
EndProjectSection
|
EndProjectSection
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ryujinx.BuildValidationTasks", "src\Ryujinx.BuildValidationTasks\Ryujinx.BuildValidationTasks.csproj", "{4A89A234-4F19-497D-A576-DDE8CDFC5B22}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ryujinx.Graphics.Metal.SharpMetalExtensions", "src/Ryujinx.Graphics.Metal.SharpMetalExtensions\Ryujinx.Graphics.Metal.SharpMetalExtensions.csproj", "{81EA598C-DBA1-40B0-8DA4-4796B78F2037}"
|
||||||
|
EndProject
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{36F870C1-3E5F-485F-B426-F0645AF78751}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{36F870C1-3E5F-485F-B426-F0645AF78751}"
|
||||||
ProjectSection(SolutionItems) = preProject
|
ProjectSection(SolutionItems) = preProject
|
||||||
.editorconfig = .editorconfig
|
.editorconfig = .editorconfig
|
||||||
@@ -94,8 +99,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
|||||||
.github\workflows\release.yml = .github\workflows\release.yml
|
.github\workflows\release.yml = .github\workflows\release.yml
|
||||||
EndProjectSection
|
EndProjectSection
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ryujinx.BuildValidationTasks", "src\Ryujinx.BuildValidationTasks\Ryujinx.BuildValidationTasks.csproj", "{4A89A234-4F19-497D-A576-DDE8CDFC5B22}"
|
|
||||||
EndProject
|
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@@ -258,13 +261,17 @@ Global
|
|||||||
{B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}.Release|Any CPU.Build.0 = Release|Any CPU
|
{B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{4A89A234-4F19-497D-A576-DDE8CDFC5B22}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{4A89A234-4F19-497D-A576-DDE8CDFC5B22}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{4A89A234-4F19-497D-A576-DDE8CDFC5B22}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{C08931FA-1191-417A-864F-3882D93E683B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{C08931FA-1191-417A-864F-3882D93E683B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{C08931FA-1191-417A-864F-3882D93E683B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{C08931FA-1191-417A-864F-3882D93E683B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{C08931FA-1191-417A-864F-3882D93E683B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{C08931FA-1191-417A-864F-3882D93E683B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{C08931FA-1191-417A-864F-3882D93E683B}.Release|Any CPU.Build.0 = Release|Any CPU
|
{C08931FA-1191-417A-864F-3882D93E683B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{4A89A234-4F19-497D-A576-DDE8CDFC5B22}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{81EA598C-DBA1-40B0-8DA4-4796B78F2037}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{4A89A234-4F19-497D-A576-DDE8CDFC5B22}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{81EA598C-DBA1-40B0-8DA4-4796B78F2037}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{4A89A234-4F19-497D-A576-DDE8CDFC5B22}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{81EA598C-DBA1-40B0-8DA4-4796B78F2037}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{81EA598C-DBA1-40B0-8DA4-4796B78F2037}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using ARMeilleure.State;
|
using ARMeilleure.State;
|
||||||
|
using Humanizer;
|
||||||
using Ryujinx.Common;
|
using Ryujinx.Common;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.Common.Memory;
|
using Ryujinx.Common.Memory;
|
||||||
@@ -58,8 +59,8 @@ namespace ARMeilleure.Translation.PTC
|
|||||||
{
|
{
|
||||||
_ptc = ptc;
|
_ptc = ptc;
|
||||||
|
|
||||||
_timer = new Timer(SaveInterval * 1000d);
|
_timer = new Timer(SaveInterval.Seconds());
|
||||||
_timer.Elapsed += PreSave;
|
_timer.Elapsed += TimerElapsed;
|
||||||
|
|
||||||
_outerHeaderMagic = BinaryPrimitives.ReadUInt64LittleEndian(EncodingCache.UTF8NoBOM.GetBytes(OuterHeaderMagicString).AsSpan());
|
_outerHeaderMagic = BinaryPrimitives.ReadUInt64LittleEndian(EncodingCache.UTF8NoBOM.GetBytes(OuterHeaderMagicString).AsSpan());
|
||||||
|
|
||||||
@@ -72,6 +73,9 @@ namespace ARMeilleure.Translation.PTC
|
|||||||
Enabled = false;
|
Enabled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void TimerElapsed(object _, ElapsedEventArgs __)
|
||||||
|
=> new Thread(PreSave) { Name = "Ptc.DiskWriter" }.Start();
|
||||||
|
|
||||||
public void AddEntry(ulong address, ExecutionMode mode, bool highCq)
|
public void AddEntry(ulong address, ExecutionMode mode, bool highCq)
|
||||||
{
|
{
|
||||||
if (IsAddressInStaticCodeRange(address))
|
if (IsAddressInStaticCodeRange(address))
|
||||||
@@ -262,7 +266,7 @@ namespace ARMeilleure.Translation.PTC
|
|||||||
compressedStream.SetLength(0L);
|
compressedStream.SetLength(0L);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PreSave(object source, ElapsedEventArgs e)
|
private void PreSave()
|
||||||
{
|
{
|
||||||
_waitEvent.Reset();
|
_waitEvent.Reset();
|
||||||
|
|
||||||
@@ -428,7 +432,7 @@ namespace ARMeilleure.Translation.PTC
|
|||||||
{
|
{
|
||||||
_disposed = true;
|
_disposed = true;
|
||||||
|
|
||||||
_timer.Elapsed -= PreSave;
|
_timer.Elapsed -= TimerElapsed;
|
||||||
_timer.Dispose();
|
_timer.Dispose();
|
||||||
|
|
||||||
Wait();
|
Wait();
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ namespace Ryujinx.Common.Configuration
|
|||||||
[JsonConverter(typeof(TypedStringEnumConverter<GraphicsBackend>))]
|
[JsonConverter(typeof(TypedStringEnumConverter<GraphicsBackend>))]
|
||||||
public enum GraphicsBackend
|
public enum GraphicsBackend
|
||||||
{
|
{
|
||||||
|
Auto,
|
||||||
Vulkan,
|
Vulkan,
|
||||||
OpenGl,
|
OpenGl,
|
||||||
Metal
|
Metal
|
||||||
|
|||||||
@@ -0,0 +1,190 @@
|
|||||||
|
using Gommon;
|
||||||
|
using Ryujinx.Common.Configuration;
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Ryujinx.Common
|
||||||
|
{
|
||||||
|
public static class TitleIDs
|
||||||
|
{
|
||||||
|
public static GraphicsBackend SelectGraphicsBackend(string titleId, GraphicsBackend currentBackend)
|
||||||
|
{
|
||||||
|
switch (currentBackend)
|
||||||
|
{
|
||||||
|
case GraphicsBackend.OpenGl when OperatingSystem.IsMacOS():
|
||||||
|
return GraphicsBackend.Vulkan;
|
||||||
|
case GraphicsBackend.Vulkan or GraphicsBackend.OpenGl or GraphicsBackend.Metal:
|
||||||
|
return currentBackend;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(OperatingSystem.IsMacOS() && RuntimeInformation.ProcessArchitecture is Architecture.Arm64))
|
||||||
|
return GraphicsBackend.Vulkan;
|
||||||
|
|
||||||
|
return GreatMetalTitles.ContainsIgnoreCase(titleId) ? GraphicsBackend.Metal : GraphicsBackend.Vulkan;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly string[] GreatMetalTitles =
|
||||||
|
[
|
||||||
|
"01006f8002326000", // Animal Crossings: New Horizons
|
||||||
|
"01009bf0072d4000", // Captain Toad: Treasure Tracker
|
||||||
|
"0100a5c00d162000", // Cuphead
|
||||||
|
"010023800d64a000", // Deltarune
|
||||||
|
"010028600EBDA000", // Mario 3D World
|
||||||
|
"0100152000022000", // Mario Kart 8 Deluxe
|
||||||
|
"01005CA01580E000", // Persona 5
|
||||||
|
"01008C0016544000", // Sea of Stars
|
||||||
|
"01006A800016E000", // Smash Ultimate
|
||||||
|
"0100000000010000", // Super Mario Odyessy
|
||||||
|
];
|
||||||
|
|
||||||
|
public static string GetDiscordGameAsset(string titleId)
|
||||||
|
=> DiscordGameAssetKeys.Contains(titleId) ? titleId : "game";
|
||||||
|
|
||||||
|
public static readonly string[] DiscordGameAssetKeys =
|
||||||
|
[
|
||||||
|
"010055d009f78000", // Fire Emblem: Three Houses
|
||||||
|
"0100a12011cc8000", // Fire Emblem: Shadow Dragon
|
||||||
|
"0100a6301214e000", // Fire Emblem Engage
|
||||||
|
"0100f15003e64000", // Fire Emblem Warriors
|
||||||
|
"010071f0143ea000", // Fire Emblem Warriors: Three Hopes
|
||||||
|
|
||||||
|
"01007e3006dda000", // Kirby Star Allies
|
||||||
|
"01004d300c5ae000", // Kirby and the Forgotten Land
|
||||||
|
"01006b601380e000", // Kirby's Return to Dream Land Deluxe
|
||||||
|
"01003fb00c5a8000", // Super Kirby Clash
|
||||||
|
"0100227010460000", // Kirby Fighters 2
|
||||||
|
"0100a8e016236000", // Kirby's Dream Buffet
|
||||||
|
|
||||||
|
"01007ef00011e000", // The Legend of Zelda: Breath of the Wild
|
||||||
|
"01006bb00c6f0000", // The Legend of Zelda: Link's Awakening
|
||||||
|
"01002da013484000", // The Legend of Zelda: Skyward Sword HD
|
||||||
|
"0100f2c0115b6000", // The Legend of Zelda: Tears of the Kingdom
|
||||||
|
"01008cf01baac000", // The Legend of Zelda: Echoes of Wisdom
|
||||||
|
"01000b900d8b0000", // Cadence of Hyrule
|
||||||
|
"0100ae00096ea000", // Hyrule Warriors: Definitive Edition
|
||||||
|
"01002b00111a2000", // Hyrule Warriors: Age of Calamity
|
||||||
|
|
||||||
|
"010048701995e000", // Luigi's Mansion 2 HD
|
||||||
|
"0100dca0064a6000", // Luigi's Mansion 3
|
||||||
|
|
||||||
|
"010093801237c000", // Metroid Dread
|
||||||
|
"010012101468c000", // Metroid Prime Remastered
|
||||||
|
|
||||||
|
"0100000000010000", // SUPER MARIO ODYSSEY
|
||||||
|
"0100ea80032ea000", // Super Mario Bros. U Deluxe
|
||||||
|
"01009b90006dc000", // Super Mario Maker 2
|
||||||
|
"010049900f546000", // Super Mario 3D All-Stars
|
||||||
|
"010049900F546001", // ^ 64
|
||||||
|
"010049900F546002", // ^ Sunshine
|
||||||
|
"010049900F546003", // ^ Galaxy
|
||||||
|
"010028600ebda000", // Super Mario 3D World + Bowser's Fury
|
||||||
|
"010015100b514000", // Super Mario Bros. Wonder
|
||||||
|
"0100152000022000", // Mario Kart 8 Deluxe
|
||||||
|
"010036b0034e4000", // Super Mario Party
|
||||||
|
"01006fe013472000", // Mario Party Superstars
|
||||||
|
"0100965017338000", // Super Mario Party Jamboree
|
||||||
|
"01006d0017f7a000", // Mario & Luigi: Brothership
|
||||||
|
"010067300059a000", // Mario + Rabbids: Kingdom Battle
|
||||||
|
"0100317013770000", // Mario + Rabbids: Sparks of Hope
|
||||||
|
"0100a3900c3e2000", // Paper Mario: The Origami King
|
||||||
|
"0100ecd018ebe000", // Paper Mario: The Thousand-Year Door
|
||||||
|
"0100bc0018138000", // Super Mario RPG
|
||||||
|
"0100bde00862a000", // Mario Tennis Aces
|
||||||
|
"0100c9c00e25c000", // Mario Golf: Super Rush
|
||||||
|
"010019401051c000", // Mario Strikers: Battle League
|
||||||
|
"010003000e146000", // Mario & Sonic at the Olympic Games Tokyo 2020
|
||||||
|
"0100b99019412000", // Mario vs. Donkey Kong
|
||||||
|
|
||||||
|
"0100aa80194b0000", // Pikmin 1
|
||||||
|
"0100d680194b2000", // Pikmin 2
|
||||||
|
"0100f4c009322000", // Pikmin 3 Deluxe
|
||||||
|
"0100b7c00933a000", // Pikmin 4
|
||||||
|
|
||||||
|
"010003f003a34000", // Pokémon: Let's Go Pikachu!
|
||||||
|
"0100187003a36000", // Pokémon: Let's Go Eevee!
|
||||||
|
"0100abf008968000", // Pokémon Sword
|
||||||
|
"01008db008c2c000", // Pokémon Shield
|
||||||
|
"0100000011d90000", // Pokémon Brilliant Diamond
|
||||||
|
"010018e011d92000", // Pokémon Shining Pearl
|
||||||
|
"01001f5010dfa000", // Pokémon Legends: Arceus
|
||||||
|
"0100a3d008c5c000", // Pokémon Scarlet
|
||||||
|
"01008f6008c5e000", // Pokémon Violet
|
||||||
|
"0100b3f000be2000", // Pokkén Tournament DX
|
||||||
|
"0100f4300bf2c000", // New Pokémon Snap
|
||||||
|
|
||||||
|
"01003bc0000a0000", // Splatoon 2 (US)
|
||||||
|
"0100f8f0000a2000", // Splatoon 2 (EU)
|
||||||
|
"01003c700009c000", // Splatoon 2 (JP)
|
||||||
|
"0100c2500fc20000", // Splatoon 3
|
||||||
|
"0100ba0018500000", // Splatoon 3: Splatfest World Premiere
|
||||||
|
|
||||||
|
"010040600c5ce000", // Tetris 99
|
||||||
|
"0100277011f1a000", // Super Mario Bros. 35
|
||||||
|
"0100ad9012510000", // PAC-MAN 99
|
||||||
|
"0100ccf019c8c000", // F-ZERO 99
|
||||||
|
"0100d870045b6000", // NES - Nintendo Switch Online
|
||||||
|
"01008d300c50c000", // SNES - Nintendo Switch Online
|
||||||
|
"0100c9a00ece6000", // N64 - Nintendo Switch Online
|
||||||
|
"0100e0601c632000", // N64 - Nintendo Switch Online 18+
|
||||||
|
"0100c62011050000", // GB - Nintendo Switch Online
|
||||||
|
"010012f017576000", // GBA - Nintendo Switch Online
|
||||||
|
|
||||||
|
"01000320000cc000", // 1-2 Switch
|
||||||
|
"0100300012f2a000", // Advance Wars 1+2: Re-Boot Camp
|
||||||
|
"01006f8002326000", // Animal Crossing: New Horizons
|
||||||
|
"0100620012d6e000", // Big Brain Academy: Brain vs. Brain
|
||||||
|
"010018300d006000", // BOXBOY! + BOXGIRL!
|
||||||
|
"0100c1f0051b6000", // Donkey Kong Country: Tropical Freeze
|
||||||
|
"0100ed000d390000", // Dr. Kawashima's Brain Training
|
||||||
|
"010067b017588000", // Endless Ocean Luminous
|
||||||
|
"0100d2f00d5c0000", // Nintendo Switch Sports
|
||||||
|
"01006b5012b32000", // Part Time UFO
|
||||||
|
"0100704000B3A000", // Snipperclips
|
||||||
|
"01006a800016e000", // Super Smash Bros. Ultimate
|
||||||
|
"0100a9400c9c2000", // Tokyo Mirage Sessions #FE Encore
|
||||||
|
|
||||||
|
"010076f0049a2000", // Bayonetta
|
||||||
|
"01007960049a0000", // Bayonetta 2
|
||||||
|
"01004a4010fea000", // Bayonetta 3
|
||||||
|
"0100cf5010fec000", // Bayonetta Origins: Cereza and the Lost Demon
|
||||||
|
|
||||||
|
"0100dcd01525a000", // Persona 3 Portable
|
||||||
|
"010062b01525c000", // Persona 4 Golden
|
||||||
|
"010075a016a3a000", // Persona 4 Arena Ultimax
|
||||||
|
"01005ca01580e000", // Persona 5 Royal
|
||||||
|
"0100801011c3e000", // Persona 5 Strikers
|
||||||
|
"010087701b092000", // Persona 5 Tactica
|
||||||
|
|
||||||
|
"01009aa000faa000", // Sonic Mania
|
||||||
|
"01004ad014bf0000", // Sonic Frontiers
|
||||||
|
"01005ea01c0fc000", // SONIC X SHADOW GENERATIONS
|
||||||
|
"01005ea01c0fc001", // ^
|
||||||
|
|
||||||
|
"010056e00853a000", // A Hat in Time
|
||||||
|
"0100dbf01000a000", // Burnout Paradise Remastered
|
||||||
|
"0100744001588000", // Cars 3: Driven to Win
|
||||||
|
"0100b41013c82000", // Cruis'n Blast
|
||||||
|
"01001b300b9be000", // Diablo III: Eternal Collection
|
||||||
|
"01008c8012920000", // Dying Light Platinum Edition
|
||||||
|
"010073c01af34000", // LEGO Horizon Adventures
|
||||||
|
"0100770008dd8000", // Monster Hunter Generations Ultimate
|
||||||
|
"0100b04011742000", // Monster Hunter Rise
|
||||||
|
"0100853015e86000", // No Man's Sky
|
||||||
|
"01007bb017812000", // Portal
|
||||||
|
"0100abd01785c000", // Portal 2
|
||||||
|
"01008e200c5c2000", // Muse Dash
|
||||||
|
"01007820196a6000", // Red Dead Redemption
|
||||||
|
"01002f7013224000", // Rune Factory 5
|
||||||
|
"01008d100d43e000", // Saints Row IV
|
||||||
|
"0100de600beee000", // Saints Row: The Third - The Full Package
|
||||||
|
"01001180021fa000", // Shovel Knight: Specter of Torment
|
||||||
|
"0100d7a01b7a2000", // Star Wars: Bounty Hunter
|
||||||
|
"0100800015926000", // Suika Game
|
||||||
|
"0100e46006708000", // Terraria
|
||||||
|
"01000a10041ea000", // The Elder Scrolls V: Skyrim
|
||||||
|
"010057a01e4d4000", // TSUKIHIME -A piece of blue glass moon-
|
||||||
|
"010080b00ad66000", // Undertale
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using Ryujinx.Common.Configuration;
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.GAL
|
namespace Ryujinx.Graphics.GAL
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using Ryujinx.Common.Configuration;
|
||||||
using Ryujinx.Graphics.GAL.Multithreading.Commands.Window;
|
using Ryujinx.Graphics.GAL.Multithreading.Commands.Window;
|
||||||
using Ryujinx.Graphics.GAL.Multithreading.Model;
|
using Ryujinx.Graphics.GAL.Multithreading.Model;
|
||||||
using Ryujinx.Graphics.GAL.Multithreading.Resources;
|
using Ryujinx.Graphics.GAL.Multithreading.Resources;
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
namespace Ryujinx.Graphics.GAL
|
|
||||||
{
|
|
||||||
public enum VSyncMode
|
|
||||||
{
|
|
||||||
Switch,
|
|
||||||
Unbounded,
|
|
||||||
Custom
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -838,6 +838,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||||||
TargetApi.OpenGL => TargetLanguage.Glsl,
|
TargetApi.OpenGL => TargetLanguage.Glsl,
|
||||||
TargetApi.Vulkan => GraphicsConfig.EnableSpirvCompilationOnVulkan ? TargetLanguage.Spirv : TargetLanguage.Glsl,
|
TargetApi.Vulkan => GraphicsConfig.EnableSpirvCompilationOnVulkan ? TargetLanguage.Spirv : TargetLanguage.Glsl,
|
||||||
TargetApi.Metal => TargetLanguage.Msl,
|
TargetApi.Metal => TargetLanguage.Msl,
|
||||||
|
_ => throw new NotImplementedException()
|
||||||
};
|
};
|
||||||
|
|
||||||
return new TranslationOptions(lang, api, flags);
|
return new TranslationOptions(lang, api, flags);
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
using SharpMetal;
|
||||||
|
using SharpMetal.ObjectiveCCore;
|
||||||
|
using SharpMetal.QuartzCore;
|
||||||
|
using System.Runtime.Versioning;
|
||||||
|
// ReSharper disable InconsistentNaming
|
||||||
|
|
||||||
|
namespace Ryujinx.Graphics.Metal.SharpMetalExtensions
|
||||||
|
{
|
||||||
|
[SupportedOSPlatform("macOS")]
|
||||||
|
public static class CAMetalLayerExtensions
|
||||||
|
{
|
||||||
|
private static readonly Selector sel_displaySyncEnabled = "displaySyncEnabled";
|
||||||
|
private static readonly Selector sel_setDisplaySyncEnabled = "setDisplaySyncEnabled:";
|
||||||
|
|
||||||
|
private static readonly Selector sel_developerHUDProperties = "developerHUDProperties";
|
||||||
|
private static readonly Selector sel_setDeveloperHUDProperties = "setDeveloperHUDProperties:";
|
||||||
|
|
||||||
|
public static bool IsDisplaySyncEnabled(this CAMetalLayer metalLayer)
|
||||||
|
=> ObjectiveCRuntime.bool_objc_msgSend(metalLayer.NativePtr, sel_displaySyncEnabled);
|
||||||
|
|
||||||
|
public static void SetDisplaySyncEnabled(this CAMetalLayer metalLayer, bool enabled)
|
||||||
|
=> ObjectiveCRuntime.objc_msgSend(metalLayer.NativePtr, sel_setDisplaySyncEnabled, enabled);
|
||||||
|
|
||||||
|
public static nint GetDeveloperHudProperties(this CAMetalLayer metalLayer)
|
||||||
|
=> ObjectiveCRuntime.IntPtr_objc_msgSend(metalLayer.NativePtr, sel_developerHUDProperties);
|
||||||
|
|
||||||
|
public static void SetDeveloperHudProperties(this CAMetalLayer metalLayer, nint dictionaryPointer)
|
||||||
|
=> ObjectiveCRuntime.objc_msgSend(metalLayer.NativePtr, sel_setDeveloperHUDProperties, dictionaryPointer);
|
||||||
|
}
|
||||||
|
}
|
||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="SharpMetal" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
@@ -1767,6 +1767,7 @@ namespace Ryujinx.Graphics.Metal
|
|||||||
Constants.StorageBuffersSetIndex => Constants.StorageBuffersIndex,
|
Constants.StorageBuffersSetIndex => Constants.StorageBuffersIndex,
|
||||||
Constants.TexturesSetIndex => Constants.TexturesIndex,
|
Constants.TexturesSetIndex => Constants.TexturesIndex,
|
||||||
Constants.ImagesSetIndex => Constants.ImagesIndex,
|
Constants.ImagesSetIndex => Constants.ImagesIndex,
|
||||||
|
_ => throw new NotImplementedException()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,8 +20,13 @@ namespace Ryujinx.Graphics.Metal
|
|||||||
|
|
||||||
private Pipeline _pipeline;
|
private Pipeline _pipeline;
|
||||||
private Window _window;
|
private Window _window;
|
||||||
|
|
||||||
|
public uint ProgramCount { get; set; }
|
||||||
|
|
||||||
|
#pragma warning disable CS0067 // The event is never used
|
||||||
public event EventHandler<ScreenCaptureImageInfo> ScreenCaptured;
|
public event EventHandler<ScreenCaptureImageInfo> ScreenCaptured;
|
||||||
|
#pragma warning restore CS0067
|
||||||
|
|
||||||
public bool PreferThreading => true;
|
public bool PreferThreading => true;
|
||||||
public IPipeline Pipeline => _pipeline;
|
public IPipeline Pipeline => _pipeline;
|
||||||
public IWindow Window => _window;
|
public IWindow Window => _window;
|
||||||
@@ -102,6 +107,7 @@ namespace Ryujinx.Graphics.Metal
|
|||||||
|
|
||||||
public IProgram CreateProgram(ShaderSource[] shaders, ShaderInfo info)
|
public IProgram CreateProgram(ShaderSource[] shaders, ShaderInfo info)
|
||||||
{
|
{
|
||||||
|
ProgramCount++;
|
||||||
return new Program(this, _device, shaders, info.ResourceLayout, info.ComputeLocalSize);
|
return new Program(this, _device, shaders, info.ResourceLayout, info.ComputeLocalSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,12 +5,9 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
|
<ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
|
||||||
<ProjectReference Include="..\Ryujinx.Graphics.GAL\Ryujinx.Graphics.GAL.csproj" />
|
<ProjectReference Include="..\Ryujinx.Graphics.GAL\Ryujinx.Graphics.GAL.csproj" />
|
||||||
</ItemGroup>
|
<ProjectReference Include="..\Ryujinx.Graphics.Metal.SharpMetalExtensions\Ryujinx.Graphics.Metal.SharpMetalExtensions.csproj" />
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="SharpMetal" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
|
using Ryujinx.Common.Configuration;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.Graphics.GAL;
|
using Ryujinx.Graphics.GAL;
|
||||||
using Ryujinx.Graphics.Metal.Effects;
|
using Ryujinx.Graphics.Metal.Effects;
|
||||||
|
using Ryujinx.Graphics.Metal.SharpMetalExtensions;
|
||||||
using SharpMetal.ObjectiveCCore;
|
using SharpMetal.ObjectiveCCore;
|
||||||
using SharpMetal.QuartzCore;
|
using SharpMetal.QuartzCore;
|
||||||
using System;
|
using System;
|
||||||
using System.Runtime.Versioning;
|
using System.Runtime.Versioning;
|
||||||
|
using AntiAliasing = Ryujinx.Graphics.GAL.AntiAliasing;
|
||||||
|
using ScalingFilter = Ryujinx.Graphics.GAL.ScalingFilter;
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Metal
|
namespace Ryujinx.Graphics.Metal
|
||||||
{
|
{
|
||||||
@@ -137,10 +141,18 @@ namespace Ryujinx.Graphics.Metal
|
|||||||
_requestedWidth = width;
|
_requestedWidth = width;
|
||||||
_requestedHeight = height;
|
_requestedHeight = height;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ChangeVSyncMode(bool vsyncEnabled)
|
public void ChangeVSyncMode(VSyncMode vSyncMode)
|
||||||
{
|
{
|
||||||
// _vsyncEnabled = vsyncEnabled;
|
switch (vSyncMode)
|
||||||
|
{
|
||||||
|
case VSyncMode.Unbounded:
|
||||||
|
_metalLayer.SetDisplaySyncEnabled(false);
|
||||||
|
break;
|
||||||
|
case VSyncMode.Switch:
|
||||||
|
_metalLayer.SetDisplaySyncEnabled(true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetAntiAliasing(AntiAliasing effect)
|
public void SetAntiAliasing(AntiAliasing effect)
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ namespace Ryujinx.Graphics.OpenGL.Queries
|
|||||||
|
|
||||||
_current = new CounterQueueEvent(this, glType, 0);
|
_current = new CounterQueueEvent(this, glType, 0);
|
||||||
|
|
||||||
_consumerThread = new Thread(EventConsumer);
|
_consumerThread = new Thread(EventConsumer) { Name = "CPU.CounterQueue." + (int)type };
|
||||||
_consumerThread.Start();
|
_consumerThread.Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
using OpenTK.Graphics.OpenGL;
|
using OpenTK.Graphics.OpenGL;
|
||||||
|
using Ryujinx.Common.Configuration;
|
||||||
using Ryujinx.Graphics.GAL;
|
using Ryujinx.Graphics.GAL;
|
||||||
using Ryujinx.Graphics.OpenGL.Effects;
|
using Ryujinx.Graphics.OpenGL.Effects;
|
||||||
using Ryujinx.Graphics.OpenGL.Effects.Smaa;
|
using Ryujinx.Graphics.OpenGL.Effects.Smaa;
|
||||||
using Ryujinx.Graphics.OpenGL.Image;
|
using Ryujinx.Graphics.OpenGL.Image;
|
||||||
using System;
|
using System;
|
||||||
|
using AntiAliasing = Ryujinx.Graphics.GAL.AntiAliasing;
|
||||||
|
using ScalingFilter = Ryujinx.Graphics.GAL.ScalingFilter;
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.OpenGL
|
namespace Ryujinx.Graphics.OpenGL
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -125,6 +125,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Msl.Instructions
|
|||||||
Instruction.Add => "PreciseFAdd",
|
Instruction.Add => "PreciseFAdd",
|
||||||
Instruction.Subtract => "PreciseFSub",
|
Instruction.Subtract => "PreciseFSub",
|
||||||
Instruction.Multiply => "PreciseFMul",
|
Instruction.Multiply => "PreciseFMul",
|
||||||
|
_ => throw new NotImplementedException()
|
||||||
};
|
};
|
||||||
|
|
||||||
return $"{func}({expr[0]}, {expr[1]})";
|
return $"{func}({expr[0]}, {expr[1]})";
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ namespace Ryujinx.Graphics.Vulkan.Queries
|
|||||||
|
|
||||||
_current = new CounterQueueEvent(this, type, 0);
|
_current = new CounterQueueEvent(this, type, 0);
|
||||||
|
|
||||||
_consumerThread = new Thread(EventConsumer);
|
_consumerThread = new Thread(EventConsumer) { Name = "CPU.CounterQueue." + (int)type };
|
||||||
_consumerThread.Start();
|
_consumerThread.Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
|
using Ryujinx.Common.Configuration;
|
||||||
using Ryujinx.Graphics.GAL;
|
using Ryujinx.Graphics.GAL;
|
||||||
using Ryujinx.Graphics.Vulkan.Effects;
|
using Ryujinx.Graphics.Vulkan.Effects;
|
||||||
using Silk.NET.Vulkan;
|
using Silk.NET.Vulkan;
|
||||||
using Silk.NET.Vulkan.Extensions.KHR;
|
using Silk.NET.Vulkan.Extensions.KHR;
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using AntiAliasing = Ryujinx.Graphics.GAL.AntiAliasing;
|
||||||
|
using ScalingFilter = Ryujinx.Graphics.GAL.ScalingFilter;
|
||||||
using VkFormat = Silk.NET.Vulkan.Format;
|
using VkFormat = Silk.NET.Vulkan.Format;
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Vulkan
|
namespace Ryujinx.Graphics.Vulkan
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
|
using Ryujinx.Common.Configuration;
|
||||||
using Ryujinx.Graphics.GAL;
|
using Ryujinx.Graphics.GAL;
|
||||||
using System;
|
using System;
|
||||||
|
using AntiAliasing = Ryujinx.Graphics.GAL.AntiAliasing;
|
||||||
|
using ScalingFilter = Ryujinx.Graphics.GAL.ScalingFilter;
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Vulkan
|
namespace Ryujinx.Graphics.Vulkan
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1034,16 +1034,16 @@ namespace Ryujinx.HLE.FileSystem
|
|||||||
switch (fileName)
|
switch (fileName)
|
||||||
{
|
{
|
||||||
case "prod.keys":
|
case "prod.keys":
|
||||||
verified = verifyKeys(lines, genericPattern);
|
verified = VerifyKeys(lines, genericPattern);
|
||||||
break;
|
break;
|
||||||
case "title.keys":
|
case "title.keys":
|
||||||
verified = verifyKeys(lines, titlePattern);
|
verified = VerifyKeys(lines, titlePattern);
|
||||||
break;
|
break;
|
||||||
case "console.keys":
|
case "console.keys":
|
||||||
verified = verifyKeys(lines, genericPattern);
|
verified = VerifyKeys(lines, genericPattern);
|
||||||
break;
|
break;
|
||||||
case "dev.keys":
|
case "dev.keys":
|
||||||
verified = verifyKeys(lines, genericPattern);
|
verified = VerifyKeys(lines, genericPattern);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new FormatException($"Keys file name \"{fileName}\" not supported. Only \"prod.keys\", \"title.keys\", \"console.keys\", \"dev.keys\" are supported.");
|
throw new FormatException($"Keys file name \"{fileName}\" not supported. Only \"prod.keys\", \"title.keys\", \"console.keys\", \"dev.keys\" are supported.");
|
||||||
@@ -1056,20 +1056,22 @@ namespace Ryujinx.HLE.FileSystem
|
|||||||
{
|
{
|
||||||
throw new FileNotFoundException($"Keys file not found at \"{filePath}\".");
|
throw new FileNotFoundException($"Keys file not found at \"{filePath}\".");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private bool verifyKeys(string[] lines, string regex)
|
return;
|
||||||
{
|
|
||||||
foreach (string line in lines)
|
bool VerifyKeys(string[] lines, string regex)
|
||||||
{
|
{
|
||||||
if (!Regex.IsMatch(line, regex))
|
foreach (string line in lines)
|
||||||
{
|
{
|
||||||
return false;
|
if (!Regex.IsMatch(line, regex))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool AreKeysAlredyPresent(string pathToCheck)
|
public bool AreKeysAlredyPresent(string pathToCheck)
|
||||||
{
|
{
|
||||||
string[] fileNames = { "prod.keys", "title.keys", "console.keys", "dev.keys" };
|
string[] fileNames = { "prod.keys", "title.keys", "console.keys", "dev.keys" };
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ using Ryujinx.HLE.HOS.Services.Account.Acc;
|
|||||||
using Ryujinx.HLE.HOS.SystemState;
|
using Ryujinx.HLE.HOS.SystemState;
|
||||||
using Ryujinx.HLE.UI;
|
using Ryujinx.HLE.UI;
|
||||||
using System;
|
using System;
|
||||||
using VSyncMode = Ryujinx.Common.Configuration.VSyncMode;
|
|
||||||
|
|
||||||
namespace Ryujinx.HLE
|
namespace Ryujinx.HLE
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -181,7 +181,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
|||||||
is64Bits = true;
|
is64Bits = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
HostThread = new Thread(ThreadStart);
|
HostThread = new Thread(ThreadStart) { Name = "HLE.KThread" };
|
||||||
|
|
||||||
Context = owner?.CreateExecutionContext() ?? new ProcessExecutionContext();
|
Context = owner?.CreateExecutionContext() ?? new ProcessExecutionContext();
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ using System.Collections.Generic;
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using VSyncMode = Ryujinx.Common.Configuration.VSyncMode;
|
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -22,8 +22,9 @@ namespace Ryujinx.Headless.SDL2.Metal
|
|||||||
GraphicsDebugLevel glLogLevel,
|
GraphicsDebugLevel glLogLevel,
|
||||||
AspectRatio aspectRatio,
|
AspectRatio aspectRatio,
|
||||||
bool enableMouse,
|
bool enableMouse,
|
||||||
HideCursorMode hideCursorMode)
|
HideCursorMode hideCursorMode,
|
||||||
: base(inputManager, glLogLevel, aspectRatio, enableMouse, hideCursorMode) { }
|
bool ignoreControllerApplet)
|
||||||
|
: base(inputManager, glLogLevel, aspectRatio, enableMouse, hideCursorMode, ignoreControllerApplet) { }
|
||||||
|
|
||||||
public override SDL_WindowFlags GetWindowFlags() => SDL_WindowFlags.SDL_WINDOW_METAL;
|
public override SDL_WindowFlags GetWindowFlags() => SDL_WindowFlags.SDL_WINDOW_METAL;
|
||||||
|
|
||||||
|
|||||||
@@ -1,755 +0,0 @@
|
|||||||
using CommandLine;
|
|
||||||
using LibHac.Tools.FsSystem;
|
|
||||||
using Ryujinx.Audio.Backends.SDL2;
|
|
||||||
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;
|
|
||||||
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.Metal;
|
|
||||||
using Ryujinx.Graphics.OpenGL;
|
|
||||||
using Ryujinx.Graphics.Vulkan;
|
|
||||||
<<<<<<< HEAD
|
|
||||||
using Ryujinx.Graphics.Vulkan.MoltenVK;
|
|
||||||
using Ryujinx.Graphics.Metal;
|
|
||||||
=======
|
|
||||||
>>>>>>> 137f5970f (Vertex Input Attributes)
|
|
||||||
using Ryujinx.Headless.SDL2.Metal;
|
|
||||||
using Ryujinx.Headless.SDL2.OpenGL;
|
|
||||||
using Ryujinx.Headless.SDL2.Vulkan;
|
|
||||||
using Ryujinx.HLE;
|
|
||||||
using Ryujinx.HLE.FileSystem;
|
|
||||||
using Ryujinx.HLE.HOS;
|
|
||||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
|
||||||
using Ryujinx.Input;
|
|
||||||
using Ryujinx.Input.HLE;
|
|
||||||
using Ryujinx.Input.SDL2;
|
|
||||||
using Ryujinx.SDL2.Common;
|
|
||||||
using Silk.NET.Vulkan;
|
|
||||||
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
|
|
||||||
{
|
|
||||||
class Program
|
|
||||||
{
|
|
||||||
public static string Version { get; private set; }
|
|
||||||
|
|
||||||
private static VirtualFileSystem _virtualFileSystem;
|
|
||||||
private static ContentManager _contentManager;
|
|
||||||
private static AccountManager _accountManager;
|
|
||||||
private static LibHacHorizonManager _libHacHorizonManager;
|
|
||||||
private static UserChannelPersistence _userChannelPersistence;
|
|
||||||
private static InputManager _inputManager;
|
|
||||||
private static Switch _emulationContext;
|
|
||||||
private static WindowBase _window;
|
|
||||||
private static WindowsMultimediaTimerResolution _windowsMultimediaTimerResolution;
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
Version = ReleaseInformation.Version;
|
|
||||||
|
|
||||||
// Make process DPI aware for proper window sizing on high-res screens.
|
|
||||||
ForceDpiAware.Windows();
|
|
||||||
|
|
||||||
Console.Title = $"Ryujinx Console {Version} (Headless SDL2)";
|
|
||||||
|
|
||||||
if (OperatingSystem.IsMacOS() || OperatingSystem.IsLinux())
|
|
||||||
{
|
|
||||||
AutoResetEvent invoked = new(false);
|
|
||||||
|
|
||||||
// MacOS must perform SDL polls from the main thread.
|
|
||||||
SDL2Driver.MainThreadDispatcher = action =>
|
|
||||||
{
|
|
||||||
invoked.Reset();
|
|
||||||
|
|
||||||
WindowBase.QueueMainThreadAction(() =>
|
|
||||||
{
|
|
||||||
action();
|
|
||||||
|
|
||||||
invoked.Set();
|
|
||||||
});
|
|
||||||
|
|
||||||
invoked.WaitOne();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (OperatingSystem.IsMacOS())
|
|
||||||
{
|
|
||||||
MVKInitialization.InitializeResolver();
|
|
||||||
}
|
|
||||||
|
|
||||||
Parser.Default.ParseArguments<Options>(args)
|
|
||||||
.WithParsed(Load)
|
|
||||||
.WithNotParsed(errors => errors.Output());
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
bool isKeyboard = true;
|
|
||||||
|
|
||||||
gamepad = _inputManager.KeyboardDriver.GetGamepad(inputId);
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void Load(Options option)
|
|
||||||
{
|
|
||||||
AppDataManager.Initialize(option.BaseDataDir);
|
|
||||||
|
|
||||||
_virtualFileSystem = VirtualFileSystem.CreateInstance();
|
|
||||||
_libHacHorizonManager = new LibHacHorizonManager();
|
|
||||||
|
|
||||||
_libHacHorizonManager.InitializeFsServer(_virtualFileSystem);
|
|
||||||
_libHacHorizonManager.InitializeArpServer();
|
|
||||||
_libHacHorizonManager.InitializeBcatServer();
|
|
||||||
_libHacHorizonManager.InitializeSystemClients();
|
|
||||||
|
|
||||||
_contentManager = new ContentManager(_virtualFileSystem);
|
|
||||||
_accountManager = new AccountManager(_libHacHorizonManager.RyujinxClient, option.UserProfile);
|
|
||||||
_userChannelPersistence = new UserChannelPersistence();
|
|
||||||
|
|
||||||
_inputManager = new InputManager(new SDL2KeyboardDriver(), new SDL2GamepadDriver());
|
|
||||||
|
|
||||||
GraphicsConfig.EnableShaderCache = true;
|
|
||||||
|
|
||||||
if (OperatingSystem.IsMacOS())
|
|
||||||
{
|
|
||||||
if (option.GraphicsBackend == GraphicsBackend.OpenGl)
|
|
||||||
{
|
|
||||||
option.GraphicsBackend = GraphicsBackend.Vulkan;
|
|
||||||
Logger.Warning?.Print(LogClass.Application, "OpenGL is not supported on macOS, switching to Vulkan!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
IGamepad gamepad;
|
|
||||||
|
|
||||||
if (option.ListInputIds)
|
|
||||||
{
|
|
||||||
Logger.Info?.Print(LogClass.Application, "Input Ids:");
|
|
||||||
|
|
||||||
foreach (string id in _inputManager.KeyboardDriver.GamepadsIds)
|
|
||||||
{
|
|
||||||
gamepad = _inputManager.KeyboardDriver.GetGamepad(id);
|
|
||||||
|
|
||||||
Logger.Info?.Print(LogClass.Application, $"- {id} (\"{gamepad.Name}\")");
|
|
||||||
|
|
||||||
gamepad.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (string id in _inputManager.GamepadDriver.GamepadsIds)
|
|
||||||
{
|
|
||||||
gamepad = _inputManager.GamepadDriver.GetGamepad(id);
|
|
||||||
|
|
||||||
Logger.Info?.Print(LogClass.Application, $"- {id} (\"{gamepad.Name}\")");
|
|
||||||
|
|
||||||
gamepad.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (option.InputPath == null)
|
|
||||||
{
|
|
||||||
Logger.Error?.Print(LogClass.Application, "Please provide a file to load");
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_inputConfiguration = new List<InputConfig>();
|
|
||||||
_enableKeyboard = option.EnableKeyboard;
|
|
||||||
_enableMouse = option.EnableMouse;
|
|
||||||
|
|
||||||
static void LoadPlayerConfiguration(string inputProfileName, string inputId, PlayerIndex index)
|
|
||||||
{
|
|
||||||
InputConfig inputConfig = HandlePlayerConfiguration(inputProfileName, inputId, index);
|
|
||||||
|
|
||||||
if (inputConfig != null)
|
|
||||||
{
|
|
||||||
_inputConfiguration.Add(inputConfig);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LoadPlayerConfiguration(option.InputProfile1Name, option.InputId1, PlayerIndex.Player1);
|
|
||||||
LoadPlayerConfiguration(option.InputProfile2Name, option.InputId2, PlayerIndex.Player2);
|
|
||||||
LoadPlayerConfiguration(option.InputProfile3Name, option.InputId3, PlayerIndex.Player3);
|
|
||||||
LoadPlayerConfiguration(option.InputProfile4Name, option.InputId4, PlayerIndex.Player4);
|
|
||||||
LoadPlayerConfiguration(option.InputProfile5Name, option.InputId5, PlayerIndex.Player5);
|
|
||||||
LoadPlayerConfiguration(option.InputProfile6Name, option.InputId6, PlayerIndex.Player6);
|
|
||||||
LoadPlayerConfiguration(option.InputProfile7Name, option.InputId7, PlayerIndex.Player7);
|
|
||||||
LoadPlayerConfiguration(option.InputProfile8Name, option.InputId8, PlayerIndex.Player8);
|
|
||||||
LoadPlayerConfiguration(option.InputProfileHandheldName, option.InputIdHandheld, PlayerIndex.Handheld);
|
|
||||||
|
|
||||||
if (_inputConfiguration.Count == 0)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setup logging level
|
|
||||||
Logger.SetEnable(LogLevel.Debug, option.LoggingEnableDebug);
|
|
||||||
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.Trace, option.LoggingEnableTrace);
|
|
||||||
Logger.SetEnable(LogLevel.Guest, !option.LoggingDisableGuest);
|
|
||||||
Logger.SetEnable(LogLevel.AccessLog, option.LoggingEnableFsAccessLog);
|
|
||||||
|
|
||||||
if (!option.DisableFileLog)
|
|
||||||
{
|
|
||||||
string logDir = AppDataManager.LogsDirPath;
|
|
||||||
FileStream logFile = null;
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(logDir))
|
|
||||||
{
|
|
||||||
logFile = FileLogTarget.PrepareLogFile(logDir);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (logFile != null)
|
|
||||||
{
|
|
||||||
Logger.AddTarget(new AsyncLogTargetWrapper(
|
|
||||||
new FileLogTarget("file", logFile),
|
|
||||||
1000,
|
|
||||||
AsyncLogTargetOverflowAction.Block
|
|
||||||
));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Logger.Error?.Print(LogClass.Application, "No writable log directory available. Make sure either the Logs directory, Application Data, or the Ryujinx directory is writable.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setup graphics configuration
|
|
||||||
GraphicsConfig.EnableShaderCache = !option.DisableShaderCache;
|
|
||||||
GraphicsConfig.EnableTextureRecompression = option.EnableTextureRecompression;
|
|
||||||
GraphicsConfig.ResScale = option.ResScale;
|
|
||||||
GraphicsConfig.MaxAnisotropy = option.MaxAnisotropy;
|
|
||||||
GraphicsConfig.ShadersDumpPath = option.GraphicsShadersDumpPath;
|
|
||||||
GraphicsConfig.EnableMacroHLE = !option.DisableMacroHLE;
|
|
||||||
|
|
||||||
DriverUtilities.InitDriverConfig(option.BackendThreading == BackendThreading.Off);
|
|
||||||
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
LoadApplication(option);
|
|
||||||
|
|
||||||
if (_userChannelPersistence.PreviousIndex == -1 || !_userChannelPersistence.ShouldRestart)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
_userChannelPersistence.ShouldRestart = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
_inputManager.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void SetupProgressHandler()
|
|
||||||
{
|
|
||||||
if (_emulationContext.Processes.ActiveApplication.DiskCacheLoadState != null)
|
|
||||||
{
|
|
||||||
_emulationContext.Processes.ActiveApplication.DiskCacheLoadState.StateChanged -= ProgressHandler;
|
|
||||||
_emulationContext.Processes.ActiveApplication.DiskCacheLoadState.StateChanged += ProgressHandler;
|
|
||||||
}
|
|
||||||
|
|
||||||
_emulationContext.Gpu.ShaderCacheStateChanged -= ProgressHandler;
|
|
||||||
_emulationContext.Gpu.ShaderCacheStateChanged += ProgressHandler;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void ProgressHandler<T>(T state, int current, int total) where T : Enum
|
|
||||||
{
|
|
||||||
string label = state switch
|
|
||||||
{
|
|
||||||
LoadState => $"PTC : {current}/{total}",
|
|
||||||
ShaderCacheState => $"Shaders : {current}/{total}",
|
|
||||||
_ => throw new ArgumentException($"Unknown Progress Handler type {typeof(T)}"),
|
|
||||||
};
|
|
||||||
|
|
||||||
Logger.Info?.Print(LogClass.Application, label);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static WindowBase CreateWindow(Options options)
|
|
||||||
{
|
|
||||||
return options.GraphicsBackend switch
|
|
||||||
{
|
|
||||||
GraphicsBackend.Vulkan => new VulkanWindow(_inputManager, options.LoggingGraphicsDebugLevel, options.AspectRatio, options.EnableMouse, options.HideCursorMode),
|
|
||||||
GraphicsBackend.Metal => new MetalWindow(_inputManager, options.LoggingGraphicsDebugLevel, options.AspectRatio, options.EnableKeyboard, options.HideCursorMode),
|
|
||||||
_ => new OpenGLWindow(_inputManager, options.LoggingGraphicsDebugLevel, options.AspectRatio, options.EnableMouse, options.HideCursorMode)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.GraphicsBackend == GraphicsBackend.Metal && window is MetalWindow metalWindow && OperatingSystem.IsMacOS())
|
|
||||||
{
|
|
||||||
return new MetalRenderer(metalWindow.GetLayer);
|
|
||||||
}
|
|
||||||
|
|
||||||
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.ExpandRAM ? MemoryConfiguration.MemoryConfiguration6GiB : MemoryConfiguration.MemoryConfiguration4GiB,
|
|
||||||
window,
|
|
||||||
options.SystemLanguage,
|
|
||||||
options.SystemRegion,
|
|
||||||
!options.DisableVSync,
|
|
||||||
!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);
|
|
||||||
|
|
||||||
return new Switch(configuration);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void ExecutionEntrypoint()
|
|
||||||
{
|
|
||||||
if (OperatingSystem.IsWindows())
|
|
||||||
{
|
|
||||||
_windowsMultimediaTimerResolution = new WindowsMultimediaTimerResolution(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
DisplaySleep.Prevent();
|
|
||||||
|
|
||||||
_window.Initialize(_emulationContext, _inputConfiguration, _enableKeyboard, _enableMouse);
|
|
||||||
|
|
||||||
_window.Execute();
|
|
||||||
|
|
||||||
_emulationContext.Dispose();
|
|
||||||
_window.Dispose();
|
|
||||||
|
|
||||||
if (OperatingSystem.IsWindows())
|
|
||||||
{
|
|
||||||
_windowsMultimediaTimerResolution?.Dispose();
|
|
||||||
_windowsMultimediaTimerResolution = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool LoadApplication(Options options)
|
|
||||||
{
|
|
||||||
string path = options.InputPath;
|
|
||||||
|
|
||||||
Logger.RestartTime();
|
|
||||||
|
|
||||||
WindowBase window = CreateWindow(options);
|
|
||||||
IRenderer renderer = CreateRenderer(options, window);
|
|
||||||
|
|
||||||
_window = window;
|
|
||||||
|
|
||||||
_window.IsFullscreen = options.IsFullscreen;
|
|
||||||
_window.DisplayId = options.DisplayId;
|
|
||||||
_window.IsExclusiveFullscreen = options.IsExclusiveFullscreen;
|
|
||||||
_window.ExclusiveFullscreenWidth = options.ExclusiveFullscreenWidth;
|
|
||||||
_window.ExclusiveFullscreenHeight = options.ExclusiveFullscreenHeight;
|
|
||||||
_window.AntiAliasing = options.AntiAliasing;
|
|
||||||
_window.ScalingFilter = options.ScalingFilter;
|
|
||||||
_window.ScalingFilterLevel = options.ScalingFilterLevel;
|
|
||||||
|
|
||||||
_emulationContext = InitializeEmulationContext(window, renderer, options);
|
|
||||||
|
|
||||||
SystemVersion firmwareVersion = _contentManager.GetCurrentFirmwareVersion();
|
|
||||||
|
|
||||||
Logger.Notice.Print(LogClass.Application, $"Using Firmware Version: {firmwareVersion?.VersionString}");
|
|
||||||
|
|
||||||
if (Directory.Exists(path))
|
|
||||||
{
|
|
||||||
string[] romFsFiles = Directory.GetFiles(path, "*.istorage");
|
|
||||||
|
|
||||||
if (romFsFiles.Length == 0)
|
|
||||||
{
|
|
||||||
romFsFiles = Directory.GetFiles(path, "*.romfs");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (romFsFiles.Length > 0)
|
|
||||||
{
|
|
||||||
Logger.Info?.Print(LogClass.Application, "Loading as cart with RomFS.");
|
|
||||||
|
|
||||||
if (!_emulationContext.LoadCart(path, romFsFiles[0]))
|
|
||||||
{
|
|
||||||
_emulationContext.Dispose();
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Logger.Info?.Print(LogClass.Application, "Loading as cart WITHOUT RomFS.");
|
|
||||||
|
|
||||||
if (!_emulationContext.LoadCart(path))
|
|
||||||
{
|
|
||||||
_emulationContext.Dispose();
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (File.Exists(path))
|
|
||||||
{
|
|
||||||
switch (Path.GetExtension(path).ToLowerInvariant())
|
|
||||||
{
|
|
||||||
case ".xci":
|
|
||||||
Logger.Info?.Print(LogClass.Application, "Loading as XCI.");
|
|
||||||
|
|
||||||
if (!_emulationContext.LoadXci(path))
|
|
||||||
{
|
|
||||||
_emulationContext.Dispose();
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case ".nca":
|
|
||||||
Logger.Info?.Print(LogClass.Application, "Loading as NCA.");
|
|
||||||
|
|
||||||
if (!_emulationContext.LoadNca(path))
|
|
||||||
{
|
|
||||||
_emulationContext.Dispose();
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case ".nsp":
|
|
||||||
case ".pfs0":
|
|
||||||
Logger.Info?.Print(LogClass.Application, "Loading as NSP.");
|
|
||||||
|
|
||||||
if (!_emulationContext.LoadNsp(path))
|
|
||||||
{
|
|
||||||
_emulationContext.Dispose();
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
Logger.Info?.Print(LogClass.Application, "Loading as Homebrew.");
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (!_emulationContext.LoadProgram(path))
|
|
||||||
{
|
|
||||||
_emulationContext.Dispose();
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (ArgumentOutOfRangeException)
|
|
||||||
{
|
|
||||||
Logger.Error?.Print(LogClass.Application, "The specified file is not supported by Ryujinx.");
|
|
||||||
|
|
||||||
_emulationContext.Dispose();
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Logger.Warning?.Print(LogClass.Application, $"Couldn't load '{options.InputPath}'. Please specify a valid XCI/NCA/NSP/PFS0/NRO file.");
|
|
||||||
|
|
||||||
_emulationContext.Dispose();
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
SetupProgressHandler();
|
|
||||||
ExecutionEntrypoint();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -36,6 +36,8 @@ namespace Ryujinx.UI.App.Common
|
|||||||
public string Path { get; set; }
|
public string Path { get; set; }
|
||||||
public BlitStruct<ApplicationControlProperty> ControlHolder { get; set; }
|
public BlitStruct<ApplicationControlProperty> ControlHolder { get; set; }
|
||||||
|
|
||||||
|
public bool HasControlHolder => ControlHolder.ByteSpan.Length > 0;
|
||||||
|
|
||||||
public string TimePlayedString => ValueFormatUtils.FormatTimeSpan(TimePlayed);
|
public string TimePlayedString => ValueFormatUtils.FormatTimeSpan(TimePlayed);
|
||||||
|
|
||||||
public string LastPlayedString => ValueFormatUtils.FormatDateTime(LastPlayed)?.Replace(" ", "\n") ?? LocalizedNever();
|
public string LastPlayedString => ValueFormatUtils.FormatDateTime(LastPlayed)?.Replace(" ", "\n") ?? LocalizedNever();
|
||||||
|
|||||||
@@ -789,16 +789,15 @@ namespace Ryujinx.UI.App.Common
|
|||||||
using HttpClient httpClient = new HttpClient();
|
using HttpClient httpClient = new HttpClient();
|
||||||
string ldnGameDataArrayString = await httpClient.GetStringAsync($"https://{ldnWebHost}/api/public_games");
|
string ldnGameDataArrayString = await httpClient.GetStringAsync($"https://{ldnWebHost}/api/public_games");
|
||||||
ldnGameDataArray = JsonHelper.Deserialize(ldnGameDataArrayString, _ldnDataSerializerContext.IEnumerableLdnGameData);
|
ldnGameDataArray = JsonHelper.Deserialize(ldnGameDataArrayString, _ldnDataSerializerContext.IEnumerableLdnGameData);
|
||||||
var evt = new LdnGameDataReceivedEventArgs
|
LdnGameDataReceived?.Invoke(null, new LdnGameDataReceivedEventArgs
|
||||||
{
|
{
|
||||||
LdnData = ldnGameDataArray
|
LdnData = ldnGameDataArray
|
||||||
};
|
});
|
||||||
LdnGameDataReceived?.Invoke(null, evt);
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logger.Warning?.Print(LogClass.Application, $"Failed to fetch the public games JSON from the API. Player and game count in the game list will be unavailable.\n{ex.Message}");
|
Logger.Warning?.Print(LogClass.Application, $"Failed to fetch the public games JSON from the API. Player and game count in the game list will be unavailable.\n{ex.Message}");
|
||||||
LdnGameDataReceived?.Invoke(null, new LdnGameDataReceivedEventArgs()
|
LdnGameDataReceived?.Invoke(null, new LdnGameDataReceivedEventArgs
|
||||||
{
|
{
|
||||||
LdnData = Array.Empty<LdnGameData>()
|
LdnData = Array.Empty<LdnGameData>()
|
||||||
});
|
});
|
||||||
@@ -806,7 +805,7 @@ namespace Ryujinx.UI.App.Common
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
LdnGameDataReceived?.Invoke(null, new LdnGameDataReceivedEventArgs()
|
LdnGameDataReceived?.Invoke(null, new LdnGameDataReceivedEventArgs
|
||||||
{
|
{
|
||||||
LdnData = Array.Empty<LdnGameData>()
|
LdnData = Array.Empty<LdnGameData>()
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
using LibHac.Ns;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Ryujinx.UI.App.Common
|
||||||
|
{
|
||||||
|
public class LdnGameDataArray
|
||||||
|
{
|
||||||
|
private readonly LdnGameData[] _ldnDatas;
|
||||||
|
|
||||||
|
public LdnGameDataArray(IEnumerable<LdnGameData> receivedData, ref ApplicationControlProperty acp)
|
||||||
|
{
|
||||||
|
LibHac.Common.FixedArrays.Array8<ulong> communicationId = acp.LocalCommunicationId;
|
||||||
|
|
||||||
|
_ldnDatas = receivedData.Where(game =>
|
||||||
|
communicationId.Items.Contains(Convert.ToUInt64(game.TitleId, 16))
|
||||||
|
).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int PlayerCount => _ldnDatas.Sum(it => it.PlayerCount);
|
||||||
|
public int GameCount => _ldnDatas.Length;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -305,14 +305,15 @@ namespace Ryujinx.UI.Common.Configuration
|
|||||||
|
|
||||||
private static GraphicsBackend DefaultGraphicsBackend()
|
private static GraphicsBackend DefaultGraphicsBackend()
|
||||||
{
|
{
|
||||||
// Any system running macOS or returning any amount of valid Vulkan devices should default to Vulkan.
|
// Any system running macOS should default to auto, so it uses Vulkan everywhere and Metal in games where it works well.
|
||||||
// Checks for if the Vulkan version and featureset is compatible should be performed within VulkanRenderer.
|
if (OperatingSystem.IsMacOS())
|
||||||
if (OperatingSystem.IsMacOS() || VulkanRenderer.GetPhysicalDevices().Length > 0)
|
return GraphicsBackend.Auto;
|
||||||
{
|
|
||||||
return GraphicsBackend.Vulkan;
|
|
||||||
}
|
|
||||||
|
|
||||||
return GraphicsBackend.OpenGl;
|
// Any system returning any amount of valid Vulkan devices should default to Vulkan.
|
||||||
}
|
// Checks for if the Vulkan version and featureset is compatible should be performed within VulkanRenderer.
|
||||||
}
|
return VulkanRenderer.GetPhysicalDevices().Length > 0
|
||||||
|
? GraphicsBackend.Vulkan
|
||||||
|
: GraphicsBackend.OpenGl;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ namespace Ryujinx.UI.Common
|
|||||||
{
|
{
|
||||||
Assets = new Assets
|
Assets = new Assets
|
||||||
{
|
{
|
||||||
LargeImageKey = _discordGameAssetKeys.Contains(procRes.ProgramIdText) ? procRes.ProgramIdText : "game",
|
LargeImageKey = TitleIDs.GetDiscordGameAsset(procRes.ProgramIdText),
|
||||||
LargeImageText = TruncateToByteLength($"{appMeta.Title} (v{procRes.DisplayVersion})"),
|
LargeImageText = TruncateToByteLength($"{appMeta.Title} (v{procRes.DisplayVersion})"),
|
||||||
SmallImageKey = "ryujinx",
|
SmallImageKey = "ryujinx",
|
||||||
SmallImageText = TruncateToByteLength(_description)
|
SmallImageText = TruncateToByteLength(_description)
|
||||||
@@ -122,151 +122,5 @@ namespace Ryujinx.UI.Common
|
|||||||
{
|
{
|
||||||
_discordClient?.Dispose();
|
_discordClient?.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static readonly string[] _discordGameAssetKeys =
|
|
||||||
[
|
|
||||||
"010055d009f78000", // Fire Emblem: Three Houses
|
|
||||||
"0100a12011cc8000", // Fire Emblem: Shadow Dragon
|
|
||||||
"0100a6301214e000", // Fire Emblem Engage
|
|
||||||
"0100f15003e64000", // Fire Emblem Warriors
|
|
||||||
"010071f0143ea000", // Fire Emblem Warriors: Three Hopes
|
|
||||||
|
|
||||||
"01007e3006dda000", // Kirby Star Allies
|
|
||||||
"01004d300c5ae000", // Kirby and the Forgotten Land
|
|
||||||
"01006b601380e000", // Kirby's Return to Dream Land Deluxe
|
|
||||||
"01003fb00c5a8000", // Super Kirby Clash
|
|
||||||
"0100227010460000", // Kirby Fighters 2
|
|
||||||
"0100a8e016236000", // Kirby's Dream Buffet
|
|
||||||
|
|
||||||
"01007ef00011e000", // The Legend of Zelda: Breath of the Wild
|
|
||||||
"01006bb00c6f0000", // The Legend of Zelda: Link's Awakening
|
|
||||||
"01002da013484000", // The Legend of Zelda: Skyward Sword HD
|
|
||||||
"0100f2c0115b6000", // The Legend of Zelda: Tears of the Kingdom
|
|
||||||
"01008cf01baac000", // The Legend of Zelda: Echoes of Wisdom
|
|
||||||
"01000b900d8b0000", // Cadence of Hyrule
|
|
||||||
"0100ae00096ea000", // Hyrule Warriors: Definitive Edition
|
|
||||||
"01002b00111a2000", // Hyrule Warriors: Age of Calamity
|
|
||||||
|
|
||||||
"010048701995e000", // Luigi's Mansion 2 HD
|
|
||||||
"0100dca0064a6000", // Luigi's Mansion 3
|
|
||||||
|
|
||||||
"010093801237c000", // Metroid Dread
|
|
||||||
"010012101468c000", // Metroid Prime Remastered
|
|
||||||
|
|
||||||
"0100000000010000", // SUPER MARIO ODYSSEY
|
|
||||||
"0100ea80032ea000", // Super Mario Bros. U Deluxe
|
|
||||||
"01009b90006dc000", // Super Mario Maker 2
|
|
||||||
"010049900f546000", // Super Mario 3D All-Stars
|
|
||||||
"010049900F546001", // ^ 64
|
|
||||||
"010049900F546002", // ^ Sunshine
|
|
||||||
"010049900F546003", // ^ Galaxy
|
|
||||||
"010028600ebda000", // Super Mario 3D World + Bowser's Fury
|
|
||||||
"010015100b514000", // Super Mario Bros. Wonder
|
|
||||||
"0100152000022000", // Mario Kart 8 Deluxe
|
|
||||||
"010036b0034e4000", // Super Mario Party
|
|
||||||
"01006fe013472000", // Mario Party Superstars
|
|
||||||
"0100965017338000", // Super Mario Party Jamboree
|
|
||||||
"01006d0017f7a000", // Mario & Luigi: Brothership
|
|
||||||
"010067300059a000", // Mario + Rabbids: Kingdom Battle
|
|
||||||
"0100317013770000", // Mario + Rabbids: Sparks of Hope
|
|
||||||
"0100a3900c3e2000", // Paper Mario: The Origami King
|
|
||||||
"0100ecd018ebe000", // Paper Mario: The Thousand-Year Door
|
|
||||||
"0100bc0018138000", // Super Mario RPG
|
|
||||||
"0100bde00862a000", // Mario Tennis Aces
|
|
||||||
"0100c9c00e25c000", // Mario Golf: Super Rush
|
|
||||||
"010019401051c000", // Mario Strikers: Battle League
|
|
||||||
"010003000e146000", // Mario & Sonic at the Olympic Games Tokyo 2020
|
|
||||||
"0100b99019412000", // Mario vs. Donkey Kong
|
|
||||||
|
|
||||||
"0100aa80194b0000", // Pikmin 1
|
|
||||||
"0100d680194b2000", // Pikmin 2
|
|
||||||
"0100f4c009322000", // Pikmin 3 Deluxe
|
|
||||||
"0100b7c00933a000", // Pikmin 4
|
|
||||||
|
|
||||||
"010003f003a34000", // Pokémon: Let's Go Pikachu!
|
|
||||||
"0100187003a36000", // Pokémon: Let's Go Eevee!
|
|
||||||
"0100abf008968000", // Pokémon Sword
|
|
||||||
"01008db008c2c000", // Pokémon Shield
|
|
||||||
"0100000011d90000", // Pokémon Brilliant Diamond
|
|
||||||
"010018e011d92000", // Pokémon Shining Pearl
|
|
||||||
"01001f5010dfa000", // Pokémon Legends: Arceus
|
|
||||||
"0100a3d008c5c000", // Pokémon Scarlet
|
|
||||||
"01008f6008c5e000", // Pokémon Violet
|
|
||||||
"0100b3f000be2000", // Pokkén Tournament DX
|
|
||||||
"0100f4300bf2c000", // New Pokémon Snap
|
|
||||||
|
|
||||||
"01003bc0000a0000", // Splatoon 2 (US)
|
|
||||||
"0100f8f0000a2000", // Splatoon 2 (EU)
|
|
||||||
"01003c700009c000", // Splatoon 2 (JP)
|
|
||||||
"0100c2500fc20000", // Splatoon 3
|
|
||||||
"0100ba0018500000", // Splatoon 3: Splatfest World Premiere
|
|
||||||
|
|
||||||
"010040600c5ce000", // Tetris 99
|
|
||||||
"0100277011f1a000", // Super Mario Bros. 35
|
|
||||||
"0100ad9012510000", // PAC-MAN 99
|
|
||||||
"0100ccf019c8c000", // F-ZERO 99
|
|
||||||
"0100d870045b6000", // NES - Nintendo Switch Online
|
|
||||||
"01008d300c50c000", // SNES - Nintendo Switch Online
|
|
||||||
"0100c9a00ece6000", // N64 - Nintendo Switch Online
|
|
||||||
"0100e0601c632000", // N64 - Nintendo Switch Online 18+
|
|
||||||
"0100c62011050000", // GB - Nintendo Switch Online
|
|
||||||
"010012f017576000", // GBA - Nintendo Switch Online
|
|
||||||
|
|
||||||
"01000320000cc000", // 1-2 Switch
|
|
||||||
"0100300012f2a000", // Advance Wars 1+2: Re-Boot Camp
|
|
||||||
"01006f8002326000", // Animal Crossing: New Horizons
|
|
||||||
"0100620012d6e000", // Big Brain Academy: Brain vs. Brain
|
|
||||||
"010018300d006000", // BOXBOY! + BOXGIRL!
|
|
||||||
"0100c1f0051b6000", // Donkey Kong Country: Tropical Freeze
|
|
||||||
"0100ed000d390000", // Dr. Kawashima's Brain Training
|
|
||||||
"010067b017588000", // Endless Ocean Luminous
|
|
||||||
"0100d2f00d5c0000", // Nintendo Switch Sports
|
|
||||||
"01006b5012b32000", // Part Time UFO
|
|
||||||
"0100704000B3A000", // Snipperclips
|
|
||||||
"01006a800016e000", // Super Smash Bros. Ultimate
|
|
||||||
"0100a9400c9c2000", // Tokyo Mirage Sessions #FE Encore
|
|
||||||
|
|
||||||
"010076f0049a2000", // Bayonetta
|
|
||||||
"01007960049a0000", // Bayonetta 2
|
|
||||||
"01004a4010fea000", // Bayonetta 3
|
|
||||||
"0100cf5010fec000", // Bayonetta Origins: Cereza and the Lost Demon
|
|
||||||
|
|
||||||
"0100dcd01525a000", // Persona 3 Portable
|
|
||||||
"010062b01525c000", // Persona 4 Golden
|
|
||||||
"010075a016a3a000", // Persona 4 Arena Ultimax
|
|
||||||
"01005ca01580e000", // Persona 5 Royal
|
|
||||||
"0100801011c3e000", // Persona 5 Strikers
|
|
||||||
"010087701b092000", // Persona 5 Tactica
|
|
||||||
|
|
||||||
"01009aa000faa000", // Sonic Mania
|
|
||||||
"01004ad014bf0000", // Sonic Frontiers
|
|
||||||
"01005ea01c0fc000", // SONIC X SHADOW GENERATIONS
|
|
||||||
"01005ea01c0fc001", // ^
|
|
||||||
|
|
||||||
"010056e00853a000", // A Hat in Time
|
|
||||||
"0100dbf01000a000", // Burnout Paradise Remastered
|
|
||||||
"0100744001588000", // Cars 3: Driven to Win
|
|
||||||
"0100b41013c82000", // Cruis'n Blast
|
|
||||||
"01001b300b9be000", // Diablo III: Eternal Collection
|
|
||||||
"01008c8012920000", // Dying Light Platinum Edition
|
|
||||||
"010073c01af34000", // LEGO Horizon Adventures
|
|
||||||
"0100770008dd8000", // Monster Hunter Generations Ultimate
|
|
||||||
"0100b04011742000", // Monster Hunter Rise
|
|
||||||
"0100853015e86000", // No Man's Sky
|
|
||||||
"01007bb017812000", // Portal
|
|
||||||
"0100abd01785c000", // Portal 2
|
|
||||||
"01008e200c5c2000", // Muse Dash
|
|
||||||
"01007820196a6000", // Red Dead Redemption
|
|
||||||
"01002f7013224000", // Rune Factory 5
|
|
||||||
"01008d100d43e000", // Saints Row IV
|
|
||||||
"0100de600beee000", // Saints Row: The Third - The Full Package
|
|
||||||
"01001180021fa000", // Shovel Knight: Specter of Torment
|
|
||||||
"0100d7a01b7a2000", // Star Wars: Bounty Hunter
|
|
||||||
"0100800015926000", // Suika Game
|
|
||||||
"0100e46006708000", // Terraria
|
|
||||||
"01000a10041ea000", // The Elder Scrolls V: Skyrim
|
|
||||||
"010057a01e4d4000", // TSUKIHIME -A piece of blue glass moon-
|
|
||||||
"010080b00ad66000", // Undertale
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
using LibHac.Common;
|
||||||
|
using LibHac.Ncm;
|
||||||
|
using LibHac.Ns;
|
||||||
|
using LibHac.Tools.FsSystem.NcaUtils;
|
||||||
|
using Ryujinx.HLE;
|
||||||
|
using Ryujinx.HLE.FileSystem;
|
||||||
|
using Ryujinx.UI.App.Common;
|
||||||
|
|
||||||
|
namespace Ryujinx.UI.Common.Helper
|
||||||
|
{
|
||||||
|
public readonly struct AppletMetadata
|
||||||
|
{
|
||||||
|
private readonly ContentManager _contentManager;
|
||||||
|
|
||||||
|
public string Name { get; }
|
||||||
|
public ulong ProgramId { get; }
|
||||||
|
|
||||||
|
public string Version { get; }
|
||||||
|
|
||||||
|
public AppletMetadata(ContentManager contentManager, string name, ulong programId, string version = "1.0.0")
|
||||||
|
: this(name, programId, version)
|
||||||
|
{
|
||||||
|
_contentManager = contentManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AppletMetadata(string name, ulong programId, string version = "1.0.0")
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
ProgramId = programId;
|
||||||
|
Version = version;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetContentPath(ContentManager contentManager)
|
||||||
|
=> (contentManager ?? _contentManager)
|
||||||
|
.GetInstalledContentPath(ProgramId, StorageId.BuiltInSystem, NcaContentType.Program);
|
||||||
|
|
||||||
|
public bool CanStart(ContentManager contentManager, out ApplicationData appData, out BlitStruct<ApplicationControlProperty> appControl)
|
||||||
|
{
|
||||||
|
contentManager ??= _contentManager;
|
||||||
|
if (contentManager == null)
|
||||||
|
{
|
||||||
|
appData = null;
|
||||||
|
appControl = new BlitStruct<ApplicationControlProperty>(0);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
appData = new()
|
||||||
|
{
|
||||||
|
Name = Name,
|
||||||
|
Id = ProgramId,
|
||||||
|
Path = GetContentPath(contentManager)
|
||||||
|
};
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(appData.Path))
|
||||||
|
{
|
||||||
|
appControl = new BlitStruct<ApplicationControlProperty>(0);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
appControl = StructHelpers.CreateCustomNacpData(Name, Version);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 609 KiB |
@@ -33,7 +33,7 @@
|
|||||||
<EmbeddedResource Include="Resources\Icon_XCI.png" />
|
<EmbeddedResource Include="Resources\Icon_XCI.png" />
|
||||||
<EmbeddedResource Include="Resources\Logo_Amiibo.png" />
|
<EmbeddedResource Include="Resources\Logo_Amiibo.png" />
|
||||||
<EmbeddedResource Include="Resources\Logo_Ryujinx.png" />
|
<EmbeddedResource Include="Resources\Logo_Ryujinx.png" />
|
||||||
<EmbeddedResource Include="Resources\Logo_Thiccjinx.png" />
|
<EmbeddedResource Include="Resources\Logo_Ryujinx_AntiAlias.png" />
|
||||||
<EmbeddedResource Include="Resources\Logo_Discord_Dark.png" />
|
<EmbeddedResource Include="Resources\Logo_Discord_Dark.png" />
|
||||||
<EmbeddedResource Include="Resources\Logo_Discord_Light.png" />
|
<EmbeddedResource Include="Resources\Logo_Discord_Light.png" />
|
||||||
<EmbeddedResource Include="Resources\Logo_GitHub_Dark.png" />
|
<EmbeddedResource Include="Resources\Logo_GitHub_Dark.png" />
|
||||||
|
|||||||
+14
-16
@@ -3,6 +3,7 @@ using Avalonia.Controls;
|
|||||||
using Avalonia.Controls.ApplicationLifetimes;
|
using Avalonia.Controls.ApplicationLifetimes;
|
||||||
using Avalonia.Input;
|
using Avalonia.Input;
|
||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
|
using Gommon;
|
||||||
using LibHac.Common;
|
using LibHac.Common;
|
||||||
using LibHac.Ns;
|
using LibHac.Ns;
|
||||||
using LibHac.Tools.FsSystem;
|
using LibHac.Tools.FsSystem;
|
||||||
@@ -310,7 +311,7 @@ namespace Ryujinx.Ava
|
|||||||
Device.VSyncMode = e.NewValue;
|
Device.VSyncMode = e.NewValue;
|
||||||
Device.UpdateVSyncInterval();
|
Device.UpdateVSyncInterval();
|
||||||
}
|
}
|
||||||
_renderer.Window?.ChangeVSyncMode((Ryujinx.Graphics.GAL.VSyncMode)e.NewValue);
|
_renderer.Window?.ChangeVSyncMode(e.NewValue);
|
||||||
|
|
||||||
_viewModel.ShowCustomVSyncIntervalPicker = (e.NewValue == VSyncMode.Custom);
|
_viewModel.ShowCustomVSyncIntervalPicker = (e.NewValue == VSyncMode.Custom);
|
||||||
}
|
}
|
||||||
@@ -894,23 +895,20 @@ namespace Ryujinx.Ava
|
|||||||
VirtualFileSystem.ReloadKeySet();
|
VirtualFileSystem.ReloadKeySet();
|
||||||
|
|
||||||
// Initialize Renderer.
|
// Initialize Renderer.
|
||||||
IRenderer renderer;
|
GraphicsBackend backend = TitleIDs.SelectGraphicsBackend(ApplicationId.ToString("X16"), ConfigurationState.Instance.Graphics.GraphicsBackend);
|
||||||
|
|
||||||
if (ConfigurationState.Instance.Graphics.GraphicsBackend.Value == GraphicsBackend.Vulkan)
|
IRenderer renderer = backend switch
|
||||||
{
|
{
|
||||||
renderer = new VulkanRenderer(
|
#pragma warning disable CA1416 // This call site is reachable on all platforms
|
||||||
|
// SelectGraphicsBackend does a check for Mac, on top of checking if it's an ARM Mac. This isn't a problem.
|
||||||
|
GraphicsBackend.Metal => new MetalRenderer((RendererHost.EmbeddedWindow as EmbeddedWindowMetal)!.CreateSurface),
|
||||||
|
#pragma warning restore CA1416
|
||||||
|
GraphicsBackend.Vulkan => VulkanRenderer.Create(
|
||||||
ConfigurationState.Instance.Graphics.PreferredGpu,
|
ConfigurationState.Instance.Graphics.PreferredGpu,
|
||||||
(RendererHost.EmbeddedWindow as EmbeddedWindowVulkan)!.CreateSurface,
|
(RendererHost.EmbeddedWindow as EmbeddedWindowVulkan)!.CreateSurface,
|
||||||
VulkanHelper.GetRequiredInstanceExtensions);
|
VulkanHelper.GetRequiredInstanceExtensions),
|
||||||
}
|
_ => new OpenGLRenderer()
|
||||||
else if (ConfigurationState.Instance.Graphics.GraphicsBackend.Value == GraphicsBackend.Metal && OperatingSystem.IsMacOS())
|
};
|
||||||
{
|
|
||||||
renderer = new MetalRenderer((RendererHost.EmbeddedWindow as EmbeddedWindowMetal).CreateSurface);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
renderer = new OpenGLRenderer();
|
|
||||||
}
|
|
||||||
|
|
||||||
BackendThreading threadingMode = ConfigurationState.Instance.Graphics.BackendThreading;
|
BackendThreading threadingMode = ConfigurationState.Instance.Graphics.BackendThreading;
|
||||||
|
|
||||||
@@ -1076,7 +1074,7 @@ namespace Ryujinx.Ava
|
|||||||
Device.Gpu.SetGpuThread();
|
Device.Gpu.SetGpuThread();
|
||||||
Device.Gpu.InitializeShaderCache(_gpuCancellationTokenSource.Token);
|
Device.Gpu.InitializeShaderCache(_gpuCancellationTokenSource.Token);
|
||||||
|
|
||||||
_renderer.Window.ChangeVSyncMode((Ryujinx.Graphics.GAL.VSyncMode)Device.VSyncMode);
|
_renderer.Window.ChangeVSyncMode(Device.VSyncMode);
|
||||||
|
|
||||||
while (_isActive)
|
while (_isActive)
|
||||||
{
|
{
|
||||||
@@ -1123,7 +1121,7 @@ namespace Ryujinx.Ava
|
|||||||
|
|
||||||
public void InitStatus()
|
public void InitStatus()
|
||||||
{
|
{
|
||||||
_viewModel.BackendText = ConfigurationState.Instance.Graphics.GraphicsBackend.Value switch
|
_viewModel.BackendText = RendererHost.Backend switch
|
||||||
{
|
{
|
||||||
GraphicsBackend.Vulkan => "Vulkan",
|
GraphicsBackend.Vulkan => "Vulkan",
|
||||||
GraphicsBackend.OpenGl => "OpenGL",
|
GraphicsBackend.OpenGl => "OpenGL",
|
||||||
|
|||||||
@@ -19677,6 +19677,54 @@
|
|||||||
"zh_TW": "選擇模擬器將使用的圖形後端。\n\n只要驅動程式是最新的,Vulkan 對所有現代顯示卡來說都更好用。Vulkan 還能在所有 GPU 廠商上實現更快的著色器編譯 (減少卡頓)。\n\nOpenGL 在舊式 Nvidia GPU、Linux 上的舊式 AMD GPU 或 VRAM 較低的 GPU 上可能會取得更好的效果,不過著色器編譯的卡頓會更嚴重。\n\n如果不確定,請設定為 Vulkan。如果您的 GPU 使用最新的圖形驅動程式也不支援 Vulkan,請設定為 OpenGL。"
|
"zh_TW": "選擇模擬器將使用的圖形後端。\n\n只要驅動程式是最新的,Vulkan 對所有現代顯示卡來說都更好用。Vulkan 還能在所有 GPU 廠商上實現更快的著色器編譯 (減少卡頓)。\n\nOpenGL 在舊式 Nvidia GPU、Linux 上的舊式 AMD GPU 或 VRAM 較低的 GPU 上可能會取得更好的效果,不過著色器編譯的卡頓會更嚴重。\n\n如果不確定,請設定為 Vulkan。如果您的 GPU 使用最新的圖形驅動程式也不支援 Vulkan,請設定為 OpenGL。"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"ID": "SettingsTabGraphicsBackendAuto",
|
||||||
|
"Translations": {
|
||||||
|
"ar_SA": "",
|
||||||
|
"de_DE": "",
|
||||||
|
"el_GR": "",
|
||||||
|
"en_US": "Auto",
|
||||||
|
"es_ES": "",
|
||||||
|
"fr_FR": "",
|
||||||
|
"he_IL": "",
|
||||||
|
"it_IT": "",
|
||||||
|
"ja_JP": "",
|
||||||
|
"ko_KR": "",
|
||||||
|
"no_NO": "",
|
||||||
|
"pl_PL": "",
|
||||||
|
"pt_BR": "",
|
||||||
|
"ru_RU": "",
|
||||||
|
"th_TH": "",
|
||||||
|
"tr_TR": "",
|
||||||
|
"uk_UA": "",
|
||||||
|
"zh_CN": "",
|
||||||
|
"zh_TW": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ID": "SettingsTabGraphicsBackendAutoTooltip",
|
||||||
|
"Translations": {
|
||||||
|
"ar_SA": "",
|
||||||
|
"de_DE": "",
|
||||||
|
"el_GR": "",
|
||||||
|
"en_US": "Uses Vulkan.\nOn an ARM Mac, and when playing a game that runs well under it, uses the Metal backend.",
|
||||||
|
"es_ES": "",
|
||||||
|
"fr_FR": "",
|
||||||
|
"he_IL": "",
|
||||||
|
"it_IT": "",
|
||||||
|
"ja_JP": "",
|
||||||
|
"ko_KR": "",
|
||||||
|
"no_NO": "",
|
||||||
|
"pl_PL": "",
|
||||||
|
"pt_BR": "",
|
||||||
|
"ru_RU": "",
|
||||||
|
"th_TH": "",
|
||||||
|
"tr_TR": "",
|
||||||
|
"uk_UA": "",
|
||||||
|
"zh_CN": "",
|
||||||
|
"zh_TW": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"ID": "SettingsEnableTextureRecompression",
|
"ID": "SettingsEnableTextureRecompression",
|
||||||
"Translations": {
|
"Translations": {
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ namespace Ryujinx.Ava.Common
|
|||||||
var cancellationToken = new CancellationTokenSource();
|
var cancellationToken = new CancellationTokenSource();
|
||||||
|
|
||||||
UpdateWaitWindow waitingDialog = new(
|
UpdateWaitWindow waitingDialog = new(
|
||||||
App.FormatTitle(LocaleKeys.DialogNcaExtractionTitle),
|
RyujinxApp.FormatTitle(LocaleKeys.DialogNcaExtractionTitle),
|
||||||
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogNcaExtractionMessage, ncaSectionType, Path.GetFileName(titleFilePath)),
|
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogNcaExtractionMessage, ncaSectionType, Path.GetFileName(titleFilePath)),
|
||||||
cancellationToken);
|
cancellationToken);
|
||||||
|
|
||||||
@@ -268,10 +268,9 @@ namespace Ryujinx.Ava.Common
|
|||||||
{
|
{
|
||||||
Dispatcher.UIThread.Post(waitingDialog.Close);
|
Dispatcher.UIThread.Post(waitingDialog.Close);
|
||||||
|
|
||||||
NotificationHelper.Show(
|
NotificationHelper.ShowInformation(
|
||||||
App.FormatTitle(LocaleKeys.DialogNcaExtractionTitle),
|
RyujinxApp.FormatTitle(LocaleKeys.DialogNcaExtractionTitle),
|
||||||
$"{titleName}\n\n{LocaleManager.Instance[LocaleKeys.DialogNcaExtractionSuccessMessage]}",
|
$"{titleName}\n\n{LocaleManager.Instance[LocaleKeys.DialogNcaExtractionSuccessMessage]}");
|
||||||
NotificationType.Information);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
using Gommon;
|
using Gommon;
|
||||||
using Ryujinx.Ava.UI.ViewModels;
|
using Ryujinx.Ava.UI.ViewModels;
|
||||||
using Ryujinx.Common;
|
using Ryujinx.Common;
|
||||||
using Ryujinx.Common.Logging;
|
|
||||||
using Ryujinx.Common.Utilities;
|
using Ryujinx.Common.Utilities;
|
||||||
using Ryujinx.UI.Common.Configuration;
|
using Ryujinx.UI.Common.Configuration;
|
||||||
using System;
|
using System;
|
||||||
@@ -17,7 +16,6 @@ namespace Ryujinx.Ava.Common.Locale
|
|||||||
private const string DefaultLanguageCode = "en_US";
|
private const string DefaultLanguageCode = "en_US";
|
||||||
|
|
||||||
private readonly Dictionary<LocaleKeys, string> _localeStrings;
|
private readonly Dictionary<LocaleKeys, string> _localeStrings;
|
||||||
private Dictionary<LocaleKeys, string> _localeDefaultStrings;
|
|
||||||
private readonly ConcurrentDictionary<LocaleKeys, object[]> _dynamicValues;
|
private readonly ConcurrentDictionary<LocaleKeys, object[]> _dynamicValues;
|
||||||
private string _localeLanguageCode;
|
private string _localeLanguageCode;
|
||||||
|
|
||||||
@@ -27,7 +25,6 @@ namespace Ryujinx.Ava.Common.Locale
|
|||||||
public LocaleManager()
|
public LocaleManager()
|
||||||
{
|
{
|
||||||
_localeStrings = new Dictionary<LocaleKeys, string>();
|
_localeStrings = new Dictionary<LocaleKeys, string>();
|
||||||
_localeDefaultStrings = new Dictionary<LocaleKeys, string>();
|
|
||||||
_dynamicValues = new ConcurrentDictionary<LocaleKeys, object[]>();
|
_dynamicValues = new ConcurrentDictionary<LocaleKeys, object[]>();
|
||||||
|
|
||||||
Load();
|
Load();
|
||||||
@@ -37,9 +34,7 @@ namespace Ryujinx.Ava.Common.Locale
|
|||||||
{
|
{
|
||||||
var localeLanguageCode = !string.IsNullOrEmpty(ConfigurationState.Instance.UI.LanguageCode.Value) ?
|
var localeLanguageCode = !string.IsNullOrEmpty(ConfigurationState.Instance.UI.LanguageCode.Value) ?
|
||||||
ConfigurationState.Instance.UI.LanguageCode.Value : CultureInfo.CurrentCulture.Name.Replace('-', '_');
|
ConfigurationState.Instance.UI.LanguageCode.Value : CultureInfo.CurrentCulture.Name.Replace('-', '_');
|
||||||
|
|
||||||
// Load en_US as default, if the target language translation is missing or incomplete.
|
|
||||||
LoadDefaultLanguage();
|
|
||||||
LoadLanguage(localeLanguageCode);
|
LoadLanguage(localeLanguageCode);
|
||||||
|
|
||||||
// Save whatever we ended up with.
|
// Save whatever we ended up with.
|
||||||
@@ -66,26 +61,14 @@ namespace Ryujinx.Ava.Common.Locale
|
|||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
// If formatting failed use the default text instead.
|
// If formatting the text failed,
|
||||||
if (_localeDefaultStrings.TryGetValue(key, out value))
|
// continue to the below line & return the text without formatting.
|
||||||
try
|
|
||||||
{
|
|
||||||
return string.Format(value, dynamicValue);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
// If formatting the default text failed return the key.
|
|
||||||
return key.ToString();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the locale doesn't contain the key return the default one.
|
return key.ToString(); // If the locale text doesn't exist return the key.
|
||||||
return _localeDefaultStrings.TryGetValue(key, out string defaultValue)
|
|
||||||
? defaultValue
|
|
||||||
: key.ToString(); // If the locale text doesn't exist return the key.
|
|
||||||
}
|
}
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
@@ -109,16 +92,11 @@ namespace Ryujinx.Ava.Common.Locale
|
|||||||
{
|
{
|
||||||
_dynamicValues[key] = values;
|
_dynamicValues[key] = values;
|
||||||
|
|
||||||
OnPropertyChanged("Item");
|
OnPropertyChanged("Translation");
|
||||||
|
|
||||||
return this[key];
|
return this[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LoadDefaultLanguage()
|
|
||||||
{
|
|
||||||
_localeDefaultStrings = LoadJsonLanguage(DefaultLanguageCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void LoadLanguage(string languageCode)
|
public void LoadLanguage(string languageCode)
|
||||||
{
|
{
|
||||||
var locale = LoadJsonLanguage(languageCode);
|
var locale = LoadJsonLanguage(languageCode);
|
||||||
@@ -126,7 +104,7 @@ namespace Ryujinx.Ava.Common.Locale
|
|||||||
if (locale == null)
|
if (locale == null)
|
||||||
{
|
{
|
||||||
_localeLanguageCode = DefaultLanguageCode;
|
_localeLanguageCode = DefaultLanguageCode;
|
||||||
locale = _localeDefaultStrings;
|
locale = LoadJsonLanguage(_localeLanguageCode);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -138,16 +116,12 @@ namespace Ryujinx.Ava.Common.Locale
|
|||||||
_localeStrings[key] = val;
|
_localeStrings[key] = val;
|
||||||
}
|
}
|
||||||
|
|
||||||
OnPropertyChanged("Item");
|
OnPropertyChanged("Translation");
|
||||||
|
|
||||||
LocaleChanged?.Invoke();
|
LocaleChanged?.Invoke();
|
||||||
}
|
}
|
||||||
|
|
||||||
#nullable enable
|
|
||||||
|
|
||||||
private static LocalesJson? _localeData;
|
private static LocalesJson? _localeData;
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
private static Dictionary<LocaleKeys, string> LoadJsonLanguage(string languageCode)
|
private static Dictionary<LocaleKeys, string> LoadJsonLanguage(string languageCode)
|
||||||
{
|
{
|
||||||
@@ -158,18 +132,29 @@ namespace Ryujinx.Ava.Common.Locale
|
|||||||
|
|
||||||
foreach (LocalesEntry locale in _localeData.Value.Locales)
|
foreach (LocalesEntry locale in _localeData.Value.Locales)
|
||||||
{
|
{
|
||||||
if (locale.Translations.Count != _localeData.Value.Languages.Count)
|
if (locale.Translations.Count < _localeData.Value.Languages.Count)
|
||||||
{
|
{
|
||||||
throw new Exception($"Locale key {{{locale.ID}}} is missing languages! Has {locale.Translations.Count} translations, expected {_localeData.Value.Languages.Count}!");
|
throw new Exception($"Locale key {{{locale.ID}}} is missing languages! Has {locale.Translations.Count} translations, expected {_localeData.Value.Languages.Count}!");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (locale.Translations.Count > _localeData.Value.Languages.Count)
|
||||||
|
{
|
||||||
|
throw new Exception($"Locale key {{{locale.ID}}} has too many languages! Has {locale.Translations.Count} translations, expected {_localeData.Value.Languages.Count}!");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Enum.TryParse<LocaleKeys>(locale.ID, out var localeKey))
|
if (!Enum.TryParse<LocaleKeys>(locale.ID, out var localeKey))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
localeStrings[localeKey] =
|
var str = locale.Translations.TryGetValue(languageCode, out string val) && !string.IsNullOrEmpty(val)
|
||||||
locale.Translations.TryGetValue(languageCode, out string val) && val != string.Empty
|
? val
|
||||||
? val
|
: locale.Translations[DefaultLanguageCode];
|
||||||
: locale.Translations[DefaultLanguageCode];
|
|
||||||
|
if (string.IsNullOrEmpty(str))
|
||||||
|
{
|
||||||
|
throw new Exception($"Locale key '{locale.ID}' has no valid translations for desired language {languageCode}! {DefaultLanguageCode} is an empty string or null");
|
||||||
|
}
|
||||||
|
|
||||||
|
localeStrings[localeKey] = str;
|
||||||
}
|
}
|
||||||
|
|
||||||
return localeStrings;
|
return localeStrings;
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ namespace Ryujinx.Ava.Common.Markup
|
|||||||
{
|
{
|
||||||
internal abstract class BasicMarkupExtension<T> : MarkupExtension
|
internal abstract class BasicMarkupExtension<T> : MarkupExtension
|
||||||
{
|
{
|
||||||
public virtual string Name => "Item";
|
public abstract string Name { get; }
|
||||||
public virtual Action<object, T?>? Setter => null;
|
public virtual Action<object, T?>? Setter => null;
|
||||||
|
|
||||||
protected abstract T? Value { get; }
|
protected abstract T? Value { get; }
|
||||||
|
|||||||
@@ -6,16 +6,19 @@ namespace Ryujinx.Ava.Common.Markup
|
|||||||
{
|
{
|
||||||
internal class IconExtension(string iconString) : BasicMarkupExtension<Icon>
|
internal class IconExtension(string iconString) : BasicMarkupExtension<Icon>
|
||||||
{
|
{
|
||||||
|
public override string Name => "Icon";
|
||||||
protected override Icon Value => new() { Value = iconString };
|
protected override Icon Value => new() { Value = iconString };
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class SpinningIconExtension(string iconString) : BasicMarkupExtension<Icon>
|
internal class SpinningIconExtension(string iconString) : BasicMarkupExtension<Icon>
|
||||||
{
|
{
|
||||||
|
public override string Name => "SIcon";
|
||||||
protected override Icon Value => new() { Value = iconString, Animation = IconAnimation.Spin };
|
protected override Icon Value => new() { Value = iconString, Animation = IconAnimation.Spin };
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class LocaleExtension(LocaleKeys key) : BasicMarkupExtension<string>
|
internal class LocaleExtension(LocaleKeys key) : BasicMarkupExtension<string>
|
||||||
{
|
{
|
||||||
|
public override string Name => "Translation";
|
||||||
protected override string Value => LocaleManager.Instance[key];
|
protected override string Value => LocaleManager.Instance[key];
|
||||||
|
|
||||||
protected override void ConfigureBindingExtension(CompiledBindingExtension bindingExtension)
|
protected override void ConfigureBindingExtension(CompiledBindingExtension bindingExtension)
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ namespace Ryujinx.Ava
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static AppBuilder BuildAvaloniaApp() =>
|
public static AppBuilder BuildAvaloniaApp() =>
|
||||||
AppBuilder.Configure<App>()
|
AppBuilder.Configure<RyujinxApp>()
|
||||||
.UsePlatformDetect()
|
.UsePlatformDetect()
|
||||||
.With(new X11PlatformOptions
|
.With(new X11PlatformOptions
|
||||||
{
|
{
|
||||||
@@ -100,7 +100,7 @@ namespace Ryujinx.Ava
|
|||||||
// Delete backup files after updating.
|
// Delete backup files after updating.
|
||||||
Task.Run(Updater.CleanupUpdate);
|
Task.Run(Updater.CleanupUpdate);
|
||||||
|
|
||||||
Console.Title = $"{App.FullAppName} Console {Version}";
|
Console.Title = $"{RyujinxApp.FullAppName} Console {Version}";
|
||||||
|
|
||||||
// Hook unhandled exception and process exit events.
|
// Hook unhandled exception and process exit events.
|
||||||
AppDomain.CurrentDomain.UnhandledException += (sender, e)
|
AppDomain.CurrentDomain.UnhandledException += (sender, e)
|
||||||
@@ -225,7 +225,7 @@ namespace Ryujinx.Ava
|
|||||||
|
|
||||||
private static void PrintSystemInfo()
|
private static void PrintSystemInfo()
|
||||||
{
|
{
|
||||||
Logger.Notice.Print(LogClass.Application, $"{App.FullAppName} Version: {Version}");
|
Logger.Notice.Print(LogClass.Application, $"{RyujinxApp.FullAppName} Version: {Version}");
|
||||||
SystemInfo.Gather().Print();
|
SystemInfo.Gather().Print();
|
||||||
|
|
||||||
var enabledLogLevels = Logger.GetEnabledLevels().ToArray();
|
var enabledLogLevels = Logger.GetEnabledLevels().ToArray();
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<Application
|
<Application
|
||||||
x:Class="Ryujinx.Ava.App"
|
x:Class="Ryujinx.Ava.RyujinxApp"
|
||||||
xmlns="https://github.com/avaloniaui"
|
xmlns="https://github.com/avaloniaui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:sty="using:FluentAvalonia.Styling">
|
xmlns:sty="using:FluentAvalonia.Styling">
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Controls.ApplicationLifetimes;
|
using Avalonia.Controls.ApplicationLifetimes;
|
||||||
|
using Avalonia.Input.Platform;
|
||||||
using Avalonia.Markup.Xaml;
|
using Avalonia.Markup.Xaml;
|
||||||
using Avalonia.Platform;
|
using Avalonia.Platform;
|
||||||
using Avalonia.Styling;
|
using Avalonia.Styling;
|
||||||
@@ -19,7 +20,7 @@ using System.Diagnostics;
|
|||||||
|
|
||||||
namespace Ryujinx.Ava
|
namespace Ryujinx.Ava
|
||||||
{
|
{
|
||||||
public class App : Application
|
public class RyujinxApp : Application
|
||||||
{
|
{
|
||||||
internal static string FormatTitle(LocaleKeys? windowTitleKey = null)
|
internal static string FormatTitle(LocaleKeys? windowTitleKey = null)
|
||||||
=> windowTitleKey is null
|
=> windowTitleKey is null
|
||||||
@@ -32,6 +33,9 @@ namespace Ryujinx.Ava
|
|||||||
.ApplicationLifetime.Cast<IClassicDesktopStyleApplicationLifetime>()
|
.ApplicationLifetime.Cast<IClassicDesktopStyleApplicationLifetime>()
|
||||||
.MainWindow.Cast<MainWindow>();
|
.MainWindow.Cast<MainWindow>();
|
||||||
|
|
||||||
|
public static bool IsClipboardAvailable(out IClipboard clipboard)
|
||||||
|
=> (clipboard = MainWindow.Clipboard) != null;
|
||||||
|
|
||||||
public static void SetTaskbarProgress(TaskBarProgressBarState state) => MainWindow.PlatformFeatures.SetTaskBarProgressBarState(state);
|
public static void SetTaskbarProgress(TaskBarProgressBarState state) => MainWindow.PlatformFeatures.SetTaskBarProgressBarState(state);
|
||||||
public static void SetTaskbarProgressValue(ulong current, ulong total) => MainWindow.PlatformFeatures.SetTaskBarProgressBarValue(current, total);
|
public static void SetTaskbarProgressValue(ulong current, ulong total) => MainWindow.PlatformFeatures.SetTaskBarProgressBarValue(current, total);
|
||||||
public static void SetTaskbarProgressValue(long current, long total) => SetTaskbarProgressValue(Convert.ToUInt64(current), Convert.ToUInt64(total));
|
public static void SetTaskbarProgressValue(long current, long total) => SetTaskbarProgressValue(Convert.ToUInt64(current), Convert.ToUInt64(total));
|
||||||
@@ -132,7 +136,7 @@ namespace Ryujinx.Ava
|
|||||||
};
|
};
|
||||||
|
|
||||||
public static ThemeVariant DetectSystemTheme() =>
|
public static ThemeVariant DetectSystemTheme() =>
|
||||||
Current is App { PlatformSettings: not null } app
|
Current is RyujinxApp { PlatformSettings: not null } app
|
||||||
? ConvertThemeVariant(app.PlatformSettings.GetColorValues().ThemeVariant)
|
? ConvertThemeVariant(app.PlatformSettings.GetColorValues().ThemeVariant)
|
||||||
: ThemeVariant.Default;
|
: ThemeVariant.Default;
|
||||||
}
|
}
|
||||||
@@ -101,11 +101,21 @@
|
|||||||
VerticalAlignment="Top"
|
VerticalAlignment="Top"
|
||||||
Orientation="Vertical"
|
Orientation="Vertical"
|
||||||
Spacing="5">
|
Spacing="5">
|
||||||
<TextBlock
|
<Button
|
||||||
HorizontalAlignment="Stretch"
|
Click="IdString_OnClick"
|
||||||
Text="{Binding IdString}"
|
HorizontalContentAlignment="Left"
|
||||||
TextAlignment="Start"
|
VerticalAlignment="Center"
|
||||||
TextWrapping="Wrap" />
|
Background="{DynamicResource AppListBackgroundColor}"
|
||||||
|
Margin="-1, 0, 0, 0"
|
||||||
|
Padding="0" >
|
||||||
|
<TextBlock
|
||||||
|
Margin="1.5"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
Text="{Binding IdString}"
|
||||||
|
Tag="{Binding Id}"
|
||||||
|
TextAlignment="Start"
|
||||||
|
TextWrapping="Wrap" />
|
||||||
|
</Button>
|
||||||
<TextBlock
|
<TextBlock
|
||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
Text="{Binding FileExtension}"
|
Text="{Binding FileExtension}"
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Controls.Notifications;
|
||||||
using Avalonia.Input;
|
using Avalonia.Input;
|
||||||
using Avalonia.Interactivity;
|
using Avalonia.Interactivity;
|
||||||
|
using FluentAvalonia.UI.Controls;
|
||||||
using Ryujinx.Ava.UI.Helpers;
|
using Ryujinx.Ava.UI.Helpers;
|
||||||
using Ryujinx.Ava.UI.ViewModels;
|
using Ryujinx.Ava.UI.ViewModels;
|
||||||
using Ryujinx.UI.App.Common;
|
using Ryujinx.UI.App.Common;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.Controls
|
namespace Ryujinx.Ava.UI.Controls
|
||||||
{
|
{
|
||||||
@@ -32,5 +35,27 @@ namespace Ryujinx.Ava.UI.Controls
|
|||||||
if (DataContext is MainWindowViewModel viewModel && sender is ListBox { SelectedItem: ApplicationData selected })
|
if (DataContext is MainWindowViewModel viewModel && sender is ListBox { SelectedItem: ApplicationData selected })
|
||||||
viewModel.ListSelectedApplication = selected;
|
viewModel.ListSelectedApplication = selected;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async void IdString_OnClick(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (DataContext is not MainWindowViewModel mwvm)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (sender is not Button { Content: TextBlock idText })
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!RyujinxApp.IsClipboardAvailable(out var clipboard))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var appData = mwvm.Applications.FirstOrDefault(it => it.IdString == idText.Text);
|
||||||
|
if (appData is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
await clipboard.SetTextAsync(appData.IdString);
|
||||||
|
|
||||||
|
NotificationHelper.ShowInformation(
|
||||||
|
"Copied Title ID",
|
||||||
|
$"{appData.Name} ({appData.IdString})");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Media;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ava.UI.Controls
|
||||||
|
{
|
||||||
|
public class MiniVerticalSeparator : Border
|
||||||
|
{
|
||||||
|
public MiniVerticalSeparator()
|
||||||
|
{
|
||||||
|
Width = 2;
|
||||||
|
Height = 12;
|
||||||
|
Margin = new Thickness();
|
||||||
|
BorderBrush = Brushes.Gray;
|
||||||
|
Background = Brushes.Gray;
|
||||||
|
BorderThickness = new Thickness(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,7 +19,7 @@ namespace Ryujinx.Ava.UI.Helpers
|
|||||||
AvaLogger.Sink = new LoggerAdapter();
|
AvaLogger.Sink = new LoggerAdapter();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static RyuLogger.Log? GetLog(AvaLogLevel level)
|
private static RyuLogger.Log? GetLog(AvaLogLevel level, string area)
|
||||||
{
|
{
|
||||||
return level switch
|
return level switch
|
||||||
{
|
{
|
||||||
@@ -27,7 +27,7 @@ namespace Ryujinx.Ava.UI.Helpers
|
|||||||
AvaLogLevel.Debug => RyuLogger.Debug,
|
AvaLogLevel.Debug => RyuLogger.Debug,
|
||||||
AvaLogLevel.Information => RyuLogger.Debug,
|
AvaLogLevel.Information => RyuLogger.Debug,
|
||||||
AvaLogLevel.Warning => RyuLogger.Debug,
|
AvaLogLevel.Warning => RyuLogger.Debug,
|
||||||
AvaLogLevel.Error => RyuLogger.Error,
|
AvaLogLevel.Error => area is "IME" ? RyuLogger.Debug : RyuLogger.Error,
|
||||||
AvaLogLevel.Fatal => RyuLogger.Error,
|
AvaLogLevel.Fatal => RyuLogger.Error,
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(level), level, null),
|
_ => throw new ArgumentOutOfRangeException(nameof(level), level, null),
|
||||||
};
|
};
|
||||||
@@ -35,17 +35,17 @@ namespace Ryujinx.Ava.UI.Helpers
|
|||||||
|
|
||||||
public bool IsEnabled(AvaLogLevel level, string area)
|
public bool IsEnabled(AvaLogLevel level, string area)
|
||||||
{
|
{
|
||||||
return GetLog(level) != null;
|
return GetLog(level, area) != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Log(AvaLogLevel level, string area, object source, string messageTemplate)
|
public void Log(AvaLogLevel level, string area, object source, string messageTemplate)
|
||||||
{
|
{
|
||||||
GetLog(level)?.PrintMsg(RyuLogClass.UI, Format(level, area, messageTemplate, source, null));
|
GetLog(level, area)?.PrintMsg(RyuLogClass.UI, Format(level, area, messageTemplate, source, null));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Log(AvaLogLevel level, string area, object source, string messageTemplate, params object[] propertyValues)
|
public void Log(AvaLogLevel level, string area, object source, string messageTemplate, params object[] propertyValues)
|
||||||
{
|
{
|
||||||
GetLog(level)?.PrintMsg(RyuLogClass.UI, Format(level, area, messageTemplate, source, propertyValues));
|
GetLog(level, area)?.PrintMsg(RyuLogClass.UI, Format(level, area, messageTemplate, source, propertyValues));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string Format(AvaLogLevel level, string area, string template, object source, object[] v)
|
private static string Format(AvaLogLevel level, string area, string template, object source, object[] v)
|
||||||
|
|||||||
@@ -62,9 +62,46 @@ namespace Ryujinx.Ava.UI.Helpers
|
|||||||
_notifications.Add(new Notification(title, text, type, delay, onClick, onClose));
|
_notifications.Add(new Notification(title, text, type, delay, onClick, onClose));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void ShowError(string message)
|
public static void ShowError(string message) =>
|
||||||
{
|
ShowError(
|
||||||
Show(LocaleManager.Instance[LocaleKeys.DialogErrorTitle], $"{LocaleManager.Instance[LocaleKeys.DialogErrorMessage]}\n\n{message}", NotificationType.Error);
|
LocaleManager.Instance[LocaleKeys.DialogErrorTitle],
|
||||||
}
|
$"{LocaleManager.Instance[LocaleKeys.DialogErrorMessage]}\n\n{message}"
|
||||||
|
);
|
||||||
|
|
||||||
|
public static void ShowInformation(string title, string text, bool waitingExit = false, Action onClick = null, Action onClose = null) =>
|
||||||
|
Show(
|
||||||
|
title,
|
||||||
|
text,
|
||||||
|
NotificationType.Information,
|
||||||
|
waitingExit,
|
||||||
|
onClick,
|
||||||
|
onClose);
|
||||||
|
|
||||||
|
public static void ShowSuccess(string title, string text, bool waitingExit = false, Action onClick = null, Action onClose = null) =>
|
||||||
|
Show(
|
||||||
|
title,
|
||||||
|
text,
|
||||||
|
NotificationType.Success,
|
||||||
|
waitingExit,
|
||||||
|
onClick,
|
||||||
|
onClose);
|
||||||
|
|
||||||
|
public static void ShowWarning(string title, string text, bool waitingExit = false, Action onClick = null, Action onClose = null) =>
|
||||||
|
Show(
|
||||||
|
title,
|
||||||
|
text,
|
||||||
|
NotificationType.Warning,
|
||||||
|
waitingExit,
|
||||||
|
onClick,
|
||||||
|
onClose);
|
||||||
|
|
||||||
|
public static void ShowError(string title, string text, bool waitingExit = false, Action onClick = null, Action onClose = null) =>
|
||||||
|
Show(
|
||||||
|
title,
|
||||||
|
text,
|
||||||
|
NotificationType.Error,
|
||||||
|
waitingExit,
|
||||||
|
onClick,
|
||||||
|
onClose);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Data;
|
using Avalonia.Data;
|
||||||
using Avalonia.Data.Converters;
|
using Avalonia.Data.Converters;
|
||||||
|
using Gommon;
|
||||||
using Ryujinx.Ava.Common.Locale;
|
using Ryujinx.Ava.Common.Locale;
|
||||||
using Ryujinx.UI.Common.Models;
|
using Ryujinx.UI.Common.Models;
|
||||||
using System;
|
using System;
|
||||||
@@ -32,11 +33,11 @@ namespace Ryujinx.Ava.UI.Helpers
|
|||||||
|
|
||||||
if (app.CurrentSavingsB < app.PotentialSavingsB)
|
if (app.CurrentSavingsB < app.PotentialSavingsB)
|
||||||
{
|
{
|
||||||
return LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.TitleXCICanSaveLabel, (app.PotentialSavingsB - app.CurrentSavingsB) / _bytesPerMB);
|
return LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.TitleXCICanSaveLabel, ((app.PotentialSavingsB - app.CurrentSavingsB) / _bytesPerMB).CoerceAtLeast(0));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.TitleXCISavingLabel, app.CurrentSavingsB / _bytesPerMB);
|
return LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.TitleXCISavingLabel, (app.CurrentSavingsB / _bytesPerMB).CoerceAtLeast(0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using Gommon;
|
||||||
using LibHac.Fs;
|
using LibHac.Fs;
|
||||||
using LibHac.Ncm;
|
using LibHac.Ncm;
|
||||||
using Ryujinx.Ava.UI.ViewModels;
|
using Ryujinx.Ava.UI.ViewModels;
|
||||||
@@ -47,7 +48,7 @@ namespace Ryujinx.Ava.UI.Models
|
|||||||
TitleId = info.ProgramId;
|
TitleId = info.ProgramId;
|
||||||
UserId = info.UserId;
|
UserId = info.UserId;
|
||||||
|
|
||||||
var appData = MainWindow.MainWindowViewModel.Applications.FirstOrDefault(x => x.IdString.Equals(TitleIdString, StringComparison.OrdinalIgnoreCase));
|
var appData = RyujinxApp.MainWindow.ViewModel.Applications.FirstOrDefault(x => x.IdString.EqualsIgnoreCase(TitleIdString));
|
||||||
|
|
||||||
InGameList = appData != null;
|
InGameList = appData != null;
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
|
using Gommon;
|
||||||
|
using Ryujinx.Common;
|
||||||
using Ryujinx.Common.Configuration;
|
using Ryujinx.Common.Configuration;
|
||||||
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.UI.Common.Configuration;
|
using Ryujinx.UI.Common.Configuration;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.Renderer
|
namespace Ryujinx.Ava.UI.Renderer
|
||||||
{
|
{
|
||||||
@@ -21,13 +25,53 @@ namespace Ryujinx.Ava.UI.Renderer
|
|||||||
{
|
{
|
||||||
GraphicsBackend.OpenGl => new EmbeddedWindowOpenGL(),
|
GraphicsBackend.OpenGl => new EmbeddedWindowOpenGL(),
|
||||||
GraphicsBackend.Metal => new EmbeddedWindowMetal(),
|
GraphicsBackend.Metal => new EmbeddedWindowMetal(),
|
||||||
GraphicsBackend.Vulkan => new EmbeddedWindowVulkan(),
|
GraphicsBackend.Vulkan or GraphicsBackend.Auto => new EmbeddedWindowVulkan(),
|
||||||
_ => throw new NotSupportedException()
|
_ => throw new NotSupportedException()
|
||||||
};
|
};
|
||||||
|
|
||||||
Initialize();
|
Initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public GraphicsBackend Backend =>
|
||||||
|
EmbeddedWindow switch
|
||||||
|
{
|
||||||
|
EmbeddedWindowVulkan => GraphicsBackend.Vulkan,
|
||||||
|
EmbeddedWindowOpenGL => GraphicsBackend.OpenGl,
|
||||||
|
EmbeddedWindowMetal => GraphicsBackend.Metal,
|
||||||
|
_ => throw new NotImplementedException()
|
||||||
|
};
|
||||||
|
|
||||||
|
public RendererHost(string titleId)
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
|
||||||
|
switch (TitleIDs.SelectGraphicsBackend(titleId, ConfigurationState.Instance.Graphics.GraphicsBackend))
|
||||||
|
{
|
||||||
|
case GraphicsBackend.OpenGl:
|
||||||
|
EmbeddedWindow = new EmbeddedWindowOpenGL();
|
||||||
|
break;
|
||||||
|
case GraphicsBackend.Metal:
|
||||||
|
EmbeddedWindow = new EmbeddedWindowMetal();
|
||||||
|
break;
|
||||||
|
case GraphicsBackend.Vulkan:
|
||||||
|
EmbeddedWindow = new EmbeddedWindowVulkan();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
string backendText = EmbeddedWindow switch
|
||||||
|
{
|
||||||
|
EmbeddedWindowVulkan => "Vulkan",
|
||||||
|
EmbeddedWindowOpenGL => "OpenGL",
|
||||||
|
EmbeddedWindowMetal => "Metal",
|
||||||
|
_ => throw new NotImplementedException()
|
||||||
|
};
|
||||||
|
|
||||||
|
Logger.Info?.PrintMsg(LogClass.Gpu, $"Backend ({ConfigurationState.Instance.Graphics.GraphicsBackend.Value}): {backendText}");
|
||||||
|
|
||||||
|
Initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private void Initialize()
|
private void Initialize()
|
||||||
{
|
{
|
||||||
EmbeddedWindow.WindowCreated += CurrentWindow_WindowCreated;
|
EmbeddedWindow.WindowCreated += CurrentWindow_WindowCreated;
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
public AboutWindowViewModel()
|
public AboutWindowViewModel()
|
||||||
{
|
{
|
||||||
Version = App.FullAppName + "\n" + Program.Version;
|
Version = RyujinxApp.FullAppName + "\n" + Program.Version;
|
||||||
UpdateLogoTheme(ConfigurationState.Instance.UI.BaseStyle.Value);
|
UpdateLogoTheme(ConfigurationState.Instance.UI.BaseStyle.Value);
|
||||||
|
|
||||||
ThemeManager.ThemeChanged += ThemeManager_ThemeChanged;
|
ThemeManager.ThemeChanged += ThemeManager_ThemeChanged;
|
||||||
@@ -64,7 +64,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
private void UpdateLogoTheme(string theme)
|
private void UpdateLogoTheme(string theme)
|
||||||
{
|
{
|
||||||
bool isDarkTheme = theme == "Dark" || (theme == "Auto" && App.DetectSystemTheme() == ThemeVariant.Dark);
|
bool isDarkTheme = theme == "Dark" || (theme == "Auto" && RyujinxApp.DetectSystemTheme() == ThemeVariant.Dark);
|
||||||
|
|
||||||
string basePath = "resm:Ryujinx.UI.Common.Resources.";
|
string basePath = "resm:Ryujinx.UI.Common.Resources.";
|
||||||
string themeSuffix = isDarkTheme ? "Dark.png" : "Light.png";
|
string themeSuffix = isDarkTheme ? "Dark.png" : "Light.png";
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
@@ -11,5 +12,13 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
{
|
{
|
||||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void OnPropertiesChanged(params ReadOnlySpan<string> propertyNames)
|
||||||
|
{
|
||||||
|
foreach (var propertyName in propertyNames)
|
||||||
|
{
|
||||||
|
OnPropertyChanged(propertyName);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ using Avalonia.Threading;
|
|||||||
using DynamicData;
|
using DynamicData;
|
||||||
using DynamicData.Binding;
|
using DynamicData.Binding;
|
||||||
using FluentAvalonia.UI.Controls;
|
using FluentAvalonia.UI.Controls;
|
||||||
|
using Gommon;
|
||||||
using LibHac.Common;
|
using LibHac.Common;
|
||||||
using LibHac.Ns;
|
using LibHac.Ns;
|
||||||
using Ryujinx.Ava.Common;
|
using Ryujinx.Ava.Common;
|
||||||
@@ -109,13 +110,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
private bool _areMimeTypesRegistered = FileAssociationHelper.AreMimeTypesRegistered;
|
private bool _areMimeTypesRegistered = FileAssociationHelper.AreMimeTypesRegistered;
|
||||||
private bool _canUpdate = true;
|
private bool _canUpdate = true;
|
||||||
private Cursor _cursor;
|
|
||||||
private string _title;
|
|
||||||
private ApplicationData _currentApplicationData;
|
private ApplicationData _currentApplicationData;
|
||||||
private readonly AutoResetEvent _rendererWaitEvent;
|
private readonly AutoResetEvent _rendererWaitEvent;
|
||||||
private WindowState _windowState;
|
|
||||||
private double _windowWidth;
|
|
||||||
private double _windowHeight;
|
|
||||||
private int _customVSyncInterval;
|
private int _customVSyncInterval;
|
||||||
private int _customVSyncIntervalPercentageProxy;
|
private int _customVSyncIntervalPercentageProxy;
|
||||||
|
|
||||||
@@ -124,14 +120,15 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
public ApplicationData ListSelectedApplication;
|
public ApplicationData ListSelectedApplication;
|
||||||
public ApplicationData GridSelectedApplication;
|
public ApplicationData GridSelectedApplication;
|
||||||
|
|
||||||
public IEnumerable<LdnGameData> LastLdnGameData;
|
// Key is Title ID
|
||||||
|
public SafeDictionary<string, LdnGameDataArray> LdnData = [];
|
||||||
|
|
||||||
// The UI specifically uses a thicker bordered variant of the icon to avoid crunching out the border at lower resolutions.
|
// The UI specifically uses a thicker bordered variant of the icon to avoid crunching out the border at lower resolutions.
|
||||||
// For an example of this, download canary 1.2.95, then open the settings menu, and look at the icon in the top-left.
|
// For an example of this, download canary 1.2.95, then open the settings menu, and look at the icon in the top-left.
|
||||||
// The border gets reduced to colored pixels in the 4 corners.
|
// The border gets reduced to colored pixels in the 4 corners.
|
||||||
public static readonly Bitmap IconBitmap =
|
public static readonly Bitmap IconBitmap =
|
||||||
new(Assembly.GetAssembly(typeof(ConfigurationState))!.GetManifestResourceStream("Ryujinx.UI.Common.Resources.Logo_Thiccjinx.png")!);
|
new(Assembly.GetAssembly(typeof(ConfigurationState))!.GetManifestResourceStream("Ryujinx.UI.Common.Resources.Logo_Ryujinx_AntiAlias.png")!);
|
||||||
|
|
||||||
public MainWindow Window { get; init; }
|
public MainWindow Window { get; init; }
|
||||||
|
|
||||||
@@ -216,7 +213,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
public bool CanUpdate
|
public bool CanUpdate
|
||||||
{
|
{
|
||||||
get => _canUpdate && EnableNonGameRunningControls && Updater.CanUpdate(false);
|
get => _canUpdate && EnableNonGameRunningControls && Updater.CanUpdate();
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
_canUpdate = value;
|
_canUpdate = value;
|
||||||
@@ -226,12 +223,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
public Cursor Cursor
|
public Cursor Cursor
|
||||||
{
|
{
|
||||||
get => _cursor;
|
get => Window.Cursor;
|
||||||
set
|
set => Window.Cursor = value;
|
||||||
{
|
|
||||||
_cursor = value;
|
|
||||||
OnPropertyChanged();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReadOnlyObservableCollection<ApplicationData> AppsObservableList
|
public ReadOnlyObservableCollection<ApplicationData> AppsObservableList
|
||||||
@@ -813,35 +806,23 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
public WindowState WindowState
|
public WindowState WindowState
|
||||||
{
|
{
|
||||||
get => _windowState;
|
get => Window.WindowState;
|
||||||
internal set
|
internal set
|
||||||
{
|
{
|
||||||
_windowState = value;
|
Window.WindowState = value;
|
||||||
|
|
||||||
OnPropertyChanged();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public double WindowWidth
|
public double WindowWidth
|
||||||
{
|
{
|
||||||
get => _windowWidth;
|
get => Window.Width;
|
||||||
set
|
set => Window.Width = value;
|
||||||
{
|
|
||||||
_windowWidth = value;
|
|
||||||
|
|
||||||
OnPropertyChanged();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public double WindowHeight
|
public double WindowHeight
|
||||||
{
|
{
|
||||||
get => _windowHeight;
|
get => Window.Height;
|
||||||
set
|
set => Window.Height = value;
|
||||||
{
|
|
||||||
_windowHeight = value;
|
|
||||||
|
|
||||||
OnPropertyChanged();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsGrid => Glyph == Glyph.Grid;
|
public bool IsGrid => Glyph == Glyph.Grid;
|
||||||
@@ -889,11 +870,11 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
public string Title
|
public string Title
|
||||||
{
|
{
|
||||||
get => _title;
|
get => Window.Title;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
_title = value;
|
Window.Title = value;
|
||||||
|
|
||||||
OnPropertyChanged();
|
OnPropertyChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1938,7 +1919,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
PrepareLoadScreen();
|
PrepareLoadScreen();
|
||||||
|
|
||||||
RendererHostControl = new RendererHost();
|
RendererHostControl = new RendererHost(application.Id.ToString("X16"));
|
||||||
|
|
||||||
AppHost = new AppHost(
|
AppHost = new AppHost(
|
||||||
RendererHostControl,
|
RendererHostControl,
|
||||||
@@ -2051,7 +2032,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
Dispatcher.UIThread.InvokeAsync(() =>
|
Dispatcher.UIThread.InvokeAsync(() =>
|
||||||
{
|
{
|
||||||
Title = App.FormatTitle();
|
Title = RyujinxApp.FormatTitle();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -71,8 +71,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
{
|
{
|
||||||
_resolutionScale = value;
|
_resolutionScale = value;
|
||||||
|
|
||||||
OnPropertyChanged(nameof(CustomResolutionScale));
|
OnPropertiesChanged(nameof(CustomResolutionScale), nameof(IsCustomResolutionScaleActive));
|
||||||
OnPropertyChanged(nameof(IsCustomResolutionScaleActive));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,11 +119,9 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsMetalAvailable => OperatingSystem.IsMacOS();
|
|
||||||
|
|
||||||
public bool IsOpenGLAvailable => !OperatingSystem.IsMacOS();
|
public bool IsOpenGLAvailable => !OperatingSystem.IsMacOS();
|
||||||
|
|
||||||
public bool IsHypervisorAvailable => OperatingSystem.IsMacOS() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64;
|
public bool IsAppleSiliconMac => OperatingSystem.IsMacOS() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64;
|
||||||
|
|
||||||
public bool GameDirectoryChanged
|
public bool GameDirectoryChanged
|
||||||
{
|
{
|
||||||
@@ -183,8 +180,9 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
int newInterval = (int)((value / 100f) * 60);
|
int newInterval = (int)((value / 100f) * 60);
|
||||||
_customVSyncInterval = newInterval;
|
_customVSyncInterval = newInterval;
|
||||||
_customVSyncIntervalPercentageProxy = value;
|
_customVSyncIntervalPercentageProxy = value;
|
||||||
OnPropertyChanged((nameof(CustomVSyncInterval)));
|
OnPropertiesChanged(
|
||||||
OnPropertyChanged((nameof(CustomVSyncIntervalPercentageText)));
|
nameof(CustomVSyncInterval),
|
||||||
|
nameof(CustomVSyncIntervalPercentageText));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,7 +190,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
string text = CustomVSyncIntervalPercentageProxy.ToString() + "%";
|
string text = CustomVSyncIntervalPercentageProxy + "%";
|
||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -223,8 +221,9 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
_customVSyncInterval = value;
|
_customVSyncInterval = value;
|
||||||
int newPercent = (int)((value / 60f) * 100);
|
int newPercent = (int)((value / 60f) * 100);
|
||||||
_customVSyncIntervalPercentageProxy = newPercent;
|
_customVSyncIntervalPercentageProxy = newPercent;
|
||||||
OnPropertyChanged(nameof(CustomVSyncIntervalPercentageProxy));
|
OnPropertiesChanged(
|
||||||
OnPropertyChanged(nameof(CustomVSyncIntervalPercentageText));
|
nameof(CustomVSyncIntervalPercentageProxy),
|
||||||
|
nameof(CustomVSyncIntervalPercentageText));
|
||||||
OnPropertyChanged();
|
OnPropertyChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -254,7 +253,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
public bool IsCustomResolutionScaleActive => _resolutionScale == 4;
|
public bool IsCustomResolutionScaleActive => _resolutionScale == 4;
|
||||||
public bool IsScalingFilterActive => _scalingFilter == (int)Ryujinx.Common.Configuration.ScalingFilter.Fsr;
|
public bool IsScalingFilterActive => _scalingFilter == (int)Ryujinx.Common.Configuration.ScalingFilter.Fsr;
|
||||||
|
|
||||||
public bool IsVulkanSelected => GraphicsBackendIndex == 0;
|
public bool IsVulkanSelected =>
|
||||||
|
GraphicsBackendIndex == 1 || (GraphicsBackendIndex == 0 && !OperatingSystem.IsMacOS());
|
||||||
public bool UseHypervisor { get; set; }
|
public bool UseHypervisor { get; set; }
|
||||||
public bool DisableP2P { get; set; }
|
public bool DisableP2P { get; set; }
|
||||||
|
|
||||||
@@ -434,7 +434,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
if (devices.Length == 0)
|
if (devices.Length == 0)
|
||||||
{
|
{
|
||||||
IsVulkanAvailable = false;
|
IsVulkanAvailable = false;
|
||||||
GraphicsBackendIndex = 1;
|
GraphicsBackendIndex = 2;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -750,7 +750,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
config.ToFileFormat().SaveConfig(Program.ConfigurationPath);
|
config.ToFileFormat().SaveConfig(Program.ConfigurationPath);
|
||||||
|
|
||||||
MainWindow.UpdateGraphicsConfig();
|
MainWindow.UpdateGraphicsConfig();
|
||||||
MainWindow.MainWindowViewModel.VSyncModeSettingChanged();
|
RyujinxApp.MainWindow.ViewModel.VSyncModeSettingChanged();
|
||||||
|
|
||||||
SaveSettingsEvent?.Invoke();
|
SaveSettingsEvent?.Invoke();
|
||||||
|
|
||||||
|
|||||||
@@ -91,39 +91,42 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
private void SortingChanged()
|
private void SortingChanged()
|
||||||
{
|
{
|
||||||
OnPropertyChanged(nameof(IsSortedByName));
|
OnPropertiesChanged(
|
||||||
OnPropertyChanged(nameof(IsSortedBySaved));
|
nameof(IsSortedByName),
|
||||||
OnPropertyChanged(nameof(SortingAscending));
|
nameof(IsSortedBySaved),
|
||||||
OnPropertyChanged(nameof(SortingField));
|
nameof(SortingAscending),
|
||||||
OnPropertyChanged(nameof(SortingFieldName));
|
nameof(SortingField),
|
||||||
|
nameof(SortingFieldName));
|
||||||
|
|
||||||
SortAndFilter();
|
SortAndFilter();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DisplayedChanged()
|
private void DisplayedChanged()
|
||||||
{
|
{
|
||||||
OnPropertyChanged(nameof(Status));
|
OnPropertiesChanged(nameof(Status), nameof(DisplayedXCIFiles), nameof(SelectedDisplayedXCIFiles));
|
||||||
OnPropertyChanged(nameof(DisplayedXCIFiles));
|
|
||||||
OnPropertyChanged(nameof(SelectedDisplayedXCIFiles));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ApplicationsChanged()
|
private void ApplicationsChanged()
|
||||||
{
|
{
|
||||||
OnPropertyChanged(nameof(AllXCIFiles));
|
OnPropertiesChanged(
|
||||||
OnPropertyChanged(nameof(Status));
|
nameof(AllXCIFiles),
|
||||||
OnPropertyChanged(nameof(PotentialSavings));
|
nameof(Status),
|
||||||
OnPropertyChanged(nameof(ActualSavings));
|
nameof(PotentialSavings),
|
||||||
OnPropertyChanged(nameof(CanTrim));
|
nameof(ActualSavings),
|
||||||
OnPropertyChanged(nameof(CanUntrim));
|
nameof(CanTrim),
|
||||||
|
nameof(CanUntrim));
|
||||||
|
|
||||||
DisplayedChanged();
|
DisplayedChanged();
|
||||||
SortAndFilter();
|
SortAndFilter();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SelectionChanged(bool displayedChanged = true)
|
private void SelectionChanged(bool displayedChanged = true)
|
||||||
{
|
{
|
||||||
OnPropertyChanged(nameof(Status));
|
OnPropertiesChanged(
|
||||||
OnPropertyChanged(nameof(CanTrim));
|
nameof(Status),
|
||||||
OnPropertyChanged(nameof(CanUntrim));
|
nameof(CanTrim),
|
||||||
OnPropertyChanged(nameof(SelectedXCIFiles));
|
nameof(CanUntrim),
|
||||||
|
nameof(SelectedXCIFiles));
|
||||||
|
|
||||||
if (displayedChanged)
|
if (displayedChanged)
|
||||||
OnPropertyChanged(nameof(SelectedDisplayedXCIFiles));
|
OnPropertyChanged(nameof(SelectedDisplayedXCIFiles));
|
||||||
@@ -131,11 +134,12 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
private void ProcessingChanged()
|
private void ProcessingChanged()
|
||||||
{
|
{
|
||||||
OnPropertyChanged(nameof(Processing));
|
OnPropertiesChanged(
|
||||||
OnPropertyChanged(nameof(Cancel));
|
nameof(Processing),
|
||||||
OnPropertyChanged(nameof(Status));
|
nameof(Cancel),
|
||||||
OnPropertyChanged(nameof(CanTrim));
|
nameof(Status),
|
||||||
OnPropertyChanged(nameof(CanUntrim));
|
nameof(CanTrim),
|
||||||
|
nameof(CanUntrim));
|
||||||
}
|
}
|
||||||
|
|
||||||
private IEnumerable<XCITrimmerFileModel> GetSelectedDisplayedXCIFiles()
|
private IEnumerable<XCITrimmerFileModel> GetSelectedDisplayedXCIFiles()
|
||||||
@@ -360,7 +364,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
value = _processingApplication.Value with { PercentageProgress = null };
|
value = _processingApplication.Value with { PercentageProgress = null };
|
||||||
|
|
||||||
if (value.HasValue)
|
if (value.HasValue)
|
||||||
_displayedXCIFiles.ReplaceWith(value.Value);
|
_displayedXCIFiles.ReplaceWith(value);
|
||||||
|
|
||||||
_processingApplication = value;
|
_processingApplication = value;
|
||||||
OnPropertyChanged();
|
OnPropertyChanged();
|
||||||
|
|||||||
@@ -17,8 +17,7 @@
|
|||||||
Margin="7, 0"
|
Margin="7, 0"
|
||||||
Height="25"
|
Height="25"
|
||||||
Width="25"
|
Width="25"
|
||||||
ToolTip.Tip="{Binding Title}"
|
ToolTip.Tip="{Binding Title}" />
|
||||||
Source="resm:Ryujinx.UI.Common.Resources.Logo_Thiccjinx.png?assembly=Ryujinx.UI.Common" />
|
|
||||||
<Menu
|
<Menu
|
||||||
Name="Menu"
|
Name="Menu"
|
||||||
Height="32"
|
Height="32"
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ using Avalonia.Controls;
|
|||||||
using Avalonia.Interactivity;
|
using Avalonia.Interactivity;
|
||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
using Gommon;
|
using Gommon;
|
||||||
using LibHac.Ncm;
|
|
||||||
using LibHac.Tools.FsSystem.NcaUtils;
|
|
||||||
using Ryujinx.Ava.Common.Locale;
|
using Ryujinx.Ava.Common.Locale;
|
||||||
using Ryujinx.Ava.UI.Helpers;
|
using Ryujinx.Ava.UI.Helpers;
|
||||||
using Ryujinx.Ava.UI.ViewModels;
|
using Ryujinx.Ava.UI.ViewModels;
|
||||||
@@ -12,8 +10,6 @@ using Ryujinx.Ava.UI.Windows;
|
|||||||
using Ryujinx.Common;
|
using Ryujinx.Common;
|
||||||
using Ryujinx.Common.Utilities;
|
using Ryujinx.Common.Utilities;
|
||||||
using Ryujinx.HLE.HOS.Services.Nfc.AmiiboDecryption;
|
using Ryujinx.HLE.HOS.Services.Nfc.AmiiboDecryption;
|
||||||
using Ryujinx.HLE;
|
|
||||||
using Ryujinx.UI.App.Common;
|
|
||||||
using Ryujinx.UI.Common;
|
using Ryujinx.UI.Common;
|
||||||
using Ryujinx.UI.Common.Configuration;
|
using Ryujinx.UI.Common.Configuration;
|
||||||
using Ryujinx.UI.Common.Helper;
|
using Ryujinx.UI.Common.Helper;
|
||||||
@@ -33,6 +29,7 @@ namespace Ryujinx.Ava.UI.Views.Main
|
|||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
RyuLogo.IsVisible = !ConfigurationState.Instance.ShowTitleBar;
|
RyuLogo.IsVisible = !ConfigurationState.Instance.ShowTitleBar;
|
||||||
|
RyuLogo.Source = MainWindowViewModel.IconBitmap;
|
||||||
|
|
||||||
ToggleFileTypesMenuItem.ItemsSource = GenerateToggleFileTypeItems();
|
ToggleFileTypesMenuItem.ItemsSource = GenerateToggleFileTypeItems();
|
||||||
ChangeLanguageMenuItem.ItemsSource = GenerateLanguageMenuItems();
|
ChangeLanguageMenuItem.ItemsSource = GenerateLanguageMenuItems();
|
||||||
@@ -124,26 +121,13 @@ namespace Ryujinx.Ava.UI.Views.Main
|
|||||||
ViewModel.LoadConfigurableHotKeys();
|
ViewModel.LoadConfigurableHotKeys();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static readonly AppletMetadata MiiApplet = new("miiEdit", 0x0100000000001009);
|
||||||
|
|
||||||
public async void OpenMiiApplet(object sender, RoutedEventArgs e)
|
public async void OpenMiiApplet(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
const string AppletName = "miiEdit";
|
if (MiiApplet.CanStart(ViewModel.ContentManager, out var appData, out var nacpData))
|
||||||
const ulong AppletProgramId = 0x0100000000001009;
|
|
||||||
const string AppletVersion = "1.0.0";
|
|
||||||
|
|
||||||
string contentPath = ViewModel.ContentManager.GetInstalledContentPath(AppletProgramId, StorageId.BuiltInSystem, NcaContentType.Program);
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(contentPath))
|
|
||||||
{
|
{
|
||||||
ApplicationData applicationData = new()
|
await ViewModel.LoadApplication(appData, ViewModel.IsFullScreen || ViewModel.StartGamesInFullscreen, nacpData);
|
||||||
{
|
|
||||||
Name = AppletName,
|
|
||||||
Id = AppletProgramId,
|
|
||||||
Path = contentPath
|
|
||||||
};
|
|
||||||
|
|
||||||
var nacpData = StructHelpers.CreateCustomNacpData(AppletName, AppletVersion);
|
|
||||||
|
|
||||||
await ViewModel.LoadApplication(applicationData, ViewModel.IsFullScreen || ViewModel.StartGamesInFullscreen, nacpData);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -132,14 +132,7 @@
|
|||||||
</Flyout>
|
</Flyout>
|
||||||
</Button.Flyout>
|
</Button.Flyout>
|
||||||
</Button>
|
</Button>
|
||||||
<Border
|
<controls:MiniVerticalSeparator IsVisible="{Binding !ShowLoadProgress}" />
|
||||||
Width="2"
|
|
||||||
Height="12"
|
|
||||||
Margin="0"
|
|
||||||
BorderBrush="Gray"
|
|
||||||
Background="Gray"
|
|
||||||
BorderThickness="1"
|
|
||||||
IsVisible="{Binding !ShowLoadProgress}" />
|
|
||||||
<TextBlock
|
<TextBlock
|
||||||
Name="DockedStatus"
|
Name="DockedStatus"
|
||||||
Margin="5,0,5,0"
|
Margin="5,0,5,0"
|
||||||
@@ -149,14 +142,7 @@
|
|||||||
PointerReleased="DockedStatus_PointerReleased"
|
PointerReleased="DockedStatus_PointerReleased"
|
||||||
Text="{Binding DockedStatusText}"
|
Text="{Binding DockedStatusText}"
|
||||||
TextAlignment="Start" />
|
TextAlignment="Start" />
|
||||||
<Border
|
<controls:MiniVerticalSeparator IsVisible="{Binding !ShowLoadProgress}" />
|
||||||
Width="2"
|
|
||||||
Height="12"
|
|
||||||
Margin="0"
|
|
||||||
BorderBrush="Gray"
|
|
||||||
Background="Gray"
|
|
||||||
BorderThickness="1"
|
|
||||||
IsVisible="{Binding !ShowLoadProgress}" />
|
|
||||||
<SplitButton
|
<SplitButton
|
||||||
Name="AspectRatioStatus"
|
Name="AspectRatioStatus"
|
||||||
Padding="5,0,5,0"
|
Padding="5,0,5,0"
|
||||||
@@ -203,14 +189,7 @@
|
|||||||
</MenuFlyout>
|
</MenuFlyout>
|
||||||
</SplitButton.Flyout>
|
</SplitButton.Flyout>
|
||||||
</SplitButton>
|
</SplitButton>
|
||||||
<Border
|
<controls:MiniVerticalSeparator IsVisible="{Binding !ShowLoadProgress}" />
|
||||||
Width="2"
|
|
||||||
Height="12"
|
|
||||||
Margin="0"
|
|
||||||
BorderBrush="Gray"
|
|
||||||
Background="Gray"
|
|
||||||
BorderThickness="1"
|
|
||||||
IsVisible="{Binding !ShowLoadProgress}" />
|
|
||||||
<ToggleSplitButton
|
<ToggleSplitButton
|
||||||
Name="VolumeStatus"
|
Name="VolumeStatus"
|
||||||
Padding="5,0,5,0"
|
Padding="5,0,5,0"
|
||||||
@@ -254,14 +233,7 @@
|
|||||||
</Flyout>
|
</Flyout>
|
||||||
</ToggleSplitButton.Flyout>
|
</ToggleSplitButton.Flyout>
|
||||||
</ToggleSplitButton>
|
</ToggleSplitButton>
|
||||||
<Border
|
<controls:MiniVerticalSeparator IsVisible="{Binding !ShowLoadProgress}" />
|
||||||
Width="2"
|
|
||||||
Height="12"
|
|
||||||
Margin="0"
|
|
||||||
BorderBrush="Gray"
|
|
||||||
Background="Gray"
|
|
||||||
BorderThickness="1"
|
|
||||||
IsVisible="{Binding !ShowLoadProgress}" />
|
|
||||||
<TextBlock
|
<TextBlock
|
||||||
Margin="5,0,5,0"
|
Margin="5,0,5,0"
|
||||||
HorizontalAlignment="Left"
|
HorizontalAlignment="Left"
|
||||||
@@ -269,14 +241,7 @@
|
|||||||
IsVisible="{Binding !ShowLoadProgress}"
|
IsVisible="{Binding !ShowLoadProgress}"
|
||||||
Text="{Binding GameStatusText}"
|
Text="{Binding GameStatusText}"
|
||||||
TextAlignment="Start" />
|
TextAlignment="Start" />
|
||||||
<Border
|
<controls:MiniVerticalSeparator IsVisible="{Binding !ShowLoadProgress}" />
|
||||||
Width="2"
|
|
||||||
Height="12"
|
|
||||||
Margin="0"
|
|
||||||
BorderBrush="Gray"
|
|
||||||
Background="Gray"
|
|
||||||
BorderThickness="1"
|
|
||||||
IsVisible="{Binding !ShowLoadProgress}" />
|
|
||||||
<TextBlock
|
<TextBlock
|
||||||
Margin="5,0,5,0"
|
Margin="5,0,5,0"
|
||||||
HorizontalAlignment="Left"
|
HorizontalAlignment="Left"
|
||||||
@@ -298,13 +263,7 @@
|
|||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
IsVisible="{Binding ShowShaderCompilationHint}"
|
IsVisible="{Binding ShowShaderCompilationHint}"
|
||||||
Text="{Binding ShaderCountText}" />
|
Text="{Binding ShaderCountText}" />
|
||||||
<Border
|
<controls:MiniVerticalSeparator IsVisible="{Binding ShowShaderCompilationHint}" />
|
||||||
Width="2"
|
|
||||||
Height="12"
|
|
||||||
Margin="0"
|
|
||||||
BorderBrush="Gray"
|
|
||||||
BorderThickness="1"
|
|
||||||
IsVisible="{Binding ShowShaderCompilationHint}" />
|
|
||||||
<TextBlock
|
<TextBlock
|
||||||
Margin="5,0,5,0"
|
Margin="5,0,5,0"
|
||||||
HorizontalAlignment="Left"
|
HorizontalAlignment="Left"
|
||||||
@@ -312,14 +271,7 @@
|
|||||||
IsVisible="{Binding !ShowLoadProgress}"
|
IsVisible="{Binding !ShowLoadProgress}"
|
||||||
Text="{Binding BackendText}"
|
Text="{Binding BackendText}"
|
||||||
TextAlignment="Start" />
|
TextAlignment="Start" />
|
||||||
<Border
|
<controls:MiniVerticalSeparator IsVisible="{Binding !ShowLoadProgress}" />
|
||||||
Width="2"
|
|
||||||
Height="12"
|
|
||||||
Margin="0"
|
|
||||||
BorderBrush="Gray"
|
|
||||||
Background="Gray"
|
|
||||||
BorderThickness="1"
|
|
||||||
IsVisible="{Binding !ShowLoadProgress}" />
|
|
||||||
<TextBlock
|
<TextBlock
|
||||||
Margin="5,0,0,0"
|
Margin="5,0,0,0"
|
||||||
HorizontalAlignment="Left"
|
HorizontalAlignment="Left"
|
||||||
@@ -334,13 +286,7 @@
|
|||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
IsVisible="{Binding ShowFirmwareStatus}"
|
IsVisible="{Binding ShowFirmwareStatus}"
|
||||||
Orientation="Horizontal">
|
Orientation="Horizontal">
|
||||||
<Border
|
<controls:MiniVerticalSeparator IsVisible="{Binding IsGameRunning}" />
|
||||||
Width="2"
|
|
||||||
Height="12"
|
|
||||||
Margin="0"
|
|
||||||
BorderBrush="Gray"
|
|
||||||
BorderThickness="1"
|
|
||||||
IsVisible="{Binding IsGameRunning}" />
|
|
||||||
<TextBlock
|
<TextBlock
|
||||||
Name="FirmwareStatus"
|
Name="FirmwareStatus"
|
||||||
Margin="5, 0, 0, 0"
|
Margin="5, 0, 0, 0"
|
||||||
|
|||||||
@@ -62,12 +62,7 @@ namespace Ryujinx.Ava.UI.Views.Main
|
|||||||
// Change the volume by 5% at a time
|
// Change the volume by 5% at a time
|
||||||
float newValue = Window.ViewModel.Volume + (float)e.Delta.Y * 0.05f;
|
float newValue = Window.ViewModel.Volume + (float)e.Delta.Y * 0.05f;
|
||||||
|
|
||||||
Window.ViewModel.Volume = newValue switch
|
Window.ViewModel.Volume = Math.Clamp(newValue, 0, 1);
|
||||||
{
|
|
||||||
< 0 => 0,
|
|
||||||
> 1 => 1,
|
|
||||||
_ => newValue,
|
|
||||||
};
|
|
||||||
|
|
||||||
e.Handled = true;
|
e.Handled = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,7 +69,7 @@
|
|||||||
</ComboBox>
|
</ComboBox>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<CheckBox IsChecked="{Binding UseHypervisor}"
|
<CheckBox IsChecked="{Binding UseHypervisor}"
|
||||||
IsVisible="{Binding IsHypervisorAvailable}"
|
IsVisible="{Binding IsAppleSiliconMac}"
|
||||||
ToolTip.Tip="{ext:Locale UseHypervisorTooltip}">
|
ToolTip.Tip="{ext:Locale UseHypervisorTooltip}">
|
||||||
<TextBlock Text="{ext:Locale SettingsTabSystemUseHypervisor}"
|
<TextBlock Text="{ext:Locale SettingsTabSystemUseHypervisor}"
|
||||||
ToolTip.Tip="{ext:Locale UseHypervisorTooltip}" />
|
ToolTip.Tip="{ext:Locale UseHypervisorTooltip}" />
|
||||||
|
|||||||
@@ -33,18 +33,23 @@
|
|||||||
ToolTip.Tip="{ext:Locale SettingsTabGraphicsBackendTooltip}"
|
ToolTip.Tip="{ext:Locale SettingsTabGraphicsBackendTooltip}"
|
||||||
Text="{ext:Locale SettingsTabGraphicsBackend}"
|
Text="{ext:Locale SettingsTabGraphicsBackend}"
|
||||||
Width="250" />
|
Width="250" />
|
||||||
<ComboBox Width="350"
|
<ComboBox
|
||||||
HorizontalContentAlignment="Left"
|
Name="GraphicsBackendSelector"
|
||||||
ToolTip.Tip="{ext:Locale SettingsTabGraphicsBackendTooltip}"
|
Width="350"
|
||||||
SelectedIndex="{Binding GraphicsBackendIndex}">
|
HorizontalContentAlignment="Left"
|
||||||
|
ToolTip.Tip="{ext:Locale SettingsTabGraphicsBackendTooltip}"
|
||||||
|
SelectedIndex="{Binding GraphicsBackendIndex}">
|
||||||
|
<ComboBoxItem IsVisible="{Binding IsVulkanAvailable}" ToolTip.Tip="{ext:Locale SettingsTabGraphicsBackendAutoTooltip}" >
|
||||||
|
<TextBlock Text="{ext:Locale SettingsTabGraphicsBackendAuto}" />
|
||||||
|
</ComboBoxItem>
|
||||||
<ComboBoxItem IsVisible="{Binding IsVulkanAvailable}">
|
<ComboBoxItem IsVisible="{Binding IsVulkanAvailable}">
|
||||||
<TextBlock Text="Vulkan" />
|
<TextBlock Text="Vulkan" />
|
||||||
</ComboBoxItem>
|
</ComboBoxItem>
|
||||||
<ComboBoxItem IsEnabled="{Binding IsOpenGLAvailable}">
|
<ComboBoxItem IsEnabled="{Binding IsOpenGLAvailable}">
|
||||||
<TextBlock Text="OpenGL" />
|
<TextBlock Text="OpenGL" />
|
||||||
</ComboBoxItem>
|
</ComboBoxItem>
|
||||||
<ComboBoxItem IsVisible="{Binding IsMetalAvailable}">
|
<ComboBoxItem IsEnabled="{Binding IsAppleSiliconMac}">
|
||||||
<TextBlock Text="Metal" />
|
<TextBlock Text="Metal (ARM Mac only, Experimental)" />
|
||||||
</ComboBoxItem>
|
</ComboBoxItem>
|
||||||
</ComboBox>
|
</ComboBox>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
|
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
Title = App.FormatTitle(LocaleKeys.Amiibo);
|
Title = RyujinxApp.FormatTitle(LocaleKeys.Amiibo);
|
||||||
}
|
}
|
||||||
|
|
||||||
public AmiiboWindow()
|
public AmiiboWindow()
|
||||||
@@ -27,7 +27,7 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
|
|
||||||
if (Program.PreviewerDetached)
|
if (Program.PreviewerDetached)
|
||||||
{
|
{
|
||||||
Title = App.FormatTitle(LocaleKeys.Amiibo);
|
Title = RyujinxApp.FormatTitle(LocaleKeys.Amiibo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
|
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
Title = App.FormatTitle(LocaleKeys.CheatWindowTitle);
|
Title = RyujinxApp.FormatTitle(LocaleKeys.CheatWindowTitle);
|
||||||
}
|
}
|
||||||
|
|
||||||
public CheatWindow(VirtualFileSystem virtualFileSystem, string titleId, string titleName, string titlePath)
|
public CheatWindow(VirtualFileSystem virtualFileSystem, string titleId, string titleName, string titlePath)
|
||||||
@@ -93,7 +93,7 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
|
|
||||||
DataContext = this;
|
DataContext = this;
|
||||||
|
|
||||||
Title = App.FormatTitle(LocaleKeys.CheatWindowTitle);
|
Title = RyujinxApp.FormatTitle(LocaleKeys.CheatWindowTitle);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Save()
|
public void Save()
|
||||||
|
|||||||
@@ -9,11 +9,6 @@
|
|||||||
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
|
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
|
||||||
xmlns:controls="clr-namespace:Ryujinx.Ava.UI.Controls"
|
xmlns:controls="clr-namespace:Ryujinx.Ava.UI.Controls"
|
||||||
xmlns:main="clr-namespace:Ryujinx.Ava.UI.Views.Main"
|
xmlns:main="clr-namespace:Ryujinx.Ava.UI.Views.Main"
|
||||||
Cursor="{Binding Cursor}"
|
|
||||||
Title="{Binding Title}"
|
|
||||||
WindowState="{Binding WindowState}"
|
|
||||||
Width="{Binding WindowWidth}"
|
|
||||||
Height="{Binding WindowHeight}"
|
|
||||||
MinWidth="800"
|
MinWidth="800"
|
||||||
MinHeight="500"
|
MinHeight="500"
|
||||||
d:DesignHeight="720"
|
d:DesignHeight="720"
|
||||||
|
|||||||
@@ -39,8 +39,6 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
{
|
{
|
||||||
public partial class MainWindow : StyleableAppWindow
|
public partial class MainWindow : StyleableAppWindow
|
||||||
{
|
{
|
||||||
internal static MainWindowViewModel MainWindowViewModel { get; private set; }
|
|
||||||
|
|
||||||
public MainWindowViewModel ViewModel { get; }
|
public MainWindowViewModel ViewModel { get; }
|
||||||
|
|
||||||
internal readonly AvaHostUIHandler UiHandler;
|
internal readonly AvaHostUIHandler UiHandler;
|
||||||
@@ -76,7 +74,7 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
|
|
||||||
public MainWindow()
|
public MainWindow()
|
||||||
{
|
{
|
||||||
DataContext = ViewModel = MainWindowViewModel = new MainWindowViewModel
|
DataContext = ViewModel = new MainWindowViewModel
|
||||||
{
|
{
|
||||||
Window = this
|
Window = this
|
||||||
};
|
};
|
||||||
@@ -86,17 +84,22 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
|
|
||||||
UiHandler = new AvaHostUIHandler(this);
|
UiHandler = new AvaHostUIHandler(this);
|
||||||
|
|
||||||
ViewModel.Title = App.FormatTitle();
|
ViewModel.Title = RyujinxApp.FormatTitle();
|
||||||
|
|
||||||
TitleBar.ExtendsContentIntoTitleBar = !ConfigurationState.Instance.ShowTitleBar;
|
TitleBar.ExtendsContentIntoTitleBar = !ConfigurationState.Instance.ShowTitleBar;
|
||||||
TitleBar.TitleBarHitTestType = (ConfigurationState.Instance.ShowTitleBar) ? TitleBarHitTestType.Simple : TitleBarHitTestType.Complex;
|
TitleBar.TitleBarHitTestType = (ConfigurationState.Instance.ShowTitleBar) ? TitleBarHitTestType.Simple : TitleBarHitTestType.Complex;
|
||||||
|
|
||||||
// Correctly size window when 'TitleBar' is enabled (Nov. 14, 2024)
|
|
||||||
TitleBarHeight = (ConfigurationState.Instance.ShowTitleBar ? TitleBar.Height : 0);
|
|
||||||
|
|
||||||
// NOTE: Height of MenuBar and StatusBar is not usable here, since it would still be 0 at this point.
|
// NOTE: Height of MenuBar and StatusBar is not usable here, since it would still be 0 at this point.
|
||||||
StatusBarHeight = StatusBarView.StatusBar.MinHeight;
|
StatusBarHeight = StatusBarView.StatusBar.MinHeight;
|
||||||
MenuBarHeight = MenuBar.MinHeight;
|
MenuBarHeight = MenuBar.MinHeight;
|
||||||
|
|
||||||
|
TitleBar.Height = MenuBarHeight;
|
||||||
|
|
||||||
|
// Correctly size window when 'TitleBar' is enabled (Nov. 14, 2024)
|
||||||
|
TitleBarHeight = (ConfigurationState.Instance.ShowTitleBar ? TitleBar.Height : 0);
|
||||||
|
|
||||||
|
ApplicationList.DataContext = DataContext;
|
||||||
|
ApplicationGrid.DataContext = DataContext;
|
||||||
|
|
||||||
SetWindowSizePosition();
|
SetWindowSizePosition();
|
||||||
|
|
||||||
@@ -114,7 +117,7 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private static void OnPlatformColorValuesChanged(object sender, PlatformColorValues e)
|
private static void OnPlatformColorValuesChanged(object sender, PlatformColorValues e)
|
||||||
{
|
{
|
||||||
if (Application.Current is App app)
|
if (Application.Current is RyujinxApp app)
|
||||||
app.ApplyConfiguredTheme(ConfigurationState.Instance.UI.BaseStyle);
|
app.ApplyConfiguredTheme(ConfigurationState.Instance.UI.BaseStyle);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,24 +167,28 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
{
|
{
|
||||||
Dispatcher.UIThread.Post(() =>
|
Dispatcher.UIThread.Post(() =>
|
||||||
{
|
{
|
||||||
var ldnGameDataArray = e.LdnData;
|
var ldnGameDataArray = e.LdnData.ToList();
|
||||||
ViewModel.LastLdnGameData = ldnGameDataArray;
|
ViewModel.LdnData.Clear();
|
||||||
foreach (var application in ViewModel.Applications)
|
foreach (var application in ViewModel.Applications)
|
||||||
{
|
{
|
||||||
|
ViewModel.LdnData[application.IdString] = new LdnGameDataArray(
|
||||||
|
ldnGameDataArray,
|
||||||
|
ref application.ControlHolder.Value
|
||||||
|
);
|
||||||
|
|
||||||
UpdateApplicationWithLdnData(application);
|
UpdateApplicationWithLdnData(application);
|
||||||
}
|
}
|
||||||
|
|
||||||
ViewModel.RefreshView();
|
ViewModel.RefreshView();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateApplicationWithLdnData(ApplicationData application)
|
private void UpdateApplicationWithLdnData(ApplicationData application)
|
||||||
{
|
{
|
||||||
if (application.ControlHolder.ByteSpan.Length > 0 && ViewModel.LastLdnGameData != null)
|
if (application.HasControlHolder && ViewModel.LdnData.TryGetValue(application.IdString, out var ldnGameDatas))
|
||||||
{
|
{
|
||||||
IEnumerable<LdnGameData> ldnGameData = ViewModel.LastLdnGameData.Where(game => application.ControlHolder.Value.LocalCommunicationId.Items.Contains(Convert.ToUInt64(game.TitleId, 16)));
|
application.PlayerCount = ldnGameDatas.PlayerCount;
|
||||||
|
application.GameCount = ldnGameDatas.GameCount;
|
||||||
application.PlayerCount = ldnGameData.Sum(game => game.PlayerCount);
|
|
||||||
application.GameCount = ldnGameData.Count();
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
|
|
||||||
public SettingsWindow(VirtualFileSystem virtualFileSystem, ContentManager contentManager)
|
public SettingsWindow(VirtualFileSystem virtualFileSystem, ContentManager contentManager)
|
||||||
{
|
{
|
||||||
Title = App.FormatTitle(LocaleKeys.Settings);
|
Title = RyujinxApp.FormatTitle(LocaleKeys.Settings);
|
||||||
|
|
||||||
DataContext = ViewModel = new SettingsViewModel(virtualFileSystem, contentManager);
|
DataContext = ViewModel = new SettingsViewModel(virtualFileSystem, contentManager);
|
||||||
|
|
||||||
@@ -56,34 +56,34 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
{
|
{
|
||||||
switch (navItem.Tag.ToString())
|
switch (navItem.Tag.ToString())
|
||||||
{
|
{
|
||||||
case "UiPage":
|
case nameof(UiPage):
|
||||||
UiPage.ViewModel = ViewModel;
|
UiPage.ViewModel = ViewModel;
|
||||||
NavPanel.Content = UiPage;
|
NavPanel.Content = UiPage;
|
||||||
break;
|
break;
|
||||||
case "InputPage":
|
case nameof(InputPage):
|
||||||
NavPanel.Content = InputPage;
|
NavPanel.Content = InputPage;
|
||||||
break;
|
break;
|
||||||
case "HotkeysPage":
|
case nameof(HotkeysPage):
|
||||||
NavPanel.Content = HotkeysPage;
|
NavPanel.Content = HotkeysPage;
|
||||||
break;
|
break;
|
||||||
case "SystemPage":
|
case nameof(SystemPage):
|
||||||
SystemPage.ViewModel = ViewModel;
|
SystemPage.ViewModel = ViewModel;
|
||||||
NavPanel.Content = SystemPage;
|
NavPanel.Content = SystemPage;
|
||||||
break;
|
break;
|
||||||
case "CpuPage":
|
case nameof(CpuPage):
|
||||||
NavPanel.Content = CpuPage;
|
NavPanel.Content = CpuPage;
|
||||||
break;
|
break;
|
||||||
case "GraphicsPage":
|
case nameof(GraphicsPage):
|
||||||
NavPanel.Content = GraphicsPage;
|
NavPanel.Content = GraphicsPage;
|
||||||
break;
|
break;
|
||||||
case "AudioPage":
|
case nameof(AudioPage):
|
||||||
NavPanel.Content = AudioPage;
|
NavPanel.Content = AudioPage;
|
||||||
break;
|
break;
|
||||||
case "NetworkPage":
|
case nameof(NetworkPage):
|
||||||
NetworkPage.ViewModel = ViewModel;
|
NetworkPage.ViewModel = ViewModel;
|
||||||
NavPanel.Content = NetworkPage;
|
NavPanel.Content = NetworkPage;
|
||||||
break;
|
break;
|
||||||
case "LoggingPage":
|
case nameof(LoggingPage):
|
||||||
NavPanel.Content = LoggingPage;
|
NavPanel.Content = LoggingPage;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ namespace Ryujinx.Ava
|
|||||||
|
|
||||||
if (!Version.TryParse(Program.Version, out Version currentVersion))
|
if (!Version.TryParse(Program.Version, out Version currentVersion))
|
||||||
{
|
{
|
||||||
Logger.Error?.Print(LogClass.Application, $"Failed to convert the current {App.FullAppName} version!");
|
Logger.Error?.Print(LogClass.Application, $"Failed to convert the current {RyujinxApp.FullAppName} version!");
|
||||||
|
|
||||||
await ContentDialogHelper.CreateWarningDialog(
|
await ContentDialogHelper.CreateWarningDialog(
|
||||||
LocaleManager.Instance[LocaleKeys.DialogUpdaterConvertFailedMessage],
|
LocaleManager.Instance[LocaleKeys.DialogUpdaterConvertFailedMessage],
|
||||||
@@ -159,7 +159,7 @@ namespace Ryujinx.Ava
|
|||||||
|
|
||||||
if (!Version.TryParse(_buildVer, out Version newVersion))
|
if (!Version.TryParse(_buildVer, out Version newVersion))
|
||||||
{
|
{
|
||||||
Logger.Error?.Print(LogClass.Application, $"Failed to convert the received {App.FullAppName} version from GitHub!");
|
Logger.Error?.Print(LogClass.Application, $"Failed to convert the received {RyujinxApp.FullAppName} version from GitHub!");
|
||||||
|
|
||||||
await ContentDialogHelper.CreateWarningDialog(
|
await ContentDialogHelper.CreateWarningDialog(
|
||||||
LocaleManager.Instance[LocaleKeys.DialogUpdaterConvertFailedGithubMessage],
|
LocaleManager.Instance[LocaleKeys.DialogUpdaterConvertFailedGithubMessage],
|
||||||
@@ -266,7 +266,7 @@ namespace Ryujinx.Ava
|
|||||||
SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterDownloading],
|
SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterDownloading],
|
||||||
IconSource = new SymbolIconSource { Symbol = Symbol.Download },
|
IconSource = new SymbolIconSource { Symbol = Symbol.Download },
|
||||||
ShowProgressBar = true,
|
ShowProgressBar = true,
|
||||||
XamlRoot = App.MainWindow,
|
XamlRoot = RyujinxApp.MainWindow,
|
||||||
};
|
};
|
||||||
|
|
||||||
taskDialog.Opened += (s, e) =>
|
taskDialog.Opened += (s, e) =>
|
||||||
@@ -490,7 +490,7 @@ namespace Ryujinx.Ava
|
|||||||
bytesWritten += readSize;
|
bytesWritten += readSize;
|
||||||
|
|
||||||
taskDialog.SetProgressBarState(GetPercentage(bytesWritten, totalBytes), TaskDialogProgressState.Normal);
|
taskDialog.SetProgressBarState(GetPercentage(bytesWritten, totalBytes), TaskDialogProgressState.Normal);
|
||||||
App.SetTaskbarProgressValue(bytesWritten, totalBytes);
|
RyujinxApp.SetTaskbarProgressValue(bytesWritten, totalBytes);
|
||||||
|
|
||||||
updateFileStream.Write(buffer, 0, readSize);
|
updateFileStream.Write(buffer, 0, readSize);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user