Compare commits

...

34 Commits

Author SHA1 Message Date
heihei123456780
3b2a0f7ff6 Merge branch 'master' into master 2024-12-27 18:37:54 +08:00
Evan Husted
9eb273a0f7 Org rename (they call me indecisive) 2024-12-27 02:05:37 -06:00
Evan Husted
ccddaa77d1 infra: Org 2024-12-27 01:59:29 -06:00
Evan Husted
01c2e67334 lol 2024-12-27 01:41:48 -06:00
Evan Husted
4c646721d6 infra: Testing moving canary to the future home of this fork 2024-12-27 01:38:51 -06:00
Evan Husted
d3bc3a1081 UI: Simplify LDN data logic 2024-12-27 01:32:23 -06:00
Evan Husted
c69881a0a2 UI: chore: remove direct static MainWindowViewModel reference 2024-12-27 00:47:57 -06:00
Evan Husted
1bc0159139 Once again, I am stupid 2024-12-27 00:41:50 -06:00
Evan Husted
0733b7d0a1 chore: Remove duplicate VSyncMode enum in GAL 2024-12-27 00:38:12 -06:00
Evan Husted
9df1366fa1 I may be stu-- nah. I am here. I am stupid for this one. 2024-12-27 00:00:29 -06:00
Evan Husted
c73b5bdf46 metal: wip: allow getting/setting developerHUDProperies 2024-12-26 23:58:55 -06:00
Evan Husted
9754d247b5 UI: Directly proxy window properties on the view model back to the stored window 2024-12-26 23:32:53 -06:00
Evan Husted
267e9f6350 UI: Redirect IME errors to Debug instead of error 2024-12-26 23:13:35 -06:00
Evan Husted
d7b3dd12d1 UI: Set title bar icon to the already loaded one 2024-12-26 22:58:49 -06:00
Evan Husted
c9b2a6b1f1 metal: Try and see if this lets VSync turn off.
(cherry picked from commit 9558b37716)
2024-12-26 22:46:18 -06:00
heihei123456780
d1824c5cb6 Merge branch 'master' into master 2024-12-26 14:30:26 +08:00
Evan Husted
17233d30da misc: give various threads dedicated names
Move all title ID lists into a TitleIDs class in Ryujinx.Common, with helpers.
Unify & simplify Auto graphics backend selection logic
2024-12-26 00:29:00 -06:00
Otozinclus
2bf48f57d2 Add more games to metal list (#447)
Mario Kart 8 Deluxe and Deltarune got tested by Isaac with help from
Peri previosly (His video: https://www.youtube.com/watch?v=GEVre_0ZVUg
)

Captain Toad, Cuphead and Animal Crossing I tested myself (side-by-side
Video comparison: https://youtu.be/auNS9MmZMPI )

Additional information:

Cuphead has flickering issues with certain UI elements on Vulkan via
MoltenVK. Metal fixes those and introduces no new issues, according to
my testing.

Animal Crossing is accurate, except for it having broken backgrounds in
interiors, causing them to appear as white instead of black. This is
caused by a hardware level sampler bug, that isaac never got to find a
workaround for.
However, this issue happens with Vulkan via MoltenVK as well, both Metal
and Vulkan have this issue, therefore Metal shouldn't have any downside
compared to using Vulkan in this game.
2024-12-25 14:19:53 -06:00
Evan Husted
412d4065b8 UI: Abstract applet launch logic for future potential applets
Optimize locale loading (remove always loading english, that was only needed with the old system)
2024-12-25 00:56:01 -06:00
Evan Husted
e6644626fc UI: Fix negative space savings in XCI trimmer 2024-12-25 00:06:29 -06:00
Evan Husted
0bacdb8765 Improve locale file parsing error descriptions 2024-12-24 22:19:58 -06:00
Evan Husted
0ca4d6e921 misc: Move StatusBarSeparator into Controls namespace, rename to MiniVerticalSeparator
add bulk property change event method
give each markup extension its own property name
2024-12-24 21:55:12 -06:00
Evan Husted
f0aa7eedf6 lol 2024-12-24 21:15:13 -06:00
Evan Husted
41acc4b1f3 UI: misc: Collapse repeated identical Border usages into a helper control. 2024-12-24 21:14:17 -06:00
Evan Husted
7aede70ba9 UI: Make custom title bar window controls extend exactly as long as the menu bar is tall 2024-12-24 21:00:41 -06:00
Evan Husted
a0a4f78cff UI: Thin down the borders of the app icon a little bit and trim down the file size significantly. 2024-12-24 20:47:14 -06:00
heihei123456780
0502da96cd Merge branch 'master' into master 2024-12-25 10:37:12 +08:00
Evan Husted
16a60fdf12 UI: Rename App to RyujinxApp
Add more NotificationHelper methods
Simplify ID copy logic
2024-12-24 13:39:48 -06:00
heihei123456780
7df0fb587e Updated zh-CN translation 2024-12-24 16:26:40 +08:00
heihei123456780
50847f0687 Merge branch 'GreemDev:master' into master 2024-12-24 16:23:41 +08:00
heihei123456780
125f6d2777 Updated zh-CN translation 2024-12-24 13:03:34 +08:00
heihei123456780
dfc4831828 Update locales.json
Fix format error.
2024-12-24 10:58:05 +08:00
heihei123456780
45feeae9db Update locales.json
Updated zh-CN translation
2024-12-24 10:52:44 +08:00
heihei123456780
e67df5219c Update locales.json
Add missing zh-CN translation.
2024-12-24 10:34:49 +08:00
61 changed files with 721 additions and 600 deletions

View File

@@ -21,9 +21,9 @@ env:
DOTNET_CLI_TELEMETRY_OPTOUT: 1
RYUJINX_BASE_VERSION: "1.2"
RYUJINX_TARGET_RELEASE_CHANNEL_NAME: "canary"
RYUJINX_TARGET_RELEASE_CHANNEL_OWNER: "GreemDev"
RYUJINX_TARGET_RELEASE_CHANNEL_OWNER: "Ryubing"
RYUJINX_TARGET_RELEASE_CHANNEL_SOURCE_REPO: "Ryujinx"
RYUJINX_TARGET_RELEASE_CHANNEL_REPO: "Ryujinx-Canary"
RYUJINX_TARGET_RELEASE_CHANNEL_REPO: "Canary-Releases"
RELEASE: 1
jobs:

View File

@@ -21,7 +21,7 @@ env:
DOTNET_CLI_TELEMETRY_OPTOUT: 1
RYUJINX_BASE_VERSION: "1.2"
RYUJINX_TARGET_RELEASE_CHANNEL_NAME: "release"
RYUJINX_TARGET_RELEASE_CHANNEL_OWNER: "GreemDev"
RYUJINX_TARGET_RELEASE_CHANNEL_OWNER: "Ryubing"
RYUJINX_TARGET_RELEASE_CHANNEL_REPO: "Ryujinx"
RELEASE: 1

View File

@@ -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}"
EndProject
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}"
ProjectSection(ProjectDependencies) = postProject
{A602AE97-91A5-4608-8DF1-EBF4ED7A0B9E} = {A602AE97-91A5-4608-8DF1-EBF4ED7A0B9E}
EndProjectSection
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}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
@@ -94,8 +99,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
.github\workflows\release.yml = .github\workflows\release.yml
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ryujinx.BuildValidationTasks", "src\Ryujinx.BuildValidationTasks\Ryujinx.BuildValidationTasks.csproj", "{4A89A234-4F19-497D-A576-DDE8CDFC5B22}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -265,6 +268,10 @@ Global
{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.Build.0 = Release|Any CPU
{81EA598C-DBA1-40B0-8DA4-4796B78F2037}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{81EA598C-DBA1-40B0-8DA4-4796B78F2037}.Debug|Any CPU.Build.0 = Debug|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
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@@ -1,4 +1,5 @@
using ARMeilleure.State;
using Humanizer;
using Ryujinx.Common;
using Ryujinx.Common.Logging;
using Ryujinx.Common.Memory;
@@ -58,8 +59,8 @@ namespace ARMeilleure.Translation.PTC
{
_ptc = ptc;
_timer = new Timer(SaveInterval * 1000d);
_timer.Elapsed += PreSave;
_timer = new Timer(SaveInterval.Seconds());
_timer.Elapsed += TimerElapsed;
_outerHeaderMagic = BinaryPrimitives.ReadUInt64LittleEndian(EncodingCache.UTF8NoBOM.GetBytes(OuterHeaderMagicString).AsSpan());
@@ -72,6 +73,9 @@ namespace ARMeilleure.Translation.PTC
Enabled = false;
}
private void TimerElapsed(object _, ElapsedEventArgs __)
=> new Thread(PreSave) { Name = "Ptc.DiskWriter" }.Start();
public void AddEntry(ulong address, ExecutionMode mode, bool highCq)
{
if (IsAddressInStaticCodeRange(address))
@@ -262,7 +266,7 @@ namespace ARMeilleure.Translation.PTC
compressedStream.SetLength(0L);
}
private void PreSave(object source, ElapsedEventArgs e)
private void PreSave()
{
_waitEvent.Reset();
@@ -428,7 +432,7 @@ namespace ARMeilleure.Translation.PTC
{
_disposed = true;
_timer.Elapsed -= PreSave;
_timer.Elapsed -= TimerElapsed;
_timer.Dispose();
Wait();

View File

@@ -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
];
}
}

View File

@@ -1,3 +1,4 @@
using Ryujinx.Common.Configuration;
using System;
namespace Ryujinx.Graphics.GAL

View File

@@ -1,3 +1,4 @@
using Ryujinx.Common.Configuration;
using Ryujinx.Graphics.GAL.Multithreading.Commands.Window;
using Ryujinx.Graphics.GAL.Multithreading.Model;
using Ryujinx.Graphics.GAL.Multithreading.Resources;

View File

@@ -1,9 +0,0 @@
namespace Ryujinx.Graphics.GAL
{
public enum VSyncMode
{
Switch,
Unbounded,
Custom
}
}

View File

@@ -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);
}
}

View File

@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="SharpMetal" />
</ItemGroup>
</Project>

View File

@@ -5,12 +5,9 @@
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
<ProjectReference Include="..\Ryujinx.Graphics.GAL\Ryujinx.Graphics.GAL.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="SharpMetal" />
<ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
<ProjectReference Include="..\Ryujinx.Graphics.GAL\Ryujinx.Graphics.GAL.csproj" />
<ProjectReference Include="..\Ryujinx.Graphics.Metal.SharpMetalExtensions\Ryujinx.Graphics.Metal.SharpMetalExtensions.csproj" />
</ItemGroup>
<ItemGroup>

View File

@@ -1,10 +1,14 @@
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Metal.Effects;
using Ryujinx.Graphics.Metal.SharpMetalExtensions;
using SharpMetal.ObjectiveCCore;
using SharpMetal.QuartzCore;
using System;
using System.Runtime.Versioning;
using AntiAliasing = Ryujinx.Graphics.GAL.AntiAliasing;
using ScalingFilter = Ryujinx.Graphics.GAL.ScalingFilter;
namespace Ryujinx.Graphics.Metal
{
@@ -140,7 +144,15 @@ namespace Ryujinx.Graphics.Metal
public void ChangeVSyncMode(VSyncMode vSyncMode)
{
//_vSyncMode = vSyncMode;
switch (vSyncMode)
{
case VSyncMode.Unbounded:
_metalLayer.SetDisplaySyncEnabled(false);
break;
case VSyncMode.Switch:
_metalLayer.SetDisplaySyncEnabled(true);
break;
}
}
public void SetAntiAliasing(AntiAliasing effect)

View File

@@ -42,7 +42,7 @@ namespace Ryujinx.Graphics.OpenGL.Queries
_current = new CounterQueueEvent(this, glType, 0);
_consumerThread = new Thread(EventConsumer);
_consumerThread = new Thread(EventConsumer) { Name = "CPU.CounterQueue." + (int)type };
_consumerThread.Start();
}

View File

@@ -1,9 +1,12 @@
using OpenTK.Graphics.OpenGL;
using Ryujinx.Common.Configuration;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.OpenGL.Effects;
using Ryujinx.Graphics.OpenGL.Effects.Smaa;
using Ryujinx.Graphics.OpenGL.Image;
using System;
using AntiAliasing = Ryujinx.Graphics.GAL.AntiAliasing;
using ScalingFilter = Ryujinx.Graphics.GAL.ScalingFilter;
namespace Ryujinx.Graphics.OpenGL
{

View File

@@ -52,7 +52,7 @@ namespace Ryujinx.Graphics.Vulkan.Queries
_current = new CounterQueueEvent(this, type, 0);
_consumerThread = new Thread(EventConsumer);
_consumerThread = new Thread(EventConsumer) { Name = "CPU.CounterQueue." + (int)type };
_consumerThread.Start();
}

View File

@@ -1,9 +1,12 @@
using Ryujinx.Common.Configuration;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Vulkan.Effects;
using Silk.NET.Vulkan;
using Silk.NET.Vulkan.Extensions.KHR;
using System;
using System.Linq;
using AntiAliasing = Ryujinx.Graphics.GAL.AntiAliasing;
using ScalingFilter = Ryujinx.Graphics.GAL.ScalingFilter;
using VkFormat = Silk.NET.Vulkan.Format;
namespace Ryujinx.Graphics.Vulkan

View File

@@ -1,5 +1,8 @@
using Ryujinx.Common.Configuration;
using Ryujinx.Graphics.GAL;
using System;
using AntiAliasing = Ryujinx.Graphics.GAL.AntiAliasing;
using ScalingFilter = Ryujinx.Graphics.GAL.ScalingFilter;
namespace Ryujinx.Graphics.Vulkan
{

View File

@@ -1034,16 +1034,16 @@ namespace Ryujinx.HLE.FileSystem
switch (fileName)
{
case "prod.keys":
verified = verifyKeys(lines, genericPattern);
verified = VerifyKeys(lines, genericPattern);
break;
case "title.keys":
verified = verifyKeys(lines, titlePattern);
verified = VerifyKeys(lines, titlePattern);
break;
case "console.keys":
verified = verifyKeys(lines, genericPattern);
verified = VerifyKeys(lines, genericPattern);
break;
case "dev.keys":
verified = verifyKeys(lines, genericPattern);
verified = VerifyKeys(lines, genericPattern);
break;
default:
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}\".");
}
}
private bool verifyKeys(string[] lines, string regex)
{
foreach (string line in lines)
return;
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)
{
string[] fileNames = { "prod.keys", "title.keys", "console.keys", "dev.keys" };

View File

@@ -9,7 +9,6 @@ using Ryujinx.HLE.HOS.Services.Account.Acc;
using Ryujinx.HLE.HOS.SystemState;
using Ryujinx.HLE.UI;
using System;
using VSyncMode = Ryujinx.Common.Configuration.VSyncMode;
namespace Ryujinx.HLE
{

View File

@@ -181,7 +181,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
is64Bits = true;
}
HostThread = new Thread(ThreadStart);
HostThread = new Thread(ThreadStart) { Name = "HLE.KThread" };
Context = owner?.CreateExecutionContext() ?? new ProcessExecutionContext();

View File

@@ -10,7 +10,6 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using VSyncMode = Ryujinx.Common.Configuration.VSyncMode;
namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
{

View File

@@ -36,6 +36,8 @@ namespace Ryujinx.UI.App.Common
public string Path { get; set; }
public BlitStruct<ApplicationControlProperty> ControlHolder { get; set; }
public bool HasControlHolder => ControlHolder.ByteSpan.Length > 0;
public string TimePlayedString => ValueFormatUtils.FormatTimeSpan(TimePlayed);
public string LastPlayedString => ValueFormatUtils.FormatDateTime(LastPlayed)?.Replace(" ", "\n") ?? LocalizedNever();

View File

@@ -789,16 +789,15 @@ namespace Ryujinx.UI.App.Common
using HttpClient httpClient = new HttpClient();
string ldnGameDataArrayString = await httpClient.GetStringAsync($"https://{ldnWebHost}/api/public_games");
ldnGameDataArray = JsonHelper.Deserialize(ldnGameDataArrayString, _ldnDataSerializerContext.IEnumerableLdnGameData);
var evt = new LdnGameDataReceivedEventArgs
LdnGameDataReceived?.Invoke(null, new LdnGameDataReceivedEventArgs
{
LdnData = ldnGameDataArray
};
LdnGameDataReceived?.Invoke(null, evt);
});
}
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}");
LdnGameDataReceived?.Invoke(null, new LdnGameDataReceivedEventArgs()
LdnGameDataReceived?.Invoke(null, new LdnGameDataReceivedEventArgs
{
LdnData = Array.Empty<LdnGameData>()
});
@@ -806,7 +805,7 @@ namespace Ryujinx.UI.App.Common
}
else
{
LdnGameDataReceived?.Invoke(null, new LdnGameDataReceivedEventArgs()
LdnGameDataReceived?.Invoke(null, new LdnGameDataReceivedEventArgs
{
LdnData = Array.Empty<LdnGameData>()
});

View File

@@ -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;
}
}

View File

@@ -76,7 +76,7 @@ namespace Ryujinx.UI.Common
{
Assets = new Assets
{
LargeImageKey = _discordGameAssetKeys.Contains(procRes.ProgramIdText) ? procRes.ProgramIdText : "game",
LargeImageKey = TitleIDs.GetDiscordGameAsset(procRes.ProgramIdText),
LargeImageText = TruncateToByteLength($"{appMeta.Title} (v{procRes.DisplayVersion})"),
SmallImageKey = "ryujinx",
SmallImageText = TruncateToByteLength(_description)
@@ -122,151 +122,5 @@ namespace Ryujinx.UI.Common
{
_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
];
}
}

View File

@@ -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

View File

@@ -33,7 +33,7 @@
<EmbeddedResource Include="Resources\Icon_XCI.png" />
<EmbeddedResource Include="Resources\Logo_Amiibo.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_Light.png" />
<EmbeddedResource Include="Resources\Logo_GitHub_Dark.png" />

View File

@@ -143,23 +143,6 @@ namespace Ryujinx.Ava
public ulong ApplicationId { get; private set; }
public bool ScreenshotRequested { get; set; }
public bool ShouldInitMetal
{
get
{
return OperatingSystem.IsMacOS() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64 &&
(
(
(
ConfigurationState.Instance.Graphics.GraphicsBackend.Value == GraphicsBackend.Auto &&
RendererHost.KnownGreatMetalTitles.ContainsIgnoreCase(ApplicationId.ToString("X16"))
) ||
ConfigurationState.Instance.Graphics.GraphicsBackend.Value == GraphicsBackend.Metal
)
);
}
}
public AppHost(
RendererHost renderer,
InputManager inputManager,
@@ -328,7 +311,7 @@ namespace Ryujinx.Ava
Device.VSyncMode = e.NewValue;
Device.UpdateVSyncInterval();
}
_renderer.Window?.ChangeVSyncMode((Ryujinx.Graphics.GAL.VSyncMode)e.NewValue);
_renderer.Window?.ChangeVSyncMode(e.NewValue);
_viewModel.ShowCustomVSyncIntervalPicker = (e.NewValue == VSyncMode.Custom);
}
@@ -912,27 +895,20 @@ namespace Ryujinx.Ava
VirtualFileSystem.ReloadKeySet();
// Initialize Renderer.
IRenderer renderer;
GraphicsBackend backend = ConfigurationState.Instance.Graphics.GraphicsBackend;
GraphicsBackend backend = TitleIDs.SelectGraphicsBackend(ApplicationId.ToString("X16"), ConfigurationState.Instance.Graphics.GraphicsBackend);
if (ShouldInitMetal)
IRenderer renderer = backend switch
{
#pragma warning disable CA1416 // This call site is reachable on all platforms
// The condition does a check for Mac, on top of checking if it's an ARM Mac. This isn't a problem.
renderer = new MetalRenderer((RendererHost.EmbeddedWindow as EmbeddedWindowMetal)!.CreateSurface);
// 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
}
else if (backend == GraphicsBackend.Vulkan || (backend == GraphicsBackend.Auto && !ShouldInitMetal))
{
renderer = VulkanRenderer.Create(
GraphicsBackend.Vulkan => VulkanRenderer.Create(
ConfigurationState.Instance.Graphics.PreferredGpu,
(RendererHost.EmbeddedWindow as EmbeddedWindowVulkan)!.CreateSurface,
VulkanHelper.GetRequiredInstanceExtensions);
}
else
{
renderer = new OpenGLRenderer();
}
VulkanHelper.GetRequiredInstanceExtensions),
_ => new OpenGLRenderer()
};
BackendThreading threadingMode = ConfigurationState.Instance.Graphics.BackendThreading;
@@ -1098,7 +1074,7 @@ namespace Ryujinx.Ava
Device.Gpu.SetGpuThread();
Device.Gpu.InitializeShaderCache(_gpuCancellationTokenSource.Token);
_renderer.Window.ChangeVSyncMode((Ryujinx.Graphics.GAL.VSyncMode)Device.VSyncMode);
_renderer.Window.ChangeVSyncMode(Device.VSyncMode);
while (_isActive)
{

View File

@@ -89,7 +89,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "Аплет для редагування Mii",
"zh_CN": "",
"zh_CN": "Mii 小程序",
"zh_TW": ""
}
},
@@ -713,7 +713,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "从bin文件扫描 Amiibo",
"zh_TW": ""
}
},
@@ -833,7 +833,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "安装密匙",
"zh_TW": ""
}
},
@@ -857,7 +857,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "从.KEYS文件或ZIP压缩包安装密匙",
"zh_TW": ""
}
},
@@ -881,7 +881,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "从一个文件夹安装密匙",
"zh_TW": ""
}
},
@@ -977,7 +977,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "Обрізати XCI файли",
"zh_CN": "",
"zh_CN": "XCI文件瘦身",
"zh_TW": ""
}
},
@@ -1217,7 +1217,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "常见问题和问题排除页面",
"zh_TW": ""
}
},
@@ -1241,7 +1241,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "打开Ryujinx官方wiki的常见问题和问题排除页面",
"zh_TW": ""
}
},
@@ -1265,7 +1265,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "安装与配置指南",
"zh_TW": ""
}
},
@@ -1289,7 +1289,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "打开Ryujinx官方wiki的安装与配置指南",
"zh_TW": ""
}
},
@@ -1313,7 +1313,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "多人游戏LDN/LAN指南",
"zh_TW": ""
}
},
@@ -1337,7 +1337,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "打开Ryujinx官方wiki的多人游戏指南",
"zh_TW": ""
}
},
@@ -2441,7 +2441,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "Перевірка та Нарізка XCI Файлів",
"zh_CN": "",
"zh_CN": "检查并瘦身XCI文件",
"zh_TW": ""
}
},
@@ -2465,7 +2465,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "Перевірка та Нарізка XCI Файлів для збереження місця на диску",
"zh_CN": "",
"zh_CN": "检查并瘦身XCI文件以节约磁盘空间",
"zh_TW": ""
}
},
@@ -2537,7 +2537,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "Обрізано XCI Файлів '{0}'",
"zh_CN": "",
"zh_CN": "XCI文件瘦身中'{0}'",
"zh_TW": ""
}
},
@@ -10097,7 +10097,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "Скасування",
"zh_CN": "",
"zh_CN": "正在取消",
"zh_TW": ""
}
},
@@ -10121,7 +10121,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "Закрити",
"zh_CN": "",
"zh_CN": "关闭",
"zh_TW": ""
}
},
@@ -11369,7 +11369,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "Показати список змін",
"zh_CN": "",
"zh_CN": "显示更新日志",
"zh_TW": ""
}
},
@@ -11825,7 +11825,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "XCI文件瘦身窗口",
"zh_TW": ""
}
},
@@ -12521,7 +12521,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "在{0}发现了一个无效的密匙文件",
"zh_TW": ""
}
},
@@ -12545,7 +12545,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "Встановлення Ключів",
"zh_CN": "",
"zh_CN": "安装密匙",
"zh_TW": ""
}
},
@@ -12569,7 +12569,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "Новий файл Ключів буде встановлено",
"zh_CN": "",
"zh_CN": "将会安装新密匙文件",
"zh_TW": ""
}
},
@@ -12593,7 +12593,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "\n\nЦе замінить собою поточні файли Ключів.",
"zh_CN": "",
"zh_CN": "\n\n这也许会替换掉一些当前已安装的密匙",
"zh_TW": ""
}
},
@@ -12617,7 +12617,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "\n\nВи хочете продовжити?",
"zh_CN": "",
"zh_CN": "\n\n你想要继续吗",
"zh_TW": ""
}
},
@@ -12641,7 +12641,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "Встановлення Ключів...",
"zh_CN": "",
"zh_CN": "安装密匙中。。。",
"zh_TW": ""
}
},
@@ -12665,7 +12665,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "Нові ключі встановлено.",
"zh_CN": "",
"zh_CN": "已成功安装新密匙文件",
"zh_TW": ""
}
},
@@ -13673,7 +13673,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "Минулі розробники:",
"zh_CN": "",
"zh_CN": "曾经的维护者:",
"zh_TW": ""
}
},
@@ -14657,7 +14657,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "重新同步系统时间以匹配您电脑的当前日期和时间。\n\n这个操作不会实时同步系统时间与电脑时间时间仍然可能不同步在这种情况下只需再次单击此按钮即可。",
"zh_TW": ""
}
},
@@ -16817,7 +16817,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "Часткові",
"zh_CN": "",
"zh_CN": "分区",
"zh_TW": ""
}
},
@@ -16841,7 +16841,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "Необрізані",
"zh_CN": "",
"zh_CN": "没有瘦身的",
"zh_TW": ""
}
},
@@ -16865,7 +16865,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "Обрізані",
"zh_CN": "",
"zh_CN": "经过瘦身的",
"zh_TW": ""
}
},
@@ -16889,7 +16889,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "(Невдача)",
"zh_CN": "",
"zh_CN": "(失败)",
"zh_TW": ""
}
},
@@ -16913,7 +16913,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "Зберегти {0:n0} Мб",
"zh_CN": "",
"zh_CN": "能节约 {0:n0} Mb",
"zh_TW": ""
}
},
@@ -16937,7 +16937,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "Збережено {0:n0} Мб",
"zh_CN": "",
"zh_CN": "节约了 {0:n0} Mb",
"zh_TW": ""
}
},
@@ -17129,7 +17129,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "Вкажіть Ваше нове ім'я Amiibo",
"zh_CN": "",
"zh_CN": "输入你的 Amiibo 的新名字",
"zh_TW": ""
}
},
@@ -17153,7 +17153,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "Будь ласка, проскануйте Ваш Amiibo.",
"zh_CN": "",
"zh_CN": "请现在扫描你的 Amiibo",
"zh_TW": ""
}
},
@@ -18233,7 +18233,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "Перевірити та Обрізати XCI файл",
"zh_CN": "",
"zh_CN": "检查并瘦身XCI文件",
"zh_TW": ""
}
},
@@ -18257,7 +18257,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "Ця функція спочатку перевірить вільний простір, а потім обрізатиме файл XCI для економії місця на диску.",
"zh_CN": "",
"zh_CN": "这个功能将会先检查XCI文件再对其执行瘦身操作以节约磁盘空间。",
"zh_TW": ""
}
},
@@ -18281,7 +18281,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "Поточний розмір файла: {0:n} MB\nРозмір файлів гри: {1:n} MB\nЕкономія місця: {2:n} MB",
"zh_CN": "",
"zh_CN": "当前文件大小: {0:n} MB\n游戏数据大小: {1:n} MB\n节约的磁盘空间: {2:n} MB",
"zh_TW": ""
}
},
@@ -18305,7 +18305,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "XCI файл не потребує обрізання. Перевірте журнали для додаткової інформації",
"zh_CN": "",
"zh_CN": "XCI文件不需要被瘦身。查看日志以获得更多细节。",
"zh_TW": ""
}
},
@@ -18329,7 +18329,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "XCI файл не може бути обрізаний. Перевірте журнали для додаткової інформації",
"zh_CN": "",
"zh_CN": "XCI文件不能被瘦身。查看日志以获得更多细节。",
"zh_TW": ""
}
},
@@ -18353,7 +18353,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "XCI файл Тільки для Читання і не може бути прочитаним. Перевірте журнали додаткової інформації",
"zh_CN": "",
"zh_CN": "XCI文件是只读的且不可以被标记为可读取的。查看日志以获得更多细节。",
"zh_TW": ""
}
},
@@ -18377,7 +18377,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "Розмір файлу XCI змінився з моменту сканування. Перевірте, чи не записується файл, та спробуйте знову",
"zh_CN": "",
"zh_CN": "XCI文件在扫描后大小发生了变化。请检查文件是否未被写入然后重试。",
"zh_TW": ""
}
},
@@ -18401,7 +18401,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "Файл XCI містить дані в зоні вільного простору, тому обрізка небезпечна",
"zh_CN": "",
"zh_CN": "XCI文件的空闲区域内有数据不能安全瘦身。",
"zh_TW": ""
}
},
@@ -18425,7 +18425,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "XCI Файл містить недійсні дані. Перевірте журнали для додаткової інформації",
"zh_CN": "",
"zh_CN": "XCI文件含有无效数据。查看日志以获得更多细节。",
"zh_TW": ""
}
},
@@ -18449,7 +18449,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "XCI Файл файл не вдалося відкрити для запису. Перевірте журнали для додаткової інформації",
"zh_CN": "",
"zh_CN": "XCI文件不能被读写。查看日志以获得更多细节。",
"zh_TW": ""
}
},
@@ -18473,7 +18473,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "Не вдалося обрізати файл XCI",
"zh_CN": "",
"zh_CN": "XCI文件瘦身失败",
"zh_TW": ""
}
},
@@ -18497,7 +18497,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "Операція перервана",
"zh_CN": "",
"zh_CN": "操作已取消",
"zh_TW": ""
}
},
@@ -18521,7 +18521,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "Операція не проводилася",
"zh_CN": "",
"zh_CN": "未执行操作",
"zh_TW": ""
}
},
@@ -18665,7 +18665,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "Обрізка XCI Файлів",
"zh_CN": "",
"zh_CN": "XCI文件瘦身器",
"zh_TW": ""
}
},
@@ -18689,7 +18689,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "{0} з {1} тайтл(ів) обрано",
"zh_CN": "",
"zh_CN": "在 {1} 中选中了 {0} 个游戏 ",
"zh_TW": ""
}
},
@@ -18713,7 +18713,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "{0} з {1} тайтл(ів) обрано ({2} відображається)",
"zh_CN": "",
"zh_CN": "在 {1} 中选中了 {0} 个游戏 (显示了 {2} 个)",
"zh_TW": ""
}
},
@@ -18737,7 +18737,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "Обрізка {0} тайтл(ів)...",
"zh_CN": "",
"zh_CN": "{0} 个游戏瘦身中。。。",
"zh_TW": ""
}
},
@@ -18785,7 +18785,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "Невдача",
"zh_CN": "",
"zh_CN": "失败",
"zh_TW": ""
}
},
@@ -18809,7 +18809,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "Потенційна економія",
"zh_CN": "",
"zh_CN": "潜在的储存空间节省",
"zh_TW": ""
}
},
@@ -18833,7 +18833,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "Зекономлено",
"zh_CN": "",
"zh_CN": "实际的储存空间节省",
"zh_TW": ""
}
},
@@ -18881,7 +18881,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "Вибрати показане",
"zh_CN": "",
"zh_CN": "选定显示的",
"zh_TW": ""
}
},
@@ -18905,7 +18905,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "Скасувати вибір показаного",
"zh_CN": "",
"zh_CN": "反选显示的",
"zh_TW": ""
}
},
@@ -18977,7 +18977,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "Обрізка",
"zh_CN": "",
"zh_CN": "瘦身",
"zh_TW": ""
}
},
@@ -19337,7 +19337,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "Продовжити",
"zh_CN": "",
"zh_CN": "继续",
"zh_TW": ""
}
},
@@ -19697,7 +19697,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "自动",
"zh_TW": ""
}
},
@@ -19721,7 +19721,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "使用Vulkan。\n在ARM Mac上当玩在其下运行良好的游戏时使用Metal后端。",
"zh_TW": ""
}
},
@@ -21065,7 +21065,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "Вимкнути хостинг P2P мережі (може збільшити затримку)",
"zh_CN": "",
"zh_CN": "禁用P2P网络连接 (也许会增加延迟)",
"zh_TW": ""
}
},
@@ -21089,7 +21089,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "Вимкнути хостинг P2P мережі, піри будуть підключатися через майстер-сервер замість прямого з'єднання з вами.",
"zh_CN": "",
"zh_CN": "禁用P2P网络连接对方将通过主服务器进行连接而不是直接连接到您。",
"zh_TW": ""
}
},
@@ -21113,7 +21113,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "Мережевий пароль:",
"zh_CN": "",
"zh_CN": "网络密码:",
"zh_TW": ""
}
},
@@ -21137,7 +21137,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "Ви зможете бачити лише ті ігри, які мають такий самий пароль, як і у вас.",
"zh_CN": "",
"zh_CN": "您只能看到与您使用相同密码的游戏房间。",
"zh_TW": ""
}
},
@@ -21161,7 +21161,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "Введіть пароль у форматі Ryujinx-<8 символів>. Ви зможете бачити лише ті ігри, які мають такий самий пароль, як і у вас.",
"zh_CN": "",
"zh_CN": "以Ryujinx-<8个十六进制字符>的格式输入密码。您只能看到与您使用相同密码的游戏房间。",
"zh_TW": ""
}
},
@@ -21185,7 +21185,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "(публічний)",
"zh_CN": "",
"zh_CN": "(公开的)",
"zh_TW": ""
}
},
@@ -21209,7 +21209,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "Згенерувати випадкову",
"zh_CN": "",
"zh_CN": "随机生成",
"zh_TW": ""
}
},
@@ -21233,7 +21233,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "Генерує новий пароль, яким можна поділитися з іншими гравцями.",
"zh_CN": "",
"zh_CN": "生成一个新的密码,可以与其他玩家共享。",
"zh_TW": ""
}
},
@@ -21257,7 +21257,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "Очистити",
"zh_CN": "",
"zh_CN": "清除",
"zh_TW": ""
}
},
@@ -21281,7 +21281,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "Очищає поточну пароль, повертаючись до публічної мережі.",
"zh_CN": "",
"zh_CN": "清除当前密码,返回公共网络。",
"zh_TW": ""
}
},
@@ -21305,7 +21305,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "Невірний пароль! Має бути в форматі \"Ryujinx-<8 символів>\"",
"zh_CN": "",
"zh_CN": "无效密码!密码的格式必须是\"Ryujinx-<8个十六进制字符>\"",
"zh_TW": ""
}
},
@@ -21329,7 +21329,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "Вертикальна синхронізація (VSync):",
"zh_CN": "",
"zh_CN": "垂直同步VSync",
"zh_TW": ""
}
},
@@ -21353,7 +21353,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "Увімкнути користувацьку частоту оновлення (Експериментально)",
"zh_CN": "",
"zh_CN": "启动自定义刷新率(实验性功能)",
"zh_TW": ""
}
},
@@ -21401,7 +21401,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "Безмежна",
"zh_CN": "",
"zh_CN": "无限制",
"zh_TW": ""
}
},
@@ -21425,7 +21425,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "Користувацька",
"zh_CN": "",
"zh_CN": "自定义刷新率",
"zh_TW": ""
}
},
@@ -21449,7 +21449,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "Емульована вертикальна синхронізація. 'Switch' емулює частоту оновлення Switch 60 Гц. 'Безмежна' — частота оновлення не матиме обмежень.",
"zh_CN": "",
"zh_CN": "模拟垂直同步。“Switch”模拟了Switch的60Hz刷新率。“无限制”没有刷新率限制。",
"zh_TW": ""
}
},
@@ -21473,7 +21473,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "Емульована вертикальна синхронізація. 'Switch' емулює частоту оновлення Switch 60 Гц. 'Безмежна' — частота оновлення не матиме обмежень. 'Користувацька' емулює вказану користувацьку частоту оновлення.",
"zh_CN": "",
"zh_CN": "模拟垂直同步。“Switch”模拟了Switch的60Hz刷新率。“无限制”没有刷新率限制。“自定义刷新率”模拟指定的自定义刷新率。",
"zh_TW": ""
}
},
@@ -21497,7 +21497,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "Дозволяє користувачу вказати емульовану частоту оновлення. У деяких іграх це може прискорити або сповільнити логіку гри. У інших іграх це може дозволити обмежити FPS на певні кратні частоти оновлення або призвести до непередбачуваної поведінки. Це експериментальна функція, без гарантій того, як це вплине на ігровий процес. \n\nЗалиште ВИМКНЕНИМ, якщо не впевнені.",
"zh_CN": "",
"zh_CN": "允许用户指定模拟刷新率。在某些游戏中这可能会加快或减慢游戏逻辑的速度。在其他游戏中它可能允许将FPS限制在刷新率的某个倍数或者导致不可预测的行为。这是一个实验性功能无法保证游戏会受到怎样的影响。\n\n如果不确定请关闭。",
"zh_TW": ""
}
},
@@ -21521,7 +21521,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "Цільове значення користувацької частоти оновлення.",
"zh_CN": "",
"zh_CN": "目标自定义刷新率值。",
"zh_TW": ""
}
},
@@ -21545,7 +21545,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "Користувацька частота оновлення, як відсоток від стандартної частоти оновлення Switch.",
"zh_CN": "",
"zh_CN": "自定义刷新率占正常SWitch刷新率的百分比值。",
"zh_TW": ""
}
},
@@ -21569,7 +21569,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "Користувацька частота оновлення %:",
"zh_CN": "",
"zh_CN": "自定义刷新率值 %:",
"zh_TW": ""
}
},
@@ -21593,7 +21593,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "Значення користувацька частота оновлення:",
"zh_CN": "",
"zh_CN": "自定义刷新率值:",
"zh_TW": ""
}
},
@@ -21617,7 +21617,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "间隔",
"zh_TW": ""
}
},
@@ -21641,7 +21641,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "Перемкнути VSync режим:",
"zh_CN": "",
"zh_CN": "设置 VSync 模式:",
"zh_TW": ""
}
},
@@ -21665,7 +21665,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "Підвищити користувацьку частоту оновлення",
"zh_CN": "",
"zh_CN": "提高自定义刷新率:",
"zh_TW": ""
}
},
@@ -21689,7 +21689,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "Понизити користувацьку частоту оновлення",
"zh_CN": "",
"zh_CN": "降低自定义刷新率:",
"zh_TW": ""
}
}

View File

@@ -146,7 +146,7 @@ namespace Ryujinx.Ava.Common
var cancellationToken = new CancellationTokenSource();
UpdateWaitWindow waitingDialog = new(
App.FormatTitle(LocaleKeys.DialogNcaExtractionTitle),
RyujinxApp.FormatTitle(LocaleKeys.DialogNcaExtractionTitle),
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogNcaExtractionMessage, ncaSectionType, Path.GetFileName(titleFilePath)),
cancellationToken);
@@ -268,10 +268,9 @@ namespace Ryujinx.Ava.Common
{
Dispatcher.UIThread.Post(waitingDialog.Close);
NotificationHelper.Show(
App.FormatTitle(LocaleKeys.DialogNcaExtractionTitle),
$"{titleName}\n\n{LocaleManager.Instance[LocaleKeys.DialogNcaExtractionSuccessMessage]}",
NotificationType.Information);
NotificationHelper.ShowInformation(
RyujinxApp.FormatTitle(LocaleKeys.DialogNcaExtractionTitle),
$"{titleName}\n\n{LocaleManager.Instance[LocaleKeys.DialogNcaExtractionSuccessMessage]}");
}
}

View File

@@ -1,7 +1,6 @@
using Gommon;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Common;
using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities;
using Ryujinx.UI.Common.Configuration;
using System;
@@ -17,7 +16,6 @@ namespace Ryujinx.Ava.Common.Locale
private const string DefaultLanguageCode = "en_US";
private readonly Dictionary<LocaleKeys, string> _localeStrings;
private Dictionary<LocaleKeys, string> _localeDefaultStrings;
private readonly ConcurrentDictionary<LocaleKeys, object[]> _dynamicValues;
private string _localeLanguageCode;
@@ -27,7 +25,6 @@ namespace Ryujinx.Ava.Common.Locale
public LocaleManager()
{
_localeStrings = new Dictionary<LocaleKeys, string>();
_localeDefaultStrings = new Dictionary<LocaleKeys, string>();
_dynamicValues = new ConcurrentDictionary<LocaleKeys, object[]>();
Load();
@@ -37,9 +34,7 @@ namespace Ryujinx.Ava.Common.Locale
{
var localeLanguageCode = !string.IsNullOrEmpty(ConfigurationState.Instance.UI.LanguageCode.Value) ?
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);
// Save whatever we ended up with.
@@ -66,26 +61,14 @@ namespace Ryujinx.Ava.Common.Locale
}
catch
{
// If formatting failed use the default text instead.
if (_localeDefaultStrings.TryGetValue(key, out value))
try
{
return string.Format(value, dynamicValue);
}
catch
{
// If formatting the default text failed return the key.
return key.ToString();
}
// If formatting the text failed,
// continue to the below line & return the text without formatting.
}
return value;
}
// If the locale doesn't contain the key return the default one.
return _localeDefaultStrings.TryGetValue(key, out string defaultValue)
? defaultValue
: key.ToString(); // If the locale text doesn't exist return the key.
return key.ToString(); // If the locale text doesn't exist return the key.
}
set
{
@@ -109,16 +92,11 @@ namespace Ryujinx.Ava.Common.Locale
{
_dynamicValues[key] = values;
OnPropertyChanged("Item");
OnPropertyChanged("Translation");
return this[key];
}
private void LoadDefaultLanguage()
{
_localeDefaultStrings = LoadJsonLanguage(DefaultLanguageCode);
}
public void LoadLanguage(string languageCode)
{
var locale = LoadJsonLanguage(languageCode);
@@ -126,7 +104,7 @@ namespace Ryujinx.Ava.Common.Locale
if (locale == null)
{
_localeLanguageCode = DefaultLanguageCode;
locale = _localeDefaultStrings;
locale = LoadJsonLanguage(_localeLanguageCode);
}
else
{
@@ -138,16 +116,12 @@ namespace Ryujinx.Ava.Common.Locale
_localeStrings[key] = val;
}
OnPropertyChanged("Item");
OnPropertyChanged("Translation");
LocaleChanged?.Invoke();
}
#nullable enable
private static LocalesJson? _localeData;
#nullable disable
private static Dictionary<LocaleKeys, string> LoadJsonLanguage(string languageCode)
{
@@ -158,18 +132,29 @@ namespace Ryujinx.Ava.Common.Locale
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}!");
}
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))
continue;
localeStrings[localeKey] =
locale.Translations.TryGetValue(languageCode, out string val) && val != string.Empty
? val
: locale.Translations[DefaultLanguageCode];
var str = locale.Translations.TryGetValue(languageCode, out string val) && !string.IsNullOrEmpty(val)
? val
: 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;

View File

@@ -14,7 +14,7 @@ namespace Ryujinx.Ava.Common.Markup
{
internal abstract class BasicMarkupExtension<T> : MarkupExtension
{
public virtual string Name => "Item";
public abstract string Name { get; }
public virtual Action<object, T?>? Setter => null;
protected abstract T? Value { get; }

View File

@@ -6,16 +6,19 @@ namespace Ryujinx.Ava.Common.Markup
{
internal class IconExtension(string iconString) : BasicMarkupExtension<Icon>
{
public override string Name => "Icon";
protected override Icon Value => new() { Value = iconString };
}
internal class SpinningIconExtension(string iconString) : BasicMarkupExtension<Icon>
{
public override string Name => "SIcon";
protected override Icon Value => new() { Value = iconString, Animation = IconAnimation.Spin };
}
internal class LocaleExtension(LocaleKeys key) : BasicMarkupExtension<string>
{
public override string Name => "Translation";
protected override string Value => LocaleManager.Instance[key];
protected override void ConfigureBindingExtension(CompiledBindingExtension bindingExtension)

View File

@@ -65,7 +65,7 @@ namespace Ryujinx.Ava
}
public static AppBuilder BuildAvaloniaApp() =>
AppBuilder.Configure<App>()
AppBuilder.Configure<RyujinxApp>()
.UsePlatformDetect()
.With(new X11PlatformOptions
{
@@ -100,7 +100,7 @@ namespace Ryujinx.Ava
// Delete backup files after updating.
Task.Run(Updater.CleanupUpdate);
Console.Title = $"{App.FullAppName} Console {Version}";
Console.Title = $"{RyujinxApp.FullAppName} Console {Version}";
// Hook unhandled exception and process exit events.
AppDomain.CurrentDomain.UnhandledException += (sender, e)
@@ -225,7 +225,7 @@ namespace Ryujinx.Ava
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();
var enabledLogLevels = Logger.GetEnabledLevels().ToArray();

View File

@@ -1,5 +1,5 @@
<Application
x:Class="Ryujinx.Ava.App"
x:Class="Ryujinx.Ava.RyujinxApp"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sty="using:FluentAvalonia.Styling">

View File

@@ -1,5 +1,6 @@
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Input.Platform;
using Avalonia.Markup.Xaml;
using Avalonia.Platform;
using Avalonia.Styling;
@@ -19,7 +20,7 @@ using System.Diagnostics;
namespace Ryujinx.Ava
{
public class App : Application
public class RyujinxApp : Application
{
internal static string FormatTitle(LocaleKeys? windowTitleKey = null)
=> windowTitleKey is null
@@ -32,8 +33,8 @@ namespace Ryujinx.Ava
.ApplicationLifetime.Cast<IClassicDesktopStyleApplicationLifetime>()
.MainWindow.Cast<MainWindow>();
public static IClassicDesktopStyleApplicationLifetime DesktopLifetime => Current!
.ApplicationLifetime.Cast<IClassicDesktopStyleApplicationLifetime>();
public static bool IsClipboardAvailable(out IClipboard clipboard)
=> (clipboard = MainWindow.Clipboard) != null;
public static void SetTaskbarProgress(TaskBarProgressBarState state) => MainWindow.PlatformFeatures.SetTaskBarProgressBarState(state);
public static void SetTaskbarProgressValue(ulong current, ulong total) => MainWindow.PlatformFeatures.SetTaskBarProgressBarValue(current, total);
@@ -135,7 +136,7 @@ namespace Ryujinx.Ava
};
public static ThemeVariant DetectSystemTheme() =>
Current is App { PlatformSettings: not null } app
Current is RyujinxApp { PlatformSettings: not null } app
? ConvertThemeVariant(app.PlatformSettings.GetColorValues().ThemeVariant)
: ThemeVariant.Default;
}

View File

@@ -43,17 +43,19 @@ namespace Ryujinx.Ava.UI.Controls
if (sender is not Button { Content: TextBlock idText })
return;
if (!RyujinxApp.IsClipboardAvailable(out var clipboard))
return;
if (App.MainWindow.Clipboard is { } clipboard)
{
var appData = mwvm.Applications.FirstOrDefault(it => it.IdString == idText.Text);
if (appData is null)
return;
var appData = mwvm.Applications.FirstOrDefault(it => it.IdString == idText.Text);
if (appData is null)
return;
await clipboard.SetTextAsync(appData.IdString);
await clipboard.SetTextAsync(appData.IdString);
NotificationHelper.Show("Copied Title ID", $"{appData.Name} ({appData.IdString})", NotificationType.Information);
}
NotificationHelper.ShowInformation(
"Copied Title ID",
$"{appData.Name} ({appData.IdString})");
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -19,7 +19,7 @@ namespace Ryujinx.Ava.UI.Helpers
AvaLogger.Sink = new LoggerAdapter();
}
private static RyuLogger.Log? GetLog(AvaLogLevel level)
private static RyuLogger.Log? GetLog(AvaLogLevel level, string area)
{
return level switch
{
@@ -27,7 +27,7 @@ namespace Ryujinx.Ava.UI.Helpers
AvaLogLevel.Debug => RyuLogger.Debug,
AvaLogLevel.Information => RyuLogger.Debug,
AvaLogLevel.Warning => RyuLogger.Debug,
AvaLogLevel.Error => RyuLogger.Error,
AvaLogLevel.Error => area is "IME" ? RyuLogger.Debug : RyuLogger.Error,
AvaLogLevel.Fatal => RyuLogger.Error,
_ => throw new ArgumentOutOfRangeException(nameof(level), level, null),
};
@@ -35,17 +35,17 @@ namespace Ryujinx.Ava.UI.Helpers
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)
{
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)
{
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)

View File

@@ -62,9 +62,46 @@ namespace Ryujinx.Ava.UI.Helpers
_notifications.Add(new Notification(title, text, type, delay, onClick, onClose));
}
public static void ShowError(string message)
{
Show(LocaleManager.Instance[LocaleKeys.DialogErrorTitle], $"{LocaleManager.Instance[LocaleKeys.DialogErrorMessage]}\n\n{message}", NotificationType.Error);
}
public static void ShowError(string message) =>
ShowError(
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);
}
}

View File

@@ -1,6 +1,7 @@
using Avalonia;
using Avalonia.Data;
using Avalonia.Data.Converters;
using Gommon;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.UI.Common.Models;
using System;
@@ -32,11 +33,11 @@ namespace Ryujinx.Ava.UI.Helpers
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
{
return LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.TitleXCISavingLabel, app.CurrentSavingsB / _bytesPerMB);
return LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.TitleXCISavingLabel, (app.CurrentSavingsB / _bytesPerMB).CoerceAtLeast(0));
}
}

View File

@@ -1,3 +1,4 @@
using Gommon;
using LibHac.Fs;
using LibHac.Ncm;
using Ryujinx.Ava.UI.ViewModels;
@@ -47,7 +48,7 @@ namespace Ryujinx.Ava.UI.Models
TitleId = info.ProgramId;
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;

View File

@@ -1,6 +1,7 @@
using Avalonia;
using Avalonia.Controls;
using Gommon;
using Ryujinx.Common;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using Ryujinx.UI.Common.Configuration;
@@ -31,15 +32,6 @@ namespace Ryujinx.Ava.UI.Renderer
Initialize();
}
public static readonly string[] KnownGreatMetalTitles =
[
"01006A800016E000", // Smash Ultimate
"0100000000010000", // Super Mario Odyessy
"01008C0016544000", // Sea of Stars
"01005CA01580E000", // Persona 5
"010028600EBDA000", // Mario 3D World
];
public GraphicsBackend Backend =>
EmbeddedWindow switch
{
@@ -53,16 +45,8 @@ namespace Ryujinx.Ava.UI.Renderer
{
InitializeComponent();
switch (ConfigurationState.Instance.Graphics.GraphicsBackend.Value)
switch (TitleIDs.SelectGraphicsBackend(titleId, ConfigurationState.Instance.Graphics.GraphicsBackend))
{
case GraphicsBackend.Auto:
EmbeddedWindow =
OperatingSystem.IsMacOS() &&
RuntimeInformation.ProcessArchitecture == Architecture.Arm64 &&
KnownGreatMetalTitles.ContainsIgnoreCase(titleId)
? new EmbeddedWindowMetal()
: new EmbeddedWindowVulkan();
break;
case GraphicsBackend.OpenGl:
EmbeddedWindow = new EmbeddedWindowOpenGL();
break;

View File

@@ -51,7 +51,7 @@ namespace Ryujinx.Ava.UI.ViewModels
public AboutWindowViewModel()
{
Version = App.FullAppName + "\n" + Program.Version;
Version = RyujinxApp.FullAppName + "\n" + Program.Version;
UpdateLogoTheme(ConfigurationState.Instance.UI.BaseStyle.Value);
ThemeManager.ThemeChanged += ThemeManager_ThemeChanged;
@@ -64,7 +64,7 @@ namespace Ryujinx.Ava.UI.ViewModels
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 themeSuffix = isDarkTheme ? "Dark.png" : "Light.png";

View File

@@ -1,3 +1,4 @@
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
@@ -11,5 +12,13 @@ namespace Ryujinx.Ava.UI.ViewModels
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected void OnPropertiesChanged(params ReadOnlySpan<string> propertyNames)
{
foreach (var propertyName in propertyNames)
{
OnPropertyChanged(propertyName);
}
}
}
}

View File

@@ -9,6 +9,7 @@ using Avalonia.Threading;
using DynamicData;
using DynamicData.Binding;
using FluentAvalonia.UI.Controls;
using Gommon;
using LibHac.Common;
using LibHac.Ns;
using Ryujinx.Ava.Common;
@@ -109,13 +110,8 @@ namespace Ryujinx.Ava.UI.ViewModels
private bool _areMimeTypesRegistered = FileAssociationHelper.AreMimeTypesRegistered;
private bool _canUpdate = true;
private Cursor _cursor;
private string _title;
private ApplicationData _currentApplicationData;
private readonly AutoResetEvent _rendererWaitEvent;
private WindowState _windowState;
private double _windowWidth;
private double _windowHeight;
private int _customVSyncInterval;
private int _customVSyncIntervalPercentageProxy;
@@ -124,14 +120,15 @@ namespace Ryujinx.Ava.UI.ViewModels
public ApplicationData ListSelectedApplication;
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.
// 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.
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; }
@@ -216,7 +213,7 @@ namespace Ryujinx.Ava.UI.ViewModels
public bool CanUpdate
{
get => _canUpdate && EnableNonGameRunningControls && Updater.CanUpdate(false);
get => _canUpdate && EnableNonGameRunningControls && Updater.CanUpdate();
set
{
_canUpdate = value;
@@ -226,12 +223,8 @@ namespace Ryujinx.Ava.UI.ViewModels
public Cursor Cursor
{
get => _cursor;
set
{
_cursor = value;
OnPropertyChanged();
}
get => Window.Cursor;
set => Window.Cursor = value;
}
public ReadOnlyObservableCollection<ApplicationData> AppsObservableList
@@ -813,35 +806,23 @@ namespace Ryujinx.Ava.UI.ViewModels
public WindowState WindowState
{
get => _windowState;
get => Window.WindowState;
internal set
{
_windowState = value;
OnPropertyChanged();
Window.WindowState = value;
}
}
public double WindowWidth
{
get => _windowWidth;
set
{
_windowWidth = value;
OnPropertyChanged();
}
get => Window.Width;
set => Window.Width = value;
}
public double WindowHeight
{
get => _windowHeight;
set
{
_windowHeight = value;
OnPropertyChanged();
}
get => Window.Height;
set => Window.Height = value;
}
public bool IsGrid => Glyph == Glyph.Grid;
@@ -889,11 +870,11 @@ namespace Ryujinx.Ava.UI.ViewModels
public string Title
{
get => _title;
get => Window.Title;
set
{
_title = value;
Window.Title = value;
OnPropertyChanged();
}
}
@@ -2051,7 +2032,7 @@ namespace Ryujinx.Ava.UI.ViewModels
Dispatcher.UIThread.InvokeAsync(() =>
{
Title = App.FormatTitle();
Title = RyujinxApp.FormatTitle();
});
}

View File

@@ -71,8 +71,7 @@ namespace Ryujinx.Ava.UI.ViewModels
{
_resolutionScale = value;
OnPropertyChanged(nameof(CustomResolutionScale));
OnPropertyChanged(nameof(IsCustomResolutionScaleActive));
OnPropertiesChanged(nameof(CustomResolutionScale), nameof(IsCustomResolutionScaleActive));
}
}
@@ -181,8 +180,9 @@ namespace Ryujinx.Ava.UI.ViewModels
int newInterval = (int)((value / 100f) * 60);
_customVSyncInterval = newInterval;
_customVSyncIntervalPercentageProxy = value;
OnPropertyChanged((nameof(CustomVSyncInterval)));
OnPropertyChanged((nameof(CustomVSyncIntervalPercentageText)));
OnPropertiesChanged(
nameof(CustomVSyncInterval),
nameof(CustomVSyncIntervalPercentageText));
}
}
@@ -190,7 +190,7 @@ namespace Ryujinx.Ava.UI.ViewModels
{
get
{
string text = CustomVSyncIntervalPercentageProxy.ToString() + "%";
string text = CustomVSyncIntervalPercentageProxy + "%";
return text;
}
}
@@ -221,8 +221,9 @@ namespace Ryujinx.Ava.UI.ViewModels
_customVSyncInterval = value;
int newPercent = (int)((value / 60f) * 100);
_customVSyncIntervalPercentageProxy = newPercent;
OnPropertyChanged(nameof(CustomVSyncIntervalPercentageProxy));
OnPropertyChanged(nameof(CustomVSyncIntervalPercentageText));
OnPropertiesChanged(
nameof(CustomVSyncIntervalPercentageProxy),
nameof(CustomVSyncIntervalPercentageText));
OnPropertyChanged();
}
}
@@ -749,7 +750,7 @@ namespace Ryujinx.Ava.UI.ViewModels
config.ToFileFormat().SaveConfig(Program.ConfigurationPath);
MainWindow.UpdateGraphicsConfig();
MainWindow.MainWindowViewModel.VSyncModeSettingChanged();
RyujinxApp.MainWindow.ViewModel.VSyncModeSettingChanged();
SaveSettingsEvent?.Invoke();

View File

@@ -91,39 +91,42 @@ namespace Ryujinx.Ava.UI.ViewModels
private void SortingChanged()
{
OnPropertyChanged(nameof(IsSortedByName));
OnPropertyChanged(nameof(IsSortedBySaved));
OnPropertyChanged(nameof(SortingAscending));
OnPropertyChanged(nameof(SortingField));
OnPropertyChanged(nameof(SortingFieldName));
OnPropertiesChanged(
nameof(IsSortedByName),
nameof(IsSortedBySaved),
nameof(SortingAscending),
nameof(SortingField),
nameof(SortingFieldName));
SortAndFilter();
}
private void DisplayedChanged()
{
OnPropertyChanged(nameof(Status));
OnPropertyChanged(nameof(DisplayedXCIFiles));
OnPropertyChanged(nameof(SelectedDisplayedXCIFiles));
OnPropertiesChanged(nameof(Status), nameof(DisplayedXCIFiles), nameof(SelectedDisplayedXCIFiles));
}
private void ApplicationsChanged()
{
OnPropertyChanged(nameof(AllXCIFiles));
OnPropertyChanged(nameof(Status));
OnPropertyChanged(nameof(PotentialSavings));
OnPropertyChanged(nameof(ActualSavings));
OnPropertyChanged(nameof(CanTrim));
OnPropertyChanged(nameof(CanUntrim));
OnPropertiesChanged(
nameof(AllXCIFiles),
nameof(Status),
nameof(PotentialSavings),
nameof(ActualSavings),
nameof(CanTrim),
nameof(CanUntrim));
DisplayedChanged();
SortAndFilter();
}
private void SelectionChanged(bool displayedChanged = true)
{
OnPropertyChanged(nameof(Status));
OnPropertyChanged(nameof(CanTrim));
OnPropertyChanged(nameof(CanUntrim));
OnPropertyChanged(nameof(SelectedXCIFiles));
OnPropertiesChanged(
nameof(Status),
nameof(CanTrim),
nameof(CanUntrim),
nameof(SelectedXCIFiles));
if (displayedChanged)
OnPropertyChanged(nameof(SelectedDisplayedXCIFiles));
@@ -131,11 +134,12 @@ namespace Ryujinx.Ava.UI.ViewModels
private void ProcessingChanged()
{
OnPropertyChanged(nameof(Processing));
OnPropertyChanged(nameof(Cancel));
OnPropertyChanged(nameof(Status));
OnPropertyChanged(nameof(CanTrim));
OnPropertyChanged(nameof(CanUntrim));
OnPropertiesChanged(
nameof(Processing),
nameof(Cancel),
nameof(Status),
nameof(CanTrim),
nameof(CanUntrim));
}
private IEnumerable<XCITrimmerFileModel> GetSelectedDisplayedXCIFiles()
@@ -360,7 +364,7 @@ namespace Ryujinx.Ava.UI.ViewModels
value = _processingApplication.Value with { PercentageProgress = null };
if (value.HasValue)
_displayedXCIFiles.ReplaceWith(value.Value);
_displayedXCIFiles.ReplaceWith(value);
_processingApplication = value;
OnPropertyChanged();

View File

@@ -17,8 +17,7 @@
Margin="7, 0"
Height="25"
Width="25"
ToolTip.Tip="{Binding Title}"
Source="resm:Ryujinx.UI.Common.Resources.Logo_Thiccjinx.png?assembly=Ryujinx.UI.Common" />
ToolTip.Tip="{Binding Title}" />
<Menu
Name="Menu"
Height="32"

View File

@@ -3,8 +3,6 @@ using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Threading;
using Gommon;
using LibHac.Ncm;
using LibHac.Tools.FsSystem.NcaUtils;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.ViewModels;
@@ -12,8 +10,6 @@ using Ryujinx.Ava.UI.Windows;
using Ryujinx.Common;
using Ryujinx.Common.Utilities;
using Ryujinx.HLE.HOS.Services.Nfc.AmiiboDecryption;
using Ryujinx.HLE;
using Ryujinx.UI.App.Common;
using Ryujinx.UI.Common;
using Ryujinx.UI.Common.Configuration;
using Ryujinx.UI.Common.Helper;
@@ -33,6 +29,7 @@ namespace Ryujinx.Ava.UI.Views.Main
InitializeComponent();
RyuLogo.IsVisible = !ConfigurationState.Instance.ShowTitleBar;
RyuLogo.Source = MainWindowViewModel.IconBitmap;
ToggleFileTypesMenuItem.ItemsSource = GenerateToggleFileTypeItems();
ChangeLanguageMenuItem.ItemsSource = GenerateLanguageMenuItems();
@@ -124,26 +121,13 @@ namespace Ryujinx.Ava.UI.Views.Main
ViewModel.LoadConfigurableHotKeys();
}
public static readonly AppletMetadata MiiApplet = new("miiEdit", 0x0100000000001009);
public async void OpenMiiApplet(object sender, RoutedEventArgs e)
{
const string AppletName = "miiEdit";
const ulong AppletProgramId = 0x0100000000001009;
const string AppletVersion = "1.0.0";
string contentPath = ViewModel.ContentManager.GetInstalledContentPath(AppletProgramId, StorageId.BuiltInSystem, NcaContentType.Program);
if (!string.IsNullOrEmpty(contentPath))
if (MiiApplet.CanStart(ViewModel.ContentManager, out var appData, out var nacpData))
{
ApplicationData applicationData = new()
{
Name = AppletName,
Id = AppletProgramId,
Path = contentPath
};
var nacpData = StructHelpers.CreateCustomNacpData(AppletName, AppletVersion);
await ViewModel.LoadApplication(applicationData, ViewModel.IsFullScreen || ViewModel.StartGamesInFullscreen, nacpData);
await ViewModel.LoadApplication(appData, ViewModel.IsFullScreen || ViewModel.StartGamesInFullscreen, nacpData);
}
}

View File

@@ -132,14 +132,7 @@
</Flyout>
</Button.Flyout>
</Button>
<Border
Width="2"
Height="12"
Margin="0"
BorderBrush="Gray"
Background="Gray"
BorderThickness="1"
IsVisible="{Binding !ShowLoadProgress}" />
<controls:MiniVerticalSeparator IsVisible="{Binding !ShowLoadProgress}" />
<TextBlock
Name="DockedStatus"
Margin="5,0,5,0"
@@ -149,14 +142,7 @@
PointerReleased="DockedStatus_PointerReleased"
Text="{Binding DockedStatusText}"
TextAlignment="Start" />
<Border
Width="2"
Height="12"
Margin="0"
BorderBrush="Gray"
Background="Gray"
BorderThickness="1"
IsVisible="{Binding !ShowLoadProgress}" />
<controls:MiniVerticalSeparator IsVisible="{Binding !ShowLoadProgress}" />
<SplitButton
Name="AspectRatioStatus"
Padding="5,0,5,0"
@@ -203,14 +189,7 @@
</MenuFlyout>
</SplitButton.Flyout>
</SplitButton>
<Border
Width="2"
Height="12"
Margin="0"
BorderBrush="Gray"
Background="Gray"
BorderThickness="1"
IsVisible="{Binding !ShowLoadProgress}" />
<controls:MiniVerticalSeparator IsVisible="{Binding !ShowLoadProgress}" />
<ToggleSplitButton
Name="VolumeStatus"
Padding="5,0,5,0"
@@ -254,14 +233,7 @@
</Flyout>
</ToggleSplitButton.Flyout>
</ToggleSplitButton>
<Border
Width="2"
Height="12"
Margin="0"
BorderBrush="Gray"
Background="Gray"
BorderThickness="1"
IsVisible="{Binding !ShowLoadProgress}" />
<controls:MiniVerticalSeparator IsVisible="{Binding !ShowLoadProgress}" />
<TextBlock
Margin="5,0,5,0"
HorizontalAlignment="Left"
@@ -269,14 +241,7 @@
IsVisible="{Binding !ShowLoadProgress}"
Text="{Binding GameStatusText}"
TextAlignment="Start" />
<Border
Width="2"
Height="12"
Margin="0"
BorderBrush="Gray"
Background="Gray"
BorderThickness="1"
IsVisible="{Binding !ShowLoadProgress}" />
<controls:MiniVerticalSeparator IsVisible="{Binding !ShowLoadProgress}" />
<TextBlock
Margin="5,0,5,0"
HorizontalAlignment="Left"
@@ -298,13 +263,7 @@
VerticalAlignment="Center"
IsVisible="{Binding ShowShaderCompilationHint}"
Text="{Binding ShaderCountText}" />
<Border
Width="2"
Height="12"
Margin="0"
BorderBrush="Gray"
BorderThickness="1"
IsVisible="{Binding ShowShaderCompilationHint}" />
<controls:MiniVerticalSeparator IsVisible="{Binding ShowShaderCompilationHint}" />
<TextBlock
Margin="5,0,5,0"
HorizontalAlignment="Left"
@@ -312,14 +271,7 @@
IsVisible="{Binding !ShowLoadProgress}"
Text="{Binding BackendText}"
TextAlignment="Start" />
<Border
Width="2"
Height="12"
Margin="0"
BorderBrush="Gray"
Background="Gray"
BorderThickness="1"
IsVisible="{Binding !ShowLoadProgress}" />
<controls:MiniVerticalSeparator IsVisible="{Binding !ShowLoadProgress}" />
<TextBlock
Margin="5,0,0,0"
HorizontalAlignment="Left"
@@ -334,13 +286,7 @@
VerticalAlignment="Center"
IsVisible="{Binding ShowFirmwareStatus}"
Orientation="Horizontal">
<Border
Width="2"
Height="12"
Margin="0"
BorderBrush="Gray"
BorderThickness="1"
IsVisible="{Binding IsGameRunning}" />
<controls:MiniVerticalSeparator IsVisible="{Binding IsGameRunning}" />
<TextBlock
Name="FirmwareStatus"
Margin="5, 0, 0, 0"

View File

@@ -62,12 +62,7 @@ namespace Ryujinx.Ava.UI.Views.Main
// Change the volume by 5% at a time
float newValue = Window.ViewModel.Volume + (float)e.Delta.Y * 0.05f;
Window.ViewModel.Volume = newValue switch
{
< 0 => 0,
> 1 => 1,
_ => newValue,
};
Window.ViewModel.Volume = Math.Clamp(newValue, 0, 1);
e.Handled = true;
}

View File

@@ -16,7 +16,7 @@ namespace Ryujinx.Ava.UI.Windows
InitializeComponent();
Title = App.FormatTitle(LocaleKeys.Amiibo);
Title = RyujinxApp.FormatTitle(LocaleKeys.Amiibo);
}
public AmiiboWindow()
@@ -27,7 +27,7 @@ namespace Ryujinx.Ava.UI.Windows
if (Program.PreviewerDetached)
{
Title = App.FormatTitle(LocaleKeys.Amiibo);
Title = RyujinxApp.FormatTitle(LocaleKeys.Amiibo);
}
}

View File

@@ -28,7 +28,7 @@ namespace Ryujinx.Ava.UI.Windows
InitializeComponent();
Title = App.FormatTitle(LocaleKeys.CheatWindowTitle);
Title = RyujinxApp.FormatTitle(LocaleKeys.CheatWindowTitle);
}
public CheatWindow(VirtualFileSystem virtualFileSystem, string titleId, string titleName, string titlePath)
@@ -93,7 +93,7 @@ namespace Ryujinx.Ava.UI.Windows
DataContext = this;
Title = App.FormatTitle(LocaleKeys.CheatWindowTitle);
Title = RyujinxApp.FormatTitle(LocaleKeys.CheatWindowTitle);
}
public void Save()

View File

@@ -9,11 +9,6 @@
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
xmlns:controls="clr-namespace:Ryujinx.Ava.UI.Controls"
xmlns:main="clr-namespace:Ryujinx.Ava.UI.Views.Main"
Cursor="{Binding Cursor}"
Title="{Binding Title}"
WindowState="{Binding WindowState}"
Width="{Binding WindowWidth}"
Height="{Binding WindowHeight}"
MinWidth="800"
MinHeight="500"
d:DesignHeight="720"

View File

@@ -39,8 +39,6 @@ namespace Ryujinx.Ava.UI.Windows
{
public partial class MainWindow : StyleableAppWindow
{
internal static MainWindowViewModel MainWindowViewModel { get; private set; }
public MainWindowViewModel ViewModel { get; }
internal readonly AvaHostUIHandler UiHandler;
@@ -76,7 +74,7 @@ namespace Ryujinx.Ava.UI.Windows
public MainWindow()
{
DataContext = ViewModel = MainWindowViewModel = new MainWindowViewModel
DataContext = ViewModel = new MainWindowViewModel
{
Window = this
};
@@ -86,17 +84,19 @@ namespace Ryujinx.Ava.UI.Windows
UiHandler = new AvaHostUIHandler(this);
ViewModel.Title = App.FormatTitle();
ViewModel.Title = RyujinxApp.FormatTitle();
TitleBar.ExtendsContentIntoTitleBar = !ConfigurationState.Instance.ShowTitleBar;
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.
StatusBarHeight = StatusBarView.StatusBar.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;
@@ -117,7 +117,7 @@ namespace Ryujinx.Ava.UI.Windows
/// </summary>
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);
}
@@ -167,24 +167,28 @@ namespace Ryujinx.Ava.UI.Windows
{
Dispatcher.UIThread.Post(() =>
{
var ldnGameDataArray = e.LdnData;
ViewModel.LastLdnGameData = ldnGameDataArray;
var ldnGameDataArray = e.LdnData.ToList();
ViewModel.LdnData.Clear();
foreach (var application in ViewModel.Applications)
{
ViewModel.LdnData[application.IdString] = new LdnGameDataArray(
ldnGameDataArray,
ref application.ControlHolder.Value
);
UpdateApplicationWithLdnData(application);
}
ViewModel.RefreshView();
});
}
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 = ldnGameData.Sum(game => game.PlayerCount);
application.GameCount = ldnGameData.Count();
application.PlayerCount = ldnGameDatas.PlayerCount;
application.GameCount = ldnGameDatas.GameCount;
}
else
{

View File

@@ -14,7 +14,7 @@ namespace Ryujinx.Ava.UI.Windows
public SettingsWindow(VirtualFileSystem virtualFileSystem, ContentManager contentManager)
{
Title = App.FormatTitle(LocaleKeys.Settings);
Title = RyujinxApp.FormatTitle(LocaleKeys.Settings);
DataContext = ViewModel = new SettingsViewModel(virtualFileSystem, contentManager);

View File

@@ -76,7 +76,7 @@ namespace Ryujinx.Ava
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(
LocaleManager.Instance[LocaleKeys.DialogUpdaterConvertFailedMessage],
@@ -159,7 +159,7 @@ namespace Ryujinx.Ava
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(
LocaleManager.Instance[LocaleKeys.DialogUpdaterConvertFailedGithubMessage],
@@ -266,7 +266,7 @@ namespace Ryujinx.Ava
SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterDownloading],
IconSource = new SymbolIconSource { Symbol = Symbol.Download },
ShowProgressBar = true,
XamlRoot = App.MainWindow,
XamlRoot = RyujinxApp.MainWindow,
};
taskDialog.Opened += (s, e) =>
@@ -490,7 +490,7 @@ namespace Ryujinx.Ava
bytesWritten += readSize;
taskDialog.SetProgressBarState(GetPercentage(bytesWritten, totalBytes), TaskDialogProgressState.Normal);
App.SetTaskbarProgressValue(bytesWritten, totalBytes);
RyujinxApp.SetTaskbarProgressValue(bytesWritten, totalBytes);
updateFileStream.Write(buffer, 0, readSize);
}