Compare commits
56 Commits
e4ef19fdf0
...
1.2.82
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b16b844760 | ||
|
|
bc07bc482d | ||
|
|
61975ca44d | ||
|
|
66054dd225 | ||
|
|
b1f61e5143 | ||
|
|
0d7d0e8092 | ||
|
|
aa2178dbe5 | ||
|
|
f92d09711b | ||
|
|
45ee8cd0e8 | ||
|
|
395bbd144a | ||
|
|
744d813b87 | ||
|
|
7d59ada798 | ||
|
|
a4b5304935 | ||
|
|
0965ee905d | ||
|
|
855161b23b | ||
|
|
6b55d158b7 | ||
|
|
91f73a4891 | ||
|
|
883d4d863a | ||
|
|
ca5de909a1 | ||
|
|
5172567b08 | ||
|
|
6fe4cee7c0 | ||
|
|
8623452abc | ||
|
|
17e8ae1d9a | ||
|
|
7591b07fce | ||
|
|
89b4389ed2 | ||
|
|
d9ee729199 | ||
|
|
ba0cd13cff | ||
|
|
501b199e24 | ||
|
|
8aecccadb8 | ||
|
|
e23d610f49 | ||
|
|
f6822f7358 | ||
|
|
d3f84a1305 | ||
|
|
06d34a5992 | ||
|
|
e8e1dc6619 | ||
|
|
c5603d4c36 | ||
|
|
05b56730d6 | ||
|
|
43f7b000ca | ||
|
|
ad89cf39b6 | ||
|
|
96c33a0b92 | ||
|
|
e0db55df46 | ||
|
|
30fef8e96e | ||
|
|
9cb5f5689b | ||
|
|
a205ec374b | ||
|
|
aab9b58542 | ||
|
|
daa648dc40 | ||
|
|
1024aa8757 | ||
|
|
13388e972a | ||
|
|
1eb78872d8 | ||
|
|
fe9fe2a10f | ||
|
|
6ab899f621 | ||
|
|
faacec9801 | ||
|
|
55fdb3f6b2 | ||
|
|
1129ab0e8c | ||
|
|
b6b391b2cf | ||
|
|
f3cf03495d | ||
|
|
7bce8206d5 |
4
.github/workflows/canary.yml
vendored
4
.github/workflows/canary.yml
vendored
@@ -29,7 +29,7 @@ env:
|
|||||||
jobs:
|
jobs:
|
||||||
tag:
|
tag:
|
||||||
name: Create tag
|
name: Create tag
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- name: Get version info
|
- name: Get version info
|
||||||
id: version_info
|
id: version_info
|
||||||
@@ -202,7 +202,7 @@ jobs:
|
|||||||
|
|
||||||
macos_release:
|
macos_release:
|
||||||
name: Release MacOS universal
|
name: Release MacOS universal
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
|||||||
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
@@ -18,7 +18,7 @@ env:
|
|||||||
jobs:
|
jobs:
|
||||||
tag:
|
tag:
|
||||||
name: Create tag
|
name: Create tag
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- name: Get version info
|
- name: Get version info
|
||||||
id: version_info
|
id: version_info
|
||||||
@@ -183,7 +183,7 @@ jobs:
|
|||||||
|
|
||||||
macos_release:
|
macos_release:
|
||||||
name: Release MacOS universal
|
name: Release MacOS universal
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
|||||||
@@ -39,12 +39,12 @@
|
|||||||
<p align="center">
|
<p align="center">
|
||||||
Click below to join the Discord:
|
Click below to join the Discord:
|
||||||
<br>
|
<br>
|
||||||
<a href="https://discord.gg/dHPrkBkkyA">
|
<a href="https://discord.gg/PEuzjrFXUA">
|
||||||
<img src="https://img.shields.io/discord/1294443224030511104?color=5865F2&label=Ryubing&logo=discord&logoColor=white" alt="Discord">
|
<img src="https://img.shields.io/discord/1294443224030511104?color=5865F2&label=Ryubing&logo=discord&logoColor=white" alt="Discord">
|
||||||
</a>
|
</a>
|
||||||
<br>
|
<br>
|
||||||
<br>
|
<br>
|
||||||
<img src="https://raw.githubusercontent.com/GreemDev/Ryujinx/refs/heads/master/docs/shell.png">
|
<img src="https://raw.githubusercontent.com/Ryubing/Ryujinx/refs/heads/master/docs/shell.png">
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|||||||
@@ -969,6 +969,7 @@
|
|||||||
0100751007ADA000,"Don't Starve: Nintendo Switch Edition",nvdec,playable,2022-02-05 20:43:34
|
0100751007ADA000,"Don't Starve: Nintendo Switch Edition",nvdec,playable,2022-02-05 20:43:34
|
||||||
010088B010DD2000,"Dongo Adventure",,playable,2022-10-04 16:22:26
|
010088B010DD2000,"Dongo Adventure",,playable,2022-10-04 16:22:26
|
||||||
0100C1F0051B6000,"Donkey Kong Country™: Tropical Freeze",,playable,2024-08-05 16:46:10
|
0100C1F0051B6000,"Donkey Kong Country™: Tropical Freeze",,playable,2024-08-05 16:46:10
|
||||||
|
01009D901BC56000,"Donkey Kong Country™: Returns HD",gpu,ingame,2025-02-16 13:44:12
|
||||||
0100F2C00F060000,"Doodle Derby",,boots,2020-12-04 22:51:48
|
0100F2C00F060000,"Doodle Derby",,boots,2020-12-04 22:51:48
|
||||||
0100416004C00000,"DOOM",gpu;slow;nvdec;online-broken,ingame,2024-09-23 15:40:07
|
0100416004C00000,"DOOM",gpu;slow;nvdec;online-broken,ingame,2024-09-23 15:40:07
|
||||||
010018900DD00000,"DOOM (1993)",nvdec;online-broken,menus,2022-09-06 13:32:19
|
010018900DD00000,"DOOM (1993)",nvdec;online-broken,menus,2022-09-06 13:32:19
|
||||||
@@ -1249,7 +1250,7 @@
|
|||||||
0100A6B00D4EC000,"Furwind",,playable,2021-02-19 19:44:08
|
0100A6B00D4EC000,"Furwind",,playable,2021-02-19 19:44:08
|
||||||
0100ECE00C0C4000,"Fury Unleashed",crash;services,ingame,2020-10-18 11:52:40
|
0100ECE00C0C4000,"Fury Unleashed",crash;services,ingame,2020-10-18 11:52:40
|
||||||
010070000ED9E000,"Fury Unleashed Demo",,playable,2020-10-08 20:09:21
|
010070000ED9E000,"Fury Unleashed Demo",,playable,2020-10-08 20:09:21
|
||||||
0100E1F013674000,"FUSER™",nvdec;UE4,playable,2022-10-17 20:58:32
|
0100E1F013674000,"FUSER™",nvdec;UE4;slow;gpu,ingame,2025-02-12 16:03:00
|
||||||
0100A7A015E4C000,"Fushigi no Gensokyo Lotus Labyrinth",Needs Update;audio;gpu;nvdec,ingame,2021-01-20 15:30:02
|
0100A7A015E4C000,"Fushigi no Gensokyo Lotus Labyrinth",Needs Update;audio;gpu;nvdec,ingame,2021-01-20 15:30:02
|
||||||
01003C300B274000,"Futari de! Nyanko Daisensou",,playable,2024-01-05 22:26:52
|
01003C300B274000,"Futari de! Nyanko Daisensou",,playable,2024-01-05 22:26:52
|
||||||
010055801134E000,"FUZE Player",online-broken;vulkan-backend-bug,ingame,2022-10-18 12:23:53
|
010055801134E000,"FUZE Player",online-broken;vulkan-backend-bug,ingame,2022-10-18 12:23:53
|
||||||
@@ -2063,7 +2064,7 @@
|
|||||||
010002700C34C000,"Numbala",,playable,2020-05-11 12:01:07
|
010002700C34C000,"Numbala",,playable,2020-05-11 12:01:07
|
||||||
010020500C8C8000,"Number Place 10000",gpu,menus,2021-11-24 09:14:23
|
010020500C8C8000,"Number Place 10000",gpu,menus,2021-11-24 09:14:23
|
||||||
010003701002C000,"Nurse Love Syndrome",,playable,2022-10-13 10:05:22
|
010003701002C000,"Nurse Love Syndrome",,playable,2022-10-13 10:05:22
|
||||||
0000000000000000,"nx-hbmenu",Needs Update;homebrew,boots,2024-04-06 22:05:32
|
,"nx-hbmenu",Needs Update;homebrew,boots,2024-04-06 22:05:32
|
||||||
,"nxquake2",services;crash;homebrew,nothing,2022-08-04 23:14:04
|
,"nxquake2",services;crash;homebrew,nothing,2022-08-04 23:14:04
|
||||||
010049F00EC30000,"Nyan Cat: Lost in Space",online,playable,2021-06-12 13:22:03
|
010049F00EC30000,"Nyan Cat: Lost in Space",online,playable,2021-06-12 13:22:03
|
||||||
01002E6014FC4000,"O---O",,playable,2022-10-29 12:12:14
|
01002E6014FC4000,"O---O",,playable,2022-10-29 12:12:14
|
||||||
@@ -2471,7 +2472,7 @@
|
|||||||
0100AFE00DDAC000,"Royal Roads",,playable,2020-11-17 12:54:38
|
0100AFE00DDAC000,"Royal Roads",,playable,2020-11-17 12:54:38
|
||||||
0100E2C00B414000,"RPG Maker MV",nvdec,playable,2021-01-05 20:12:01
|
0100E2C00B414000,"RPG Maker MV",nvdec,playable,2021-01-05 20:12:01
|
||||||
01005CD015986000,"rRootage Reloaded",,playable,2022-08-05 23:20:18
|
01005CD015986000,"rRootage Reloaded",,playable,2022-08-05 23:20:18
|
||||||
0000000000000000,"RSDKv5u",homebrew,ingame,2024-04-01 16:25:34
|
,"RSDKv5u",homebrew,ingame,2024-04-01 16:25:34
|
||||||
010009B00D33C000,"Rugby Challenge 4",slow;online-broken;UE4,playable,2022-10-06 12:45:53
|
010009B00D33C000,"Rugby Challenge 4",slow;online-broken;UE4,playable,2022-10-06 12:45:53
|
||||||
01006EC00F2CC000,"RUINER",UE4,playable,2022-10-03 14:11:33
|
01006EC00F2CC000,"RUINER",UE4,playable,2022-10-03 14:11:33
|
||||||
010074F00DE4A000,"Run the Fan",,playable,2021-02-27 13:36:28
|
010074F00DE4A000,"Run the Fan",,playable,2021-02-27 13:36:28
|
||||||
@@ -2480,6 +2481,7 @@
|
|||||||
010081C0191D8000,"Rune Factory 3 Special",,playable,2023-10-15 08:32:49
|
010081C0191D8000,"Rune Factory 3 Special",,playable,2023-10-15 08:32:49
|
||||||
010051D00E3A4000,"Rune Factory 4 Special",32-bit;crash;nvdec,ingame,2023-05-06 08:49:17
|
010051D00E3A4000,"Rune Factory 4 Special",32-bit;crash;nvdec,ingame,2023-05-06 08:49:17
|
||||||
010014D01216E000,"Rune Factory 5 (JP)",gpu,ingame,2021-06-01 12:00:36
|
010014D01216E000,"Rune Factory 5 (JP)",gpu,ingame,2021-06-01 12:00:36
|
||||||
|
010071E0145F8000,"Rustler",,playable,2025-02-10 20:17:12
|
||||||
0100E21013908000,"RWBY: Grimm Eclipse - Definitive Edition",online-broken,playable,2022-11-03 10:44:01
|
0100E21013908000,"RWBY: Grimm Eclipse - Definitive Edition",online-broken,playable,2022-11-03 10:44:01
|
||||||
010012C0060F0000,"RXN -Raijin-",nvdec,playable,2021-01-10 16:05:43
|
010012C0060F0000,"RXN -Raijin-",nvdec,playable,2021-01-10 16:05:43
|
||||||
0100B8B012ECA000,"S.N.I.P.E.R. - Hunter Scope",,playable,2021-04-19 15:58:09
|
0100B8B012ECA000,"S.N.I.P.E.R. - Hunter Scope",,playable,2021-04-19 15:58:09
|
||||||
@@ -2673,10 +2675,10 @@
|
|||||||
01004F401BEBE000,"Song of Nunu: A League of Legends Story",,ingame,2024-07-12 18:53:44
|
01004F401BEBE000,"Song of Nunu: A League of Legends Story",,ingame,2024-07-12 18:53:44
|
||||||
0100E5400BF94000,"Songbird Symphony",,playable,2021-02-27 02:44:04
|
0100E5400BF94000,"Songbird Symphony",,playable,2021-02-27 02:44:04
|
||||||
010031D00A604000,"Songbringer",,playable,2020-06-22 10:42:02
|
010031D00A604000,"Songbringer",,playable,2020-06-22 10:42:02
|
||||||
0000000000000000,"Sonic 1 (2013)",crash;homebrew,ingame,2024-04-06 18:31:20
|
,"Sonic 1 (2013)",crash;homebrew,ingame,2024-04-06 18:31:20
|
||||||
0000000000000000,"Sonic 2 (2013)",crash;homebrew,ingame,2024-04-01 16:25:30
|
,"Sonic 2 (2013)",crash;homebrew,ingame,2024-04-01 16:25:30
|
||||||
0000000000000000,"Sonic A.I.R",homebrew,ingame,2024-04-01 16:25:32
|
,"Sonic A.I.R",homebrew,ingame,2024-04-01 16:25:32
|
||||||
0000000000000000,"Sonic CD",crash;homebrew,ingame,2024-04-01 16:25:31
|
,"Sonic CD",crash;homebrew,ingame,2024-04-01 16:25:31
|
||||||
010040E0116B8000,"Sonic Colors: Ultimate",,playable,2022-11-12 21:24:26
|
010040E0116B8000,"Sonic Colors: Ultimate",,playable,2022-11-12 21:24:26
|
||||||
01001270012B6000,"SONIC FORCES™",,playable,2024-07-28 13:11:21
|
01001270012B6000,"SONIC FORCES™",,playable,2024-07-28 13:11:21
|
||||||
01004AD014BF0000,"Sonic Frontiers",gpu;deadlock;amd-vendor-bug;intel-vendor-bug,ingame,2024-09-05 09:18:53
|
01004AD014BF0000,"Sonic Frontiers",gpu;deadlock;amd-vendor-bug;intel-vendor-bug,ingame,2024-09-05 09:18:53
|
||||||
@@ -2693,7 +2695,7 @@
|
|||||||
0100707011722000,"Space Elite Force",,playable,2020-11-27 15:21:05
|
0100707011722000,"Space Elite Force",,playable,2020-11-27 15:21:05
|
||||||
010047B010260000,"Space Pioneer",,playable,2022-10-20 12:24:37
|
010047B010260000,"Space Pioneer",,playable,2022-10-20 12:24:37
|
||||||
010010A009830000,"Space Ribbon",,playable,2022-08-15 17:17:10
|
010010A009830000,"Space Ribbon",,playable,2022-08-15 17:17:10
|
||||||
0000000000000000,"SpaceCadetPinball",homebrew,ingame,2024-04-18 19:30:04
|
,"SpaceCadetPinball",homebrew,ingame,2024-04-18 19:30:04
|
||||||
0100D9B0041CE000,"Spacecats with Lasers",,playable,2022-08-15 17:22:44
|
0100D9B0041CE000,"Spacecats with Lasers",,playable,2022-08-15 17:22:44
|
||||||
010034800FB60000,"Spaceland",,playable,2020-11-01 14:31:56
|
010034800FB60000,"Spaceland",,playable,2020-11-01 14:31:56
|
||||||
010028D0045CE000,"Sparkle 2",,playable,2020-10-19 11:51:39
|
010028D0045CE000,"Sparkle 2",,playable,2020-10-19 11:51:39
|
||||||
@@ -2838,7 +2840,7 @@
|
|||||||
0100000000010000,"Super Mario Odyssey™",nvdec;intel-vendor-bug;mac-bug,playable,2024-08-25 01:32:34
|
0100000000010000,"Super Mario Odyssey™",nvdec;intel-vendor-bug;mac-bug,playable,2024-08-25 01:32:34
|
||||||
010036B0034E4000,"Super Mario Party™",gpu;Needs Update;ldn-works,ingame,2024-06-21 05:10:16
|
010036B0034E4000,"Super Mario Party™",gpu;Needs Update;ldn-works,ingame,2024-06-21 05:10:16
|
||||||
0100BC0018138000,"Super Mario RPG™",gpu;audio;nvdec,ingame,2024-06-19 17:43:42
|
0100BC0018138000,"Super Mario RPG™",gpu;audio;nvdec,ingame,2024-06-19 17:43:42
|
||||||
0000000000000000,"Super Mario World",homebrew,boots,2024-06-13 01:40:31
|
,"Super Mario World",homebrew,boots,2024-06-13 01:40:31
|
||||||
010049900F546000,"Super Mario™ 3D All-Stars",services-horizon;slow;vulkan;amd-vendor-bug,ingame,2024-05-07 02:38:16
|
010049900F546000,"Super Mario™ 3D All-Stars",services-horizon;slow;vulkan;amd-vendor-bug,ingame,2024-05-07 02:38:16
|
||||||
010028600EBDA000,"Super Mario™ 3D World + Bowser’s Fury",ldn-works,playable,2024-07-31 10:45:37
|
010028600EBDA000,"Super Mario™ 3D World + Bowser’s Fury",ldn-works,playable,2024-07-31 10:45:37
|
||||||
01004F8006A78000,"Super Meat Boy",services,playable,2020-04-02 23:10:07
|
01004F8006A78000,"Super Meat Boy",services,playable,2020-04-02 23:10:07
|
||||||
@@ -2987,8 +2989,8 @@
|
|||||||
010015D003EE4000,"The Jackbox Party Pack 2",online-working,playable,2022-08-22 18:23:40
|
010015D003EE4000,"The Jackbox Party Pack 2",online-working,playable,2022-08-22 18:23:40
|
||||||
0100CC80013D6000,"The Jackbox Party Pack 3",slow;online-working,playable,2022-08-22 18:41:06
|
0100CC80013D6000,"The Jackbox Party Pack 3",slow;online-working,playable,2022-08-22 18:41:06
|
||||||
0100E1F003EE8000,"The Jackbox Party Pack 4",online-working,playable,2022-08-22 18:56:34
|
0100E1F003EE8000,"The Jackbox Party Pack 4",online-working,playable,2022-08-22 18:56:34
|
||||||
01006fe0096ac000,"The Jackbox Party Pack 5",ldn-untested,boots,2025-02-03 22:32:00
|
01006fe0096ac000,"The Jackbox Party Pack 5",slow;online-working,ingame,2025-02-14 05:32:00
|
||||||
01005a400db52000,"The Jackbox Party Pack 6",ldn-untested,boots,2025-02-03 22:32:00
|
01005a400db52000,"The Jackbox Party Pack 6",slow;online-working,ingame,2025-02-14 05:26:00
|
||||||
010052C00B184000,"The Journey Down: Chapter One",nvdec,playable,2021-02-24 13:32:41
|
010052C00B184000,"The Journey Down: Chapter One",nvdec,playable,2021-02-24 13:32:41
|
||||||
01006BC00B188000,"The Journey Down: Chapter Three",nvdec,playable,2021-02-24 13:45:27
|
01006BC00B188000,"The Journey Down: Chapter Three",nvdec,playable,2021-02-24 13:45:27
|
||||||
01009AB00B186000,"The Journey Down: Chapter Two",nvdec,playable,2021-02-24 13:32:13
|
01009AB00B186000,"The Journey Down: Chapter Two",nvdec,playable,2021-02-24 13:32:13
|
||||||
|
|||||||
|
@@ -7,6 +7,7 @@ namespace ARMeilleure.Memory
|
|||||||
public const int DefaultGranularity = 65536; // Mapping granularity in Windows.
|
public const int DefaultGranularity = 65536; // Mapping granularity in Windows.
|
||||||
|
|
||||||
public IJitMemoryBlock Block { get; }
|
public IJitMemoryBlock Block { get; }
|
||||||
|
public IJitMemoryAllocator Allocator { get; }
|
||||||
|
|
||||||
public nint Pointer => Block.Pointer;
|
public nint Pointer => Block.Pointer;
|
||||||
|
|
||||||
@@ -21,6 +22,7 @@ namespace ARMeilleure.Memory
|
|||||||
granularity = DefaultGranularity;
|
granularity = DefaultGranularity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Allocator = allocator;
|
||||||
Block = allocator.Reserve(maxSize);
|
Block = allocator.Reserve(maxSize);
|
||||||
_maxSize = maxSize;
|
_maxSize = maxSize;
|
||||||
_sizeGranularity = granularity;
|
_sizeGranularity = granularity;
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ using ARMeilleure.CodeGen;
|
|||||||
using ARMeilleure.CodeGen.Unwinding;
|
using ARMeilleure.CodeGen.Unwinding;
|
||||||
using ARMeilleure.Memory;
|
using ARMeilleure.Memory;
|
||||||
using ARMeilleure.Native;
|
using ARMeilleure.Native;
|
||||||
|
using Humanizer;
|
||||||
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.Memory;
|
using Ryujinx.Memory;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@@ -18,9 +20,8 @@ namespace ARMeilleure.Translation.Cache
|
|||||||
private static readonly int _pageMask = _pageSize - 1;
|
private static readonly int _pageMask = _pageSize - 1;
|
||||||
|
|
||||||
private const int CodeAlignment = 4; // Bytes.
|
private const int CodeAlignment = 4; // Bytes.
|
||||||
private const int CacheSize = 2047 * 1024 * 1024;
|
private const int CacheSize = 256 * 1024 * 1024;
|
||||||
|
|
||||||
private static ReservedRegion _jitRegion;
|
|
||||||
private static JitCacheInvalidation _jitCacheInvalidator;
|
private static JitCacheInvalidation _jitCacheInvalidator;
|
||||||
|
|
||||||
private static CacheMemoryAllocator _cacheAllocator;
|
private static CacheMemoryAllocator _cacheAllocator;
|
||||||
@@ -30,6 +31,9 @@ namespace ARMeilleure.Translation.Cache
|
|||||||
private static readonly Lock _lock = new();
|
private static readonly Lock _lock = new();
|
||||||
private static bool _initialized;
|
private static bool _initialized;
|
||||||
|
|
||||||
|
private static readonly List<ReservedRegion> _jitRegions = new();
|
||||||
|
private static int _activeRegionIndex = 0;
|
||||||
|
|
||||||
[SupportedOSPlatform("windows")]
|
[SupportedOSPlatform("windows")]
|
||||||
[LibraryImport("kernel32.dll", SetLastError = true)]
|
[LibraryImport("kernel32.dll", SetLastError = true)]
|
||||||
public static partial nint FlushInstructionCache(nint hProcess, nint lpAddress, nuint dwSize);
|
public static partial nint FlushInstructionCache(nint hProcess, nint lpAddress, nuint dwSize);
|
||||||
@@ -48,7 +52,9 @@ namespace ARMeilleure.Translation.Cache
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_jitRegion = new ReservedRegion(allocator, CacheSize);
|
ReservedRegion firstRegion = new(allocator, CacheSize);
|
||||||
|
_jitRegions.Add(firstRegion);
|
||||||
|
_activeRegionIndex = 0;
|
||||||
|
|
||||||
if (!OperatingSystem.IsWindows() && !OperatingSystem.IsMacOS())
|
if (!OperatingSystem.IsWindows() && !OperatingSystem.IsMacOS())
|
||||||
{
|
{
|
||||||
@@ -59,7 +65,9 @@ namespace ARMeilleure.Translation.Cache
|
|||||||
|
|
||||||
if (OperatingSystem.IsWindows())
|
if (OperatingSystem.IsWindows())
|
||||||
{
|
{
|
||||||
JitUnwindWindows.InstallFunctionTableHandler(_jitRegion.Pointer, CacheSize, _jitRegion.Pointer + Allocate(_pageSize));
|
JitUnwindWindows.InstallFunctionTableHandler(
|
||||||
|
firstRegion.Pointer, CacheSize, firstRegion.Pointer + Allocate(_pageSize)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_initialized = true;
|
_initialized = true;
|
||||||
@@ -75,8 +83,8 @@ namespace ARMeilleure.Translation.Cache
|
|||||||
Debug.Assert(_initialized);
|
Debug.Assert(_initialized);
|
||||||
|
|
||||||
int funcOffset = Allocate(code.Length);
|
int funcOffset = Allocate(code.Length);
|
||||||
|
ReservedRegion targetRegion = _jitRegions[_activeRegionIndex];
|
||||||
nint funcPtr = _jitRegion.Pointer + funcOffset;
|
nint funcPtr = targetRegion.Pointer + funcOffset;
|
||||||
|
|
||||||
if (OperatingSystem.IsMacOS() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
|
if (OperatingSystem.IsMacOS() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
|
||||||
{
|
{
|
||||||
@@ -90,9 +98,9 @@ namespace ARMeilleure.Translation.Cache
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ReprotectAsWritable(funcOffset, code.Length);
|
ReprotectAsWritable(targetRegion, funcOffset, code.Length);
|
||||||
Marshal.Copy(code, 0, funcPtr, code.Length);
|
Marshal.Copy(code, 0, funcPtr, code.Length);
|
||||||
ReprotectAsExecutable(funcOffset, code.Length);
|
ReprotectAsExecutable(targetRegion, funcOffset, code.Length);
|
||||||
|
|
||||||
if (OperatingSystem.IsWindows() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
|
if (OperatingSystem.IsWindows() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
|
||||||
{
|
{
|
||||||
@@ -116,52 +124,83 @@ namespace ARMeilleure.Translation.Cache
|
|||||||
{
|
{
|
||||||
Debug.Assert(_initialized);
|
Debug.Assert(_initialized);
|
||||||
|
|
||||||
int funcOffset = (int)(pointer.ToInt64() - _jitRegion.Pointer.ToInt64());
|
foreach (ReservedRegion region in _jitRegions)
|
||||||
|
|
||||||
if (TryFind(funcOffset, out CacheEntry entry, out int entryIndex) && entry.Offset == funcOffset)
|
|
||||||
{
|
{
|
||||||
_cacheAllocator.Free(funcOffset, AlignCodeSize(entry.Size));
|
if (pointer.ToInt64() < region.Pointer.ToInt64() ||
|
||||||
_cacheEntries.RemoveAt(entryIndex);
|
pointer.ToInt64() >= (region.Pointer + CacheSize).ToInt64())
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int funcOffset = (int)(pointer.ToInt64() - region.Pointer.ToInt64());
|
||||||
|
|
||||||
|
if (TryFind(funcOffset, out CacheEntry entry, out int entryIndex) && entry.Offset == funcOffset)
|
||||||
|
{
|
||||||
|
_cacheAllocator.Free(funcOffset, AlignCodeSize(entry.Size));
|
||||||
|
_cacheEntries.RemoveAt(entryIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ReprotectAsWritable(int offset, int size)
|
private static void ReprotectAsWritable(ReservedRegion region, int offset, int size)
|
||||||
{
|
{
|
||||||
int endOffs = offset + size;
|
int endOffs = offset + size;
|
||||||
|
|
||||||
int regionStart = offset & ~_pageMask;
|
int regionStart = offset & ~_pageMask;
|
||||||
int regionEnd = (endOffs + _pageMask) & ~_pageMask;
|
int regionEnd = (endOffs + _pageMask) & ~_pageMask;
|
||||||
|
|
||||||
_jitRegion.Block.MapAsRwx((ulong)regionStart, (ulong)(regionEnd - regionStart));
|
region.Block.MapAsRwx((ulong)regionStart, (ulong)(regionEnd - regionStart));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ReprotectAsExecutable(int offset, int size)
|
private static void ReprotectAsExecutable(ReservedRegion region, int offset, int size)
|
||||||
{
|
{
|
||||||
int endOffs = offset + size;
|
int endOffs = offset + size;
|
||||||
|
|
||||||
int regionStart = offset & ~_pageMask;
|
int regionStart = offset & ~_pageMask;
|
||||||
int regionEnd = (endOffs + _pageMask) & ~_pageMask;
|
int regionEnd = (endOffs + _pageMask) & ~_pageMask;
|
||||||
|
|
||||||
_jitRegion.Block.MapAsRx((ulong)regionStart, (ulong)(regionEnd - regionStart));
|
region.Block.MapAsRx((ulong)regionStart, (ulong)(regionEnd - regionStart));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int Allocate(int codeSize)
|
private static int Allocate(int codeSize)
|
||||||
{
|
{
|
||||||
codeSize = AlignCodeSize(codeSize);
|
codeSize = AlignCodeSize(codeSize);
|
||||||
|
|
||||||
int allocOffset = _cacheAllocator.Allocate(codeSize);
|
for (int i = _activeRegionIndex; i < _jitRegions.Count; i++)
|
||||||
|
|
||||||
if (allocOffset < 0)
|
|
||||||
{
|
{
|
||||||
throw new OutOfMemoryException("JIT Cache exhausted.");
|
int allocOffset = _cacheAllocator.Allocate(codeSize);
|
||||||
|
|
||||||
|
if (allocOffset >= 0)
|
||||||
|
{
|
||||||
|
_jitRegions[i].ExpandIfNeeded((ulong)allocOffset + (ulong)codeSize);
|
||||||
|
_activeRegionIndex = i;
|
||||||
|
return allocOffset;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_jitRegion.ExpandIfNeeded((ulong)allocOffset + (ulong)codeSize);
|
int exhaustedRegion = _activeRegionIndex;
|
||||||
|
var newRegion = new ReservedRegion(_jitRegions[0].Allocator, CacheSize);
|
||||||
|
_jitRegions.Add(newRegion);
|
||||||
|
_activeRegionIndex = _jitRegions.Count - 1;
|
||||||
|
|
||||||
|
int newRegionNumber = _activeRegionIndex;
|
||||||
|
|
||||||
return allocOffset;
|
Logger.Warning?.Print(LogClass.Cpu, $"JIT Cache Region {exhaustedRegion} exhausted, creating new Cache Region {newRegionNumber} ({((long)(newRegionNumber + 1) * CacheSize).Bytes()} Total Allocation).");
|
||||||
|
|
||||||
|
_cacheAllocator = new CacheMemoryAllocator(CacheSize);
|
||||||
|
|
||||||
|
int allocOffsetNew = _cacheAllocator.Allocate(codeSize);
|
||||||
|
if (allocOffsetNew < 0)
|
||||||
|
{
|
||||||
|
throw new OutOfMemoryException("Failed to allocate in new Cache Region!");
|
||||||
|
}
|
||||||
|
|
||||||
|
newRegion.ExpandIfNeeded((ulong)allocOffsetNew + (ulong)codeSize);
|
||||||
|
return allocOffsetNew;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static int AlignCodeSize(int codeSize)
|
private static int AlignCodeSize(int codeSize)
|
||||||
{
|
{
|
||||||
return checked(codeSize + (CodeAlignment - 1)) & ~(CodeAlignment - 1);
|
return checked(codeSize + (CodeAlignment - 1)) & ~(CodeAlignment - 1);
|
||||||
@@ -185,18 +224,21 @@ namespace ARMeilleure.Translation.Cache
|
|||||||
{
|
{
|
||||||
lock (_lock)
|
lock (_lock)
|
||||||
{
|
{
|
||||||
int index = _cacheEntries.BinarySearch(new CacheEntry(offset, 0, default));
|
foreach (ReservedRegion _ in _jitRegions)
|
||||||
|
|
||||||
if (index < 0)
|
|
||||||
{
|
{
|
||||||
index = ~index - 1;
|
int index = _cacheEntries.BinarySearch(new CacheEntry(offset, 0, default));
|
||||||
}
|
|
||||||
|
|
||||||
if (index >= 0)
|
if (index < 0)
|
||||||
{
|
{
|
||||||
entry = _cacheEntries[index];
|
index = ~index - 1;
|
||||||
entryIndex = index;
|
}
|
||||||
return true;
|
|
||||||
|
if (index >= 0)
|
||||||
|
{
|
||||||
|
entry = _cacheEntries[index];
|
||||||
|
entryIndex = index;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -144,17 +144,15 @@ namespace ARMeilleure.Translation.PTC
|
|||||||
|
|
||||||
public List<ulong> GetBlacklistedFunctions()
|
public List<ulong> GetBlacklistedFunctions()
|
||||||
{
|
{
|
||||||
List<ulong> funcs = new List<ulong>();
|
List<ulong> funcs = [];
|
||||||
|
|
||||||
foreach (var profiledFunc in ProfiledFuncs)
|
foreach ((ulong ptr, FuncProfile funcProfile) in ProfiledFuncs)
|
||||||
{
|
{
|
||||||
if (profiledFunc.Value.Blacklist)
|
if (!funcProfile.Blacklist)
|
||||||
{
|
continue;
|
||||||
if (!funcs.Contains(profiledFunc.Key))
|
|
||||||
{
|
if (!funcs.Contains(ptr))
|
||||||
funcs.Add(profiledFunc.Key);
|
funcs.Add(ptr);
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return funcs;
|
return funcs;
|
||||||
|
|||||||
@@ -219,6 +219,7 @@ namespace Ryujinx.Common
|
|||||||
//Misc Games
|
//Misc Games
|
||||||
"010056e00853a000", // A Hat in Time
|
"010056e00853a000", // A Hat in Time
|
||||||
"0100fd1014726000", // Baldurs Gate: Dark Alliance
|
"0100fd1014726000", // Baldurs Gate: Dark Alliance
|
||||||
|
"01008c2019598000", // Bluey: The Video Game
|
||||||
"0100c6800b934000", // Brawlhalla
|
"0100c6800b934000", // Brawlhalla
|
||||||
"0100dbf01000a000", // Burnout Paradise Remastered
|
"0100dbf01000a000", // Burnout Paradise Remastered
|
||||||
"0100744001588000", // Cars 3: Driven to Win
|
"0100744001588000", // Cars 3: Driven to Win
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
using ARMeilleure.Memory;
|
using ARMeilleure.Memory;
|
||||||
|
using Humanizer;
|
||||||
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.Memory;
|
using Ryujinx.Memory;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@@ -15,9 +17,8 @@ namespace Ryujinx.Cpu.LightningJit.Cache
|
|||||||
private static readonly int _pageMask = _pageSize - 1;
|
private static readonly int _pageMask = _pageSize - 1;
|
||||||
|
|
||||||
private const int CodeAlignment = 4; // Bytes.
|
private const int CodeAlignment = 4; // Bytes.
|
||||||
private const int CacheSize = 2047 * 1024 * 1024;
|
private const int CacheSize = 256 * 1024 * 1024;
|
||||||
|
|
||||||
private static ReservedRegion _jitRegion;
|
|
||||||
private static JitCacheInvalidation _jitCacheInvalidator;
|
private static JitCacheInvalidation _jitCacheInvalidator;
|
||||||
|
|
||||||
private static CacheMemoryAllocator _cacheAllocator;
|
private static CacheMemoryAllocator _cacheAllocator;
|
||||||
@@ -26,6 +27,8 @@ namespace Ryujinx.Cpu.LightningJit.Cache
|
|||||||
|
|
||||||
private static readonly Lock _lock = new();
|
private static readonly Lock _lock = new();
|
||||||
private static bool _initialized;
|
private static bool _initialized;
|
||||||
|
private static readonly List<ReservedRegion> _jitRegions = new();
|
||||||
|
private static int _activeRegionIndex = 0;
|
||||||
|
|
||||||
[SupportedOSPlatform("windows")]
|
[SupportedOSPlatform("windows")]
|
||||||
[LibraryImport("kernel32.dll", SetLastError = true)]
|
[LibraryImport("kernel32.dll", SetLastError = true)]
|
||||||
@@ -45,7 +48,9 @@ namespace Ryujinx.Cpu.LightningJit.Cache
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_jitRegion = new ReservedRegion(allocator, CacheSize);
|
ReservedRegion firstRegion = new(allocator, CacheSize);
|
||||||
|
_jitRegions.Add(firstRegion);
|
||||||
|
_activeRegionIndex = 0;
|
||||||
|
|
||||||
if (!OperatingSystem.IsWindows() && !OperatingSystem.IsMacOS())
|
if (!OperatingSystem.IsWindows() && !OperatingSystem.IsMacOS())
|
||||||
{
|
{
|
||||||
@@ -65,8 +70,8 @@ namespace Ryujinx.Cpu.LightningJit.Cache
|
|||||||
Debug.Assert(_initialized);
|
Debug.Assert(_initialized);
|
||||||
|
|
||||||
int funcOffset = Allocate(code.Length);
|
int funcOffset = Allocate(code.Length);
|
||||||
|
ReservedRegion targetRegion = _jitRegions[_activeRegionIndex];
|
||||||
nint funcPtr = _jitRegion.Pointer + funcOffset;
|
nint funcPtr = targetRegion.Pointer + funcOffset;
|
||||||
|
|
||||||
if (OperatingSystem.IsMacOS() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
|
if (OperatingSystem.IsMacOS() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
|
||||||
{
|
{
|
||||||
@@ -80,18 +85,11 @@ namespace Ryujinx.Cpu.LightningJit.Cache
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ReprotectAsWritable(funcOffset, code.Length);
|
ReprotectAsWritable(targetRegion, funcOffset, code.Length);
|
||||||
code.CopyTo(new Span<byte>((void*)funcPtr, code.Length));
|
Marshal.Copy(code.ToArray(), 0, funcPtr, code.Length);
|
||||||
ReprotectAsExecutable(funcOffset, code.Length);
|
ReprotectAsExecutable(targetRegion, funcOffset, code.Length);
|
||||||
|
|
||||||
if (OperatingSystem.IsWindows() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
|
_jitCacheInvalidator?.Invalidate(funcPtr, (ulong)code.Length);
|
||||||
{
|
|
||||||
FlushInstructionCache(Process.GetCurrentProcess().Handle, funcPtr, (nuint)code.Length);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_jitCacheInvalidator?.Invalidate(funcPtr, (ulong)code.Length);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Add(funcOffset, code.Length);
|
Add(funcOffset, code.Length);
|
||||||
@@ -106,50 +104,80 @@ namespace Ryujinx.Cpu.LightningJit.Cache
|
|||||||
{
|
{
|
||||||
Debug.Assert(_initialized);
|
Debug.Assert(_initialized);
|
||||||
|
|
||||||
int funcOffset = (int)(pointer.ToInt64() - _jitRegion.Pointer.ToInt64());
|
foreach (ReservedRegion region in _jitRegions)
|
||||||
|
|
||||||
if (TryFind(funcOffset, out CacheEntry entry, out int entryIndex) && entry.Offset == funcOffset)
|
|
||||||
{
|
{
|
||||||
_cacheAllocator.Free(funcOffset, AlignCodeSize(entry.Size));
|
if (pointer.ToInt64() < region.Pointer.ToInt64() ||
|
||||||
_cacheEntries.RemoveAt(entryIndex);
|
pointer.ToInt64() >= (region.Pointer + CacheSize).ToInt64())
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int funcOffset = (int)(pointer.ToInt64() - region.Pointer.ToInt64());
|
||||||
|
|
||||||
|
if (TryFind(funcOffset, out CacheEntry entry, out int entryIndex) && entry.Offset == funcOffset)
|
||||||
|
{
|
||||||
|
_cacheAllocator.Free(funcOffset, AlignCodeSize(entry.Size));
|
||||||
|
_cacheEntries.RemoveAt(entryIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ReprotectAsWritable(int offset, int size)
|
private static void ReprotectAsWritable(ReservedRegion region, int offset, int size)
|
||||||
{
|
{
|
||||||
int endOffs = offset + size;
|
int endOffs = offset + size;
|
||||||
|
|
||||||
int regionStart = offset & ~_pageMask;
|
int regionStart = offset & ~_pageMask;
|
||||||
int regionEnd = (endOffs + _pageMask) & ~_pageMask;
|
int regionEnd = (endOffs + _pageMask) & ~_pageMask;
|
||||||
|
|
||||||
_jitRegion.Block.MapAsRwx((ulong)regionStart, (ulong)(regionEnd - regionStart));
|
region.Block.MapAsRwx((ulong)regionStart, (ulong)(regionEnd - regionStart));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ReprotectAsExecutable(int offset, int size)
|
private static void ReprotectAsExecutable(ReservedRegion region, int offset, int size)
|
||||||
{
|
{
|
||||||
int endOffs = offset + size;
|
int endOffs = offset + size;
|
||||||
|
|
||||||
int regionStart = offset & ~_pageMask;
|
int regionStart = offset & ~_pageMask;
|
||||||
int regionEnd = (endOffs + _pageMask) & ~_pageMask;
|
int regionEnd = (endOffs + _pageMask) & ~_pageMask;
|
||||||
|
|
||||||
_jitRegion.Block.MapAsRx((ulong)regionStart, (ulong)(regionEnd - regionStart));
|
region.Block.MapAsRx((ulong)regionStart, (ulong)(regionEnd - regionStart));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int Allocate(int codeSize)
|
private static int Allocate(int codeSize)
|
||||||
{
|
{
|
||||||
codeSize = AlignCodeSize(codeSize);
|
codeSize = AlignCodeSize(codeSize);
|
||||||
|
|
||||||
int allocOffset = _cacheAllocator.Allocate(codeSize);
|
for (int i = _activeRegionIndex; i < _jitRegions.Count; i++)
|
||||||
|
|
||||||
if (allocOffset < 0)
|
|
||||||
{
|
{
|
||||||
throw new OutOfMemoryException("JIT Cache exhausted.");
|
int allocOffset = _cacheAllocator.Allocate(codeSize);
|
||||||
|
|
||||||
|
if (allocOffset >= 0)
|
||||||
|
{
|
||||||
|
_jitRegions[i].ExpandIfNeeded((ulong)allocOffset + (ulong)codeSize);
|
||||||
|
_activeRegionIndex = i;
|
||||||
|
return allocOffset;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_jitRegion.ExpandIfNeeded((ulong)allocOffset + (ulong)codeSize);
|
int exhaustedRegion = _activeRegionIndex;
|
||||||
|
ReservedRegion newRegion = new(_jitRegions[0].Allocator, CacheSize);
|
||||||
|
_jitRegions.Add(newRegion);
|
||||||
|
_activeRegionIndex = _jitRegions.Count - 1;
|
||||||
|
|
||||||
|
int newRegionNumber = _activeRegionIndex;
|
||||||
|
|
||||||
return allocOffset;
|
Logger.Warning?.Print(LogClass.Cpu, $"JIT Cache Region {exhaustedRegion} exhausted, creating new Cache Region {newRegionNumber} ({((long)(newRegionNumber + 1) * CacheSize).Bytes()} Total Allocation).");
|
||||||
|
|
||||||
|
_cacheAllocator = new CacheMemoryAllocator(CacheSize);
|
||||||
|
|
||||||
|
int allocOffsetNew = _cacheAllocator.Allocate(codeSize);
|
||||||
|
if (allocOffsetNew < 0)
|
||||||
|
{
|
||||||
|
throw new OutOfMemoryException("Failed to allocate in new Cache Region!");
|
||||||
|
}
|
||||||
|
|
||||||
|
newRegion.ExpandIfNeeded((ulong)allocOffsetNew + (ulong)codeSize);
|
||||||
|
return allocOffsetNew;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int AlignCodeSize(int codeSize)
|
private static int AlignCodeSize(int codeSize)
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ namespace Ryujinx.Cpu.LightningJit.Cache
|
|||||||
{
|
{
|
||||||
private const int CodeAlignment = 4; // Bytes.
|
private const int CodeAlignment = 4; // Bytes.
|
||||||
private const int SharedCacheSize = 2047 * 1024 * 1024;
|
private const int SharedCacheSize = 2047 * 1024 * 1024;
|
||||||
private const int LocalCacheSize = 128 * 1024 * 1024;
|
private const int LocalCacheSize = 256 * 1024 * 1024;
|
||||||
|
|
||||||
// How many calls to the same function we allow until we pad the shared cache to force the function to become available there
|
// How many calls to the same function we allow until we pad the shared cache to force the function to become available there
|
||||||
// and allow the guest to take the fast path.
|
// and allow the guest to take the fast path.
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using Silk.NET.Vulkan;
|
using Silk.NET.Vulkan;
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Vulkan
|
namespace Ryujinx.Graphics.Vulkan
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ using System.IO;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Applets.Error
|
namespace Ryujinx.HLE.HOS.Applets.Error
|
||||||
{
|
{
|
||||||
@@ -159,13 +158,15 @@ namespace Ryujinx.HLE.HOS.Applets.Error
|
|||||||
|
|
||||||
string[] buttons = GetButtonsText(module, description, "DlgBtn");
|
string[] buttons = GetButtonsText(module, description, "DlgBtn");
|
||||||
|
|
||||||
bool showDetails = _horizon.Device.UIHandler.DisplayErrorAppletDialog($"Error Code: {module}-{description:0000}", "\n" + message, buttons);
|
(uint Module, uint Description) errorCodeTuple = (module, uint.Parse(description.ToString("0000")));
|
||||||
|
|
||||||
|
bool showDetails = _horizon.Device.UIHandler.DisplayErrorAppletDialog($"Error Code: {module}-{description:0000}", "\n" + message, buttons, errorCodeTuple);
|
||||||
if (showDetails)
|
if (showDetails)
|
||||||
{
|
{
|
||||||
message = GetMessageText(module, description, "FlvMsg");
|
message = GetMessageText(module, description, "FlvMsg");
|
||||||
buttons = GetButtonsText(module, description, "FlvBtn");
|
buttons = GetButtonsText(module, description, "FlvBtn");
|
||||||
|
|
||||||
_horizon.Device.UIHandler.DisplayErrorAppletDialog($"Details: {module}-{description:0000}", "\n" + message, buttons);
|
_horizon.Device.UIHandler.DisplayErrorAppletDialog($"Details: {module}-{description:0000}", "\n" + message, buttons, errorCodeTuple);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,10 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
|
|||||||
{
|
{
|
||||||
if (errorCode != LinuxError.SUCCESS)
|
if (errorCode != LinuxError.SUCCESS)
|
||||||
{
|
{
|
||||||
|
if (errorCode != LinuxError.EWOULDBLOCK)
|
||||||
|
{
|
||||||
|
Logger.Warning?.Print(LogClass.ServiceBsd, $"Operation failed with error {errorCode}.");
|
||||||
|
}
|
||||||
result = -1;
|
result = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,6 +70,8 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
|
|||||||
BsdSocketType type = (BsdSocketType)context.RequestData.ReadInt32();
|
BsdSocketType type = (BsdSocketType)context.RequestData.ReadInt32();
|
||||||
ProtocolType protocol = (ProtocolType)context.RequestData.ReadInt32();
|
ProtocolType protocol = (ProtocolType)context.RequestData.ReadInt32();
|
||||||
|
|
||||||
|
Logger.Info?.PrintMsg(LogClass.ServiceBsd, $"Creating socket with domain={domain}, type={type}, protocol={protocol}");
|
||||||
|
|
||||||
BsdSocketCreationFlags creationFlags = (BsdSocketCreationFlags)((int)type >> (int)BsdSocketCreationFlags.FlagsShift);
|
BsdSocketCreationFlags creationFlags = (BsdSocketCreationFlags)((int)type >> (int)BsdSocketCreationFlags.FlagsShift);
|
||||||
type &= BsdSocketType.TypeMask;
|
type &= BsdSocketType.TypeMask;
|
||||||
|
|
||||||
@@ -95,12 +101,21 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ISocket newBsdSocket = new ManagedSocket(netDomain, (SocketType)type, protocol, context.Device.Configuration.MultiplayerLanInterfaceId)
|
|
||||||
{
|
|
||||||
Blocking = !creationFlags.HasFlag(BsdSocketCreationFlags.NonBlocking),
|
|
||||||
};
|
|
||||||
|
|
||||||
LinuxError errno = LinuxError.SUCCESS;
|
LinuxError errno = LinuxError.SUCCESS;
|
||||||
|
ISocket newBsdSocket;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
newBsdSocket = new ManagedSocket(netDomain, (SocketType)type, protocol, context.Device.Configuration.MultiplayerLanInterfaceId)
|
||||||
|
{
|
||||||
|
Blocking = !creationFlags.HasFlag(BsdSocketCreationFlags.NonBlocking),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
catch (SocketException exception)
|
||||||
|
{
|
||||||
|
LinuxError errNo = WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
|
||||||
|
return WriteBsdResult(context, 0, errNo);
|
||||||
|
}
|
||||||
|
|
||||||
int newSockFd = _context.RegisterFileDescriptor(newBsdSocket);
|
int newSockFd = _context.RegisterFileDescriptor(newBsdSocket);
|
||||||
|
|
||||||
@@ -111,6 +126,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
|
|||||||
|
|
||||||
if (exempt)
|
if (exempt)
|
||||||
{
|
{
|
||||||
|
Logger.Info?.Print(LogClass.ServiceBsd, "Disconnecting exempt socket.");
|
||||||
newBsdSocket.Disconnect();
|
newBsdSocket.Disconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -797,7 +813,11 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
|
|||||||
{
|
{
|
||||||
errno = socket.Listen(backlog);
|
errno = socket.Listen(backlog);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger.Warning?.PrintMsg(LogClass.ServiceBsd, $"Invalid socket fd '{socketFd}'.");
|
||||||
|
}
|
||||||
|
|
||||||
return WriteBsdResult(context, 0, errno);
|
return WriteBsdResult(context, 0, errno);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -92,18 +92,30 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
|
|||||||
{
|
{
|
||||||
newSocket = new ManagedSocket(Socket.Accept());
|
newSocket = new ManagedSocket(Socket.Accept());
|
||||||
|
|
||||||
|
IPEndPoint remoteEndPoint = newSocket.RemoteEndPoint;
|
||||||
|
bool isPrivateIp = remoteEndPoint.Address.ToString().StartsWith("192.168.");
|
||||||
|
Logger.Info?.PrintMsg(LogClass.ServiceBsd,
|
||||||
|
isPrivateIp
|
||||||
|
? $"Accepted connection from {ProtocolType}/{remoteEndPoint.Address}:{remoteEndPoint.Port}"
|
||||||
|
: $"Accepted connection from {ProtocolType}/***:{remoteEndPoint.Port}");
|
||||||
|
|
||||||
return LinuxError.SUCCESS;
|
return LinuxError.SUCCESS;
|
||||||
}
|
}
|
||||||
catch (SocketException exception)
|
catch (SocketException exception)
|
||||||
{
|
{
|
||||||
newSocket = null;
|
newSocket = null;
|
||||||
|
|
||||||
|
if (exception.SocketErrorCode != SocketError.WouldBlock)
|
||||||
|
{
|
||||||
|
Logger.Warning?.Print(LogClass.ServiceBsd, $"Socket Exception: {exception}");
|
||||||
|
}
|
||||||
return WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
|
return WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public LinuxError Bind(IPEndPoint localEndPoint)
|
public LinuxError Bind(IPEndPoint localEndPoint)
|
||||||
{
|
{
|
||||||
|
Logger.Info?.PrintMsg(LogClass.ServiceBsd, $"Socket binding to: {ProtocolType}/{localEndPoint.Port}");
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Socket.Bind(localEndPoint);
|
Socket.Bind(localEndPoint);
|
||||||
@@ -112,6 +124,10 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
|
|||||||
}
|
}
|
||||||
catch (SocketException exception)
|
catch (SocketException exception)
|
||||||
{
|
{
|
||||||
|
if (exception.SocketErrorCode != SocketError.WouldBlock)
|
||||||
|
{
|
||||||
|
Logger.Warning?.Print(LogClass.ServiceBsd, $"Socket Exception: {exception}");
|
||||||
|
}
|
||||||
return WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
|
return WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -123,6 +139,15 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
|
|||||||
|
|
||||||
public LinuxError Connect(IPEndPoint remoteEndPoint)
|
public LinuxError Connect(IPEndPoint remoteEndPoint)
|
||||||
{
|
{
|
||||||
|
bool isLDNPrivateIP = remoteEndPoint.Address.ToString().StartsWith("192.168.");
|
||||||
|
if (isLDNPrivateIP)
|
||||||
|
{
|
||||||
|
Logger.Info?.PrintMsg(LogClass.ServiceBsd, $"Connecting to: {ProtocolType}/{remoteEndPoint.Address}:{remoteEndPoint.Port}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger.Info?.PrintMsg(LogClass.ServiceBsd, $"Connecting to: {ProtocolType}/***:{remoteEndPoint.Port}");
|
||||||
|
}
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Socket.Connect(remoteEndPoint);
|
Socket.Connect(remoteEndPoint);
|
||||||
@@ -137,6 +162,10 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
if (exception.SocketErrorCode != SocketError.WouldBlock)
|
||||||
|
{
|
||||||
|
Logger.Warning?.Print(LogClass.ServiceBsd, $"Socket Exception: {exception}");
|
||||||
|
}
|
||||||
return WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
|
return WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -144,11 +173,13 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
|
|||||||
|
|
||||||
public void Disconnect()
|
public void Disconnect()
|
||||||
{
|
{
|
||||||
|
Logger.Info?.Print(LogClass.ServiceBsd, "Socket disconnecting");
|
||||||
Socket.Disconnect(true);
|
Socket.Disconnect(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
|
Logger.Info?.Print(LogClass.ServiceBsd, "Socket closed");
|
||||||
Socket.Close();
|
Socket.Close();
|
||||||
Socket.Dispose();
|
Socket.Dispose();
|
||||||
}
|
}
|
||||||
@@ -159,10 +190,16 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
|
|||||||
{
|
{
|
||||||
Socket.Listen(backlog);
|
Socket.Listen(backlog);
|
||||||
|
|
||||||
|
Logger.Info?.PrintMsg(LogClass.ServiceBsd, $"Socket listening: {ProtocolType}/{(Socket.LocalEndPoint as IPEndPoint).Port}");
|
||||||
|
|
||||||
return LinuxError.SUCCESS;
|
return LinuxError.SUCCESS;
|
||||||
}
|
}
|
||||||
catch (SocketException exception)
|
catch (SocketException exception)
|
||||||
{
|
{
|
||||||
|
if (exception.SocketErrorCode != SocketError.WouldBlock)
|
||||||
|
{
|
||||||
|
Logger.Warning?.Print(LogClass.ServiceBsd, $"Socket Exception: {exception}");
|
||||||
|
}
|
||||||
return WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
|
return WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -182,11 +219,15 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
|
|||||||
}
|
}
|
||||||
catch (SocketException exception)
|
catch (SocketException exception)
|
||||||
{
|
{
|
||||||
|
if (exception.SocketErrorCode != SocketError.WouldBlock)
|
||||||
|
{
|
||||||
|
Logger.Warning?.Print(LogClass.ServiceBsd, $"Socket Exception: {exception}");
|
||||||
|
}
|
||||||
return WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
|
return WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool hasEmittedBlockingWarning = false;
|
private bool _hasEmittedBlockingWarning;
|
||||||
|
|
||||||
public LinuxError Receive(out int receiveSize, Span<byte> buffer, BsdSocketFlags flags)
|
public LinuxError Receive(out int receiveSize, Span<byte> buffer, BsdSocketFlags flags)
|
||||||
{
|
{
|
||||||
@@ -202,10 +243,10 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
|
|||||||
shouldBlockAfterOperation = true;
|
shouldBlockAfterOperation = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Blocking && !hasEmittedBlockingWarning)
|
if (Blocking && !_hasEmittedBlockingWarning)
|
||||||
{
|
{
|
||||||
Logger.Warning?.PrintMsg(LogClass.ServiceBsd, "Blocking socket operations are not yet working properly. Expect network errors.");
|
Logger.Warning?.PrintMsg(LogClass.ServiceBsd, "Blocking socket operations are not yet working properly. Expect network errors.");
|
||||||
hasEmittedBlockingWarning = true;
|
_hasEmittedBlockingWarning = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
receiveSize = Socket.Receive(buffer, ConvertBsdSocketFlags(flags));
|
receiveSize = Socket.Receive(buffer, ConvertBsdSocketFlags(flags));
|
||||||
@@ -214,6 +255,10 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
|
|||||||
}
|
}
|
||||||
catch (SocketException exception)
|
catch (SocketException exception)
|
||||||
{
|
{
|
||||||
|
if (exception.SocketErrorCode != SocketError.WouldBlock)
|
||||||
|
{
|
||||||
|
Logger.Warning?.Print(LogClass.ServiceBsd, $"Socket Exception: {exception}");
|
||||||
|
}
|
||||||
receiveSize = -1;
|
receiveSize = -1;
|
||||||
|
|
||||||
result = WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
|
result = WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
|
||||||
@@ -245,10 +290,10 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
|
|||||||
shouldBlockAfterOperation = true;
|
shouldBlockAfterOperation = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Blocking && !hasEmittedBlockingWarning)
|
if (Blocking && !_hasEmittedBlockingWarning)
|
||||||
{
|
{
|
||||||
Logger.Warning?.PrintMsg(LogClass.ServiceBsd, "Blocking socket operations are not yet working properly. Expect network errors.");
|
Logger.Warning?.PrintMsg(LogClass.ServiceBsd, "Blocking socket operations are not yet working properly. Expect network errors.");
|
||||||
hasEmittedBlockingWarning = true;
|
_hasEmittedBlockingWarning = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Socket.IsBound)
|
if (!Socket.IsBound)
|
||||||
@@ -265,6 +310,10 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
|
|||||||
}
|
}
|
||||||
catch (SocketException exception)
|
catch (SocketException exception)
|
||||||
{
|
{
|
||||||
|
if (exception.SocketErrorCode != SocketError.WouldBlock)
|
||||||
|
{
|
||||||
|
Logger.Warning?.Print(LogClass.ServiceBsd, $"Socket Exception: {exception}");
|
||||||
|
}
|
||||||
receiveSize = -1;
|
receiveSize = -1;
|
||||||
|
|
||||||
result = WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
|
result = WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
|
||||||
@@ -288,6 +337,10 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
|
|||||||
}
|
}
|
||||||
catch (SocketException exception)
|
catch (SocketException exception)
|
||||||
{
|
{
|
||||||
|
if (exception.SocketErrorCode != SocketError.WouldBlock)
|
||||||
|
{
|
||||||
|
Logger.Warning?.Print(LogClass.ServiceBsd, $"Socket Exception: {exception}");
|
||||||
|
}
|
||||||
sendSize = -1;
|
sendSize = -1;
|
||||||
|
|
||||||
return WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
|
return WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
|
||||||
@@ -304,6 +357,10 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
|
|||||||
}
|
}
|
||||||
catch (SocketException exception)
|
catch (SocketException exception)
|
||||||
{
|
{
|
||||||
|
if (exception.SocketErrorCode != SocketError.WouldBlock)
|
||||||
|
{
|
||||||
|
Logger.Warning?.Print(LogClass.ServiceBsd, $"Socket Exception: {exception}");
|
||||||
|
}
|
||||||
sendSize = -1;
|
sendSize = -1;
|
||||||
|
|
||||||
return WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
|
return WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
|
||||||
@@ -341,6 +398,10 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
|
|||||||
}
|
}
|
||||||
catch (SocketException exception)
|
catch (SocketException exception)
|
||||||
{
|
{
|
||||||
|
if (exception.SocketErrorCode != SocketError.WouldBlock)
|
||||||
|
{
|
||||||
|
Logger.Warning?.Print(LogClass.ServiceBsd, $"Socket Exception: {exception}");
|
||||||
|
}
|
||||||
return WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
|
return WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -387,6 +448,10 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
|
|||||||
}
|
}
|
||||||
catch (SocketException exception)
|
catch (SocketException exception)
|
||||||
{
|
{
|
||||||
|
if (exception.SocketErrorCode != SocketError.WouldBlock)
|
||||||
|
{
|
||||||
|
Logger.Warning?.Print(LogClass.ServiceBsd, $"Socket Exception: {exception}");
|
||||||
|
}
|
||||||
return WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
|
return WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -519,6 +584,10 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
|
|||||||
}
|
}
|
||||||
catch (SocketException exception)
|
catch (SocketException exception)
|
||||||
{
|
{
|
||||||
|
if (exception.SocketErrorCode != SocketError.WouldBlock)
|
||||||
|
{
|
||||||
|
Logger.Warning?.Print(LogClass.ServiceBsd, $"Socket Exception: {exception}");
|
||||||
|
}
|
||||||
return WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
|
return WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -557,6 +626,10 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
|
|||||||
}
|
}
|
||||||
catch (SocketException exception)
|
catch (SocketException exception)
|
||||||
{
|
{
|
||||||
|
if (exception.SocketErrorCode != SocketError.WouldBlock)
|
||||||
|
{
|
||||||
|
Logger.Warning?.Print(LogClass.ServiceBsd, $"Socket Exception: {exception}");
|
||||||
|
}
|
||||||
return WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
|
return WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -150,6 +150,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
|
|||||||
{ BsdSocketOption.SoLinger, SocketOptionName.Linger },
|
{ BsdSocketOption.SoLinger, SocketOptionName.Linger },
|
||||||
{ BsdSocketOption.SoOobInline, SocketOptionName.OutOfBandInline },
|
{ BsdSocketOption.SoOobInline, SocketOptionName.OutOfBandInline },
|
||||||
{ BsdSocketOption.SoReusePort, SocketOptionName.ReuseAddress },
|
{ BsdSocketOption.SoReusePort, SocketOptionName.ReuseAddress },
|
||||||
|
{ BsdSocketOption.SoNoSigpipe, SocketOptionName.DontLinger },
|
||||||
{ BsdSocketOption.SoSndBuf, SocketOptionName.SendBuffer },
|
{ BsdSocketOption.SoSndBuf, SocketOptionName.SendBuffer },
|
||||||
{ BsdSocketOption.SoRcvBuf, SocketOptionName.ReceiveBuffer },
|
{ BsdSocketOption.SoRcvBuf, SocketOptionName.ReceiveBuffer },
|
||||||
{ BsdSocketOption.SoSndLoWat, SocketOptionName.SendLowWater },
|
{ BsdSocketOption.SoSndLoWat, SocketOptionName.SendLowWater },
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Proxy;
|
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu.Proxy;
|
||||||
|
using Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@@ -64,10 +66,18 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Proxy
|
|||||||
{
|
{
|
||||||
if (_proxy.Supported(domain, type, protocol))
|
if (_proxy.Supported(domain, type, protocol))
|
||||||
{
|
{
|
||||||
|
Logger.Info?.PrintMsg(LogClass.ServiceBsd, $"Socket is using LDN proxy");
|
||||||
return new LdnProxySocket(domain, type, protocol, _proxy);
|
return new LdnProxySocket(domain, type, protocol, _proxy);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger.Warning?.PrintMsg(LogClass.ServiceBsd, $"LDN proxy does not support socket {domain}, {type}, {protocol}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger.Info?.PrintMsg(LogClass.ServiceBsd, $"Opening socket using host networking stack");
|
||||||
}
|
}
|
||||||
|
|
||||||
return new DefaultSocket(domain, type, protocol, lanInterfaceId);
|
return new DefaultSocket(domain, type, protocol, lanInterfaceId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,10 +45,12 @@ namespace Ryujinx.HLE.UI
|
|||||||
/// <param name="value">The value associated to the <paramref name="kind"/>.</param>
|
/// <param name="value">The value associated to the <paramref name="kind"/>.</param>
|
||||||
void ExecuteProgram(Switch device, ProgramSpecifyKind kind, ulong value);
|
void ExecuteProgram(Switch device, ProgramSpecifyKind kind, ulong value);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
/// Displays a Message Dialog box specific to Error Applet and blocks until it is closed.
|
/// Displays a Message Dialog box specific to Error Applet and blocks until it is closed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>False when OK is pressed, True when another button (Details) is pressed.</returns>
|
/// <returns>False when OK is pressed, True when another button (Details) is pressed.</returns>
|
||||||
bool DisplayErrorAppletDialog(string title, string message, string[] buttonsText);
|
// ReSharper disable once UnusedParameter.Global
|
||||||
|
bool DisplayErrorAppletDialog(string title, string message, string[] buttonsText, (uint Module, uint Description)? errorCode = null);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a handler to process keyboard inputs into text strings.
|
/// Creates a handler to process keyboard inputs into text strings.
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
using MsgPack;
|
|
||||||
using Ryujinx.Horizon.Common;
|
using Ryujinx.Horizon.Common;
|
||||||
using Ryujinx.Horizon.Prepo.Types;
|
using Ryujinx.Horizon.Prepo.Types;
|
||||||
using Ryujinx.Memory;
|
using Ryujinx.Memory;
|
||||||
|
|||||||
@@ -185,6 +185,15 @@ namespace Ryujinx.Input.HLE
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool InputUpdatesBlocked
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
return _blockInputUpdates;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void BlockInputUpdates()
|
public void BlockInputUpdates()
|
||||||
{
|
{
|
||||||
lock (_lock)
|
lock (_lock)
|
||||||
|
|||||||
@@ -517,7 +517,7 @@ namespace Ryujinx.Ava
|
|||||||
Device?.System.ChangeDockedModeState(e.NewValue);
|
Device?.System.ChangeDockedModeState(e.NewValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateAudioVolumeState(object sender, ReactiveEventArgs<float> e)
|
public void UpdateAudioVolumeState(object sender, ReactiveEventArgs<float> e)
|
||||||
{
|
{
|
||||||
Device?.SetVolume(e.NewValue);
|
Device?.SetVolume(e.NewValue);
|
||||||
|
|
||||||
@@ -582,13 +582,6 @@ namespace Ryujinx.Ava
|
|||||||
Rainbow.Disable();
|
Rainbow.Disable();
|
||||||
Rainbow.Reset();
|
Rainbow.Reset();
|
||||||
|
|
||||||
// Reload settings when the game is turned off
|
|
||||||
// (resets custom settings if there were any)
|
|
||||||
Program.ReloadConfig();
|
|
||||||
|
|
||||||
//Updates the gameList (changes the status of the user setting if it was added or removed during the game)
|
|
||||||
RyujinxApp.MainWindow.GameListUpdate();
|
|
||||||
|
|
||||||
_isStopped = true;
|
_isStopped = true;
|
||||||
Stop();
|
Stop();
|
||||||
}
|
}
|
||||||
@@ -1048,6 +1041,7 @@ namespace Ryujinx.Ava
|
|||||||
if (_viewModel.StartGamesInFullscreen)
|
if (_viewModel.StartGamesInFullscreen)
|
||||||
{
|
{
|
||||||
_viewModel.WindowState = WindowState.FullScreen;
|
_viewModel.WindowState = WindowState.FullScreen;
|
||||||
|
_viewModel.Window.TitleBar.ExtendsContentIntoTitleBar = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_viewModel.WindowState is WindowState.FullScreen || _viewModel.StartGamesWithoutUI)
|
if (_viewModel.WindowState is WindowState.FullScreen || _viewModel.StartGamesWithoutUI)
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
<Styles
|
<Styles
|
||||||
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:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||||
xmlns:ext="clr-namespace:Ryujinx.Ava.Common.Markup"
|
|
||||||
xmlns:windowing="clr-namespace:FluentAvalonia.UI.Windowing;assembly=FluentAvalonia">
|
xmlns:windowing="clr-namespace:FluentAvalonia.UI.Windowing;assembly=FluentAvalonia">
|
||||||
<Design.PreviewWith>
|
<Design.PreviewWith>
|
||||||
<Border Height="2000"
|
<Border Height="2000"
|
||||||
@@ -31,8 +30,7 @@
|
|||||||
<Button
|
<Button
|
||||||
Name="btnRem"
|
Name="btnRem"
|
||||||
HorizontalAlignment="Right"
|
HorizontalAlignment="Right"
|
||||||
Content="Add"
|
Content="Add" />
|
||||||
Classes="red"/>
|
|
||||||
<TextBox
|
<TextBox
|
||||||
Width="100"
|
Width="100"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
@@ -43,11 +41,7 @@
|
|||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
<ui:NumberBox Value="1" />
|
<ui:NumberBox Value="1" />
|
||||||
<MenuItem
|
|
||||||
Header="123 0000"
|
|
||||||
ToolTip.Tip="What this"/>
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
</Border>
|
</Border>
|
||||||
</Design.PreviewWith>
|
</Design.PreviewWith>
|
||||||
<Style Selector="DropDownButton">
|
<Style Selector="DropDownButton">
|
||||||
@@ -379,16 +373,6 @@
|
|||||||
<Setter Property="Background"
|
<Setter Property="Background"
|
||||||
Value="{DynamicResource AppListHoverBackgroundColor}" />
|
Value="{DynamicResource AppListHoverBackgroundColor}" />
|
||||||
</Style>
|
</Style>
|
||||||
<Style Selector="Button.red /template/ ContentPresenter">
|
|
||||||
<Setter Property="CornerRadius" Value="4"/>
|
|
||||||
<Setter Property="Background" Value="red"/>
|
|
||||||
<Setter Property="Foreground" Value="White"/>
|
|
||||||
</Style>
|
|
||||||
<Style Selector="Button.red:pointerover /template/ ContentPresenter">
|
|
||||||
<Setter Property="CornerRadius" Value="4"/>
|
|
||||||
<Setter Property="Background" Value="{DynamicResource WarningBackgroundColor}" />
|
|
||||||
<Setter Property="Foreground" Value="White"/>
|
|
||||||
</Style>
|
|
||||||
<Styles.Resources>
|
<Styles.Resources>
|
||||||
<SolidColorBrush x:Key="ThemeAccentColorBrush"
|
<SolidColorBrush x:Key="ThemeAccentColorBrush"
|
||||||
Color="{DynamicResource SystemAccentColor}" />
|
Color="{DynamicResource SystemAccentColor}" />
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<ResourceDictionary xmlns="https://github.com/avaloniaui"
|
<ResourceDictionary xmlns="https://github.com/avaloniaui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||||
<ResourceDictionary.ThemeDictionaries>
|
<ResourceDictionary.ThemeDictionaries>
|
||||||
<ResourceDictionary x:Key="Default">
|
<ResourceDictionary x:Key="Default">
|
||||||
@@ -12,13 +12,11 @@
|
|||||||
<Color x:Key="MenuFlyoutPresenterBorderColor">#C1C1C1</Color>
|
<Color x:Key="MenuFlyoutPresenterBorderColor">#C1C1C1</Color>
|
||||||
<Color x:Key="AppListBackgroundColor">#b3ffffff</Color>
|
<Color x:Key="AppListBackgroundColor">#b3ffffff</Color>
|
||||||
<Color x:Key="AppListHoverBackgroundColor">#80cccccc</Color>
|
<Color x:Key="AppListHoverBackgroundColor">#80cccccc</Color>
|
||||||
<Color x:Key="WarningBackgroundColor">#FF6347</Color>
|
|
||||||
<Color x:Key="SecondaryTextColor">#A0000000</Color>
|
<Color x:Key="SecondaryTextColor">#A0000000</Color>
|
||||||
<Color x:Key="FavoriteApplicationIconColor">#fffcd12a</Color>
|
<Color x:Key="FavoriteApplicationIconColor">#fffcd12a</Color>
|
||||||
<Color x:Key="Switch">#FF2EEAC9</Color>
|
<Color x:Key="Switch">#FF2EEAC9</Color>
|
||||||
<Color x:Key="Unbounded">#FFFF4554</Color>
|
<Color x:Key="Unbounded">#FFFF4554</Color>
|
||||||
<Color x:Key="Custom">#6483F5</Color>
|
<Color x:Key="Custom">#6483F5</Color>
|
||||||
<Color x:Key="Warning">#800080</Color>
|
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
<ResourceDictionary x:Key="Light">
|
<ResourceDictionary x:Key="Light">
|
||||||
<SolidColorBrush x:Key="DataGridSelectionBackgroundBrush"
|
<SolidColorBrush x:Key="DataGridSelectionBackgroundBrush"
|
||||||
@@ -31,13 +29,11 @@
|
|||||||
<Color x:Key="MenuFlyoutPresenterBorderColor">#C1C1C1</Color>
|
<Color x:Key="MenuFlyoutPresenterBorderColor">#C1C1C1</Color>
|
||||||
<Color x:Key="AppListBackgroundColor">#b3ffffff</Color>
|
<Color x:Key="AppListBackgroundColor">#b3ffffff</Color>
|
||||||
<Color x:Key="AppListHoverBackgroundColor">#80cccccc</Color>
|
<Color x:Key="AppListHoverBackgroundColor">#80cccccc</Color>
|
||||||
<Color x:Key="WarningBackgroundColor">#FF6347</Color>
|
|
||||||
<Color x:Key="SecondaryTextColor">#A0000000</Color>
|
<Color x:Key="SecondaryTextColor">#A0000000</Color>
|
||||||
<Color x:Key="FavoriteApplicationIconColor">#fffcd12a</Color>
|
<Color x:Key="FavoriteApplicationIconColor">#fffcd12a</Color>
|
||||||
<Color x:Key="Switch">#13c3a4</Color>
|
<Color x:Key="Switch">#13c3a4</Color>
|
||||||
<Color x:Key="Unbounded">#FFFF4554</Color>
|
<Color x:Key="Unbounded">#FFFF4554</Color>
|
||||||
<Color x:Key="Custom">#6483F5</Color>
|
<Color x:Key="Custom">#6483F5</Color>
|
||||||
<Color x:Key="Warning">#800080</Color>
|
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
<ResourceDictionary x:Key="Dark">
|
<ResourceDictionary x:Key="Dark">
|
||||||
<SolidColorBrush x:Key="DataGridSelectionBackgroundBrush"
|
<SolidColorBrush x:Key="DataGridSelectionBackgroundBrush"
|
||||||
@@ -50,13 +46,11 @@
|
|||||||
<Color x:Key="MenuFlyoutPresenterBorderColor">#3D3D3D</Color>
|
<Color x:Key="MenuFlyoutPresenterBorderColor">#3D3D3D</Color>
|
||||||
<Color x:Key="AppListBackgroundColor">#0FFFFFFF</Color>
|
<Color x:Key="AppListBackgroundColor">#0FFFFFFF</Color>
|
||||||
<Color x:Key="AppListHoverBackgroundColor">#1EFFFFFF</Color>
|
<Color x:Key="AppListHoverBackgroundColor">#1EFFFFFF</Color>
|
||||||
<Color x:Key="WarningBackgroundColor">#FF6347</Color>
|
|
||||||
<Color x:Key="SecondaryTextColor">#A0FFFFFF</Color>
|
<Color x:Key="SecondaryTextColor">#A0FFFFFF</Color>
|
||||||
<Color x:Key="FavoriteApplicationIconColor">#fffcd12a</Color>
|
<Color x:Key="FavoriteApplicationIconColor">#fffcd12a</Color>
|
||||||
<Color x:Key="Switch">#FF2EEAC9</Color>
|
<Color x:Key="Switch">#FF2EEAC9</Color>
|
||||||
<Color x:Key="Unbounded">#FFFF4554</Color>
|
<Color x:Key="Unbounded">#FFFF4554</Color>
|
||||||
<Color x:Key="Custom">#6483F5</Color>
|
<Color x:Key="Custom">#6483F5</Color>
|
||||||
<Color x:Key="Warning">#FFA500</Color>
|
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
</ResourceDictionary.ThemeDictionaries>
|
</ResourceDictionary.ThemeDictionaries>
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,14 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace Ryujinx.Ava.Common
|
|
||||||
{
|
|
||||||
public static class ThemeManager
|
|
||||||
{
|
|
||||||
public static event Action ThemeChanged;
|
|
||||||
|
|
||||||
public static void OnThemeChanged()
|
|
||||||
{
|
|
||||||
ThemeChanged?.Invoke();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
using DiscordRPC;
|
using DiscordRPC;
|
||||||
using Gommon;
|
using Gommon;
|
||||||
using MsgPack;
|
|
||||||
using Ryujinx.Ava.Utilities;
|
using Ryujinx.Ava.Utilities;
|
||||||
using Ryujinx.Ava.Utilities.AppLibrary;
|
using Ryujinx.Ava.Utilities.AppLibrary;
|
||||||
using Ryujinx.Ava.Utilities.Configuration;
|
using Ryujinx.Ava.Utilities.Configuration;
|
||||||
@@ -11,7 +10,6 @@ using Ryujinx.HLE;
|
|||||||
using Ryujinx.HLE.Loaders.Processes;
|
using Ryujinx.HLE.Loaders.Processes;
|
||||||
using Ryujinx.Horizon;
|
using Ryujinx.Horizon;
|
||||||
using Ryujinx.Horizon.Prepo.Types;
|
using Ryujinx.Horizon.Prepo.Types;
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
namespace Ryujinx.Ava
|
namespace Ryujinx.Ava
|
||||||
@@ -26,7 +24,7 @@ namespace Ryujinx.Ava
|
|||||||
|
|
||||||
private static readonly string _description =
|
private static readonly string _description =
|
||||||
ReleaseInformation.IsValid
|
ReleaseInformation.IsValid
|
||||||
? $"{VersionString} {ReleaseInformation.ReleaseChannelOwner}/{ReleaseInformation.ReleaseChannelSourceRepo}@{ReleaseInformation.BuildGitHash}"
|
? $"{VersionString} {ReleaseInformation.ReleaseChannelOwner}/{ReleaseInformation.ReleaseChannelSourceRepo}"
|
||||||
: "dev build";
|
: "dev build";
|
||||||
|
|
||||||
private const string ApplicationId = "1293250299716173864";
|
private const string ApplicationId = "1293250299716173864";
|
||||||
@@ -58,6 +56,7 @@ namespace Ryujinx.Ava
|
|||||||
ConfigurationState.Instance.EnableDiscordIntegration.Event += Update;
|
ConfigurationState.Instance.EnableDiscordIntegration.Event += Update;
|
||||||
TitleIDs.CurrentApplication.Event += (_, e) => Use(e.NewValue);
|
TitleIDs.CurrentApplication.Event += (_, e) => Use(e.NewValue);
|
||||||
HorizonStatic.PlayReport += HandlePlayReport;
|
HorizonStatic.PlayReport += HandlePlayReport;
|
||||||
|
PlayReports.Initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void Update(object sender, ReactiveEventArgs<bool> evnt)
|
private static void Update(object sender, ReactiveEventArgs<bool> evnt)
|
||||||
|
|||||||
@@ -148,7 +148,7 @@ namespace Ryujinx.Headless
|
|||||||
IgnoreMissingServices = configurationState.System.IgnoreMissingServices;
|
IgnoreMissingServices = configurationState.System.IgnoreMissingServices;
|
||||||
|
|
||||||
if (NeedsOverride(nameof(IgnoreControllerApplet)))
|
if (NeedsOverride(nameof(IgnoreControllerApplet)))
|
||||||
IgnoreControllerApplet = configurationState.System.IgnoreApplet;
|
IgnoreControllerApplet = configurationState.System.IgnoreControllerApplet;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -387,7 +387,7 @@ namespace Ryujinx.Headless
|
|||||||
[Option("graphics-shaders-dump-path", Required = false, HelpText = "Dumps shaders in this local directory. (Developer only)")]
|
[Option("graphics-shaders-dump-path", Required = false, HelpText = "Dumps shaders in this local directory. (Developer only)")]
|
||||||
public string GraphicsShadersDumpPath { get; set; }
|
public string GraphicsShadersDumpPath { get; set; }
|
||||||
|
|
||||||
[Option("graphics-backend", Required = false, Default = GraphicsBackend.OpenGl, HelpText = "Change Graphics Backend to use.")]
|
[Option("graphics-backend", Required = false, Default = GraphicsBackend.Vulkan, HelpText = "Change Graphics Backend to use.")]
|
||||||
public GraphicsBackend GraphicsBackend { get; set; }
|
public GraphicsBackend GraphicsBackend { get; set; }
|
||||||
|
|
||||||
[Option("preferred-gpu-vendor", Required = false, Default = "", HelpText = "When using the Vulkan backend, prefer using the GPU from the specified vendor.")]
|
[Option("preferred-gpu-vendor", Required = false, Default = "", HelpText = "When using the Vulkan backend, prefer using the GPU from the specified vendor.")]
|
||||||
|
|||||||
@@ -513,7 +513,7 @@ namespace Ryujinx.Headless
|
|||||||
Exit();
|
Exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool DisplayErrorAppletDialog(string title, string message, string[] buttonsText)
|
public bool DisplayErrorAppletDialog(string title, string message, string[] buttonsText, (uint Module, uint Description)? errorCode = null)
|
||||||
{
|
{
|
||||||
SDL_MessageBoxData data = new()
|
SDL_MessageBoxData data = new()
|
||||||
{
|
{
|
||||||
@@ -521,7 +521,7 @@ namespace Ryujinx.Headless
|
|||||||
message = message,
|
message = message,
|
||||||
buttons = new SDL_MessageBoxButtonData[buttonsText.Length],
|
buttons = new SDL_MessageBoxButtonData[buttonsText.Length],
|
||||||
numbuttons = buttonsText.Length,
|
numbuttons = buttonsText.Length,
|
||||||
window = WindowHandle,
|
window = WindowHandle
|
||||||
};
|
};
|
||||||
|
|
||||||
for (int i = 0; i < buttonsText.Length; i++)
|
for (int i = 0; i < buttonsText.Length; i++)
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ namespace Ryujinx.Ava
|
|||||||
public static double DesktopScaleFactor { get; set; } = 1.0;
|
public static double DesktopScaleFactor { get; set; } = 1.0;
|
||||||
public static string Version { get; private set; }
|
public static string Version { get; private set; }
|
||||||
public static string ConfigurationPath { get; private set; }
|
public static string ConfigurationPath { get; private set; }
|
||||||
public static string GlobalConfigurationPath { get; private set; }
|
|
||||||
public static bool PreviewerDetached { get; private set; }
|
public static bool PreviewerDetached { get; private set; }
|
||||||
public static bool UseHardwareAcceleration { get; private set; }
|
public static bool UseHardwareAcceleration { get; private set; }
|
||||||
|
|
||||||
@@ -156,43 +155,11 @@ namespace Ryujinx.Ava
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool FindGameConfig(string gameDir)
|
|
||||||
{
|
|
||||||
if (File.Exists(gameDir))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string GetDirGameUserConfig(string gameId, bool rememberGlobalDir = false, bool changeFolderForGame = false)
|
|
||||||
{
|
|
||||||
string gameDir = Path.Combine(AppDataManager.GamesDirPath, gameId, ReleaseInformation.ConfigName);
|
|
||||||
|
|
||||||
// Should load with the game if there is a custom setting for the game
|
|
||||||
if (rememberGlobalDir)
|
|
||||||
{
|
|
||||||
GlobalConfigurationPath = ConfigurationPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (changeFolderForGame)
|
|
||||||
{
|
|
||||||
ConfigurationPath = gameDir;
|
|
||||||
}
|
|
||||||
|
|
||||||
return gameDir;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void ReloadConfig()
|
public static void ReloadConfig()
|
||||||
{
|
{
|
||||||
//It is necessary that when a user setting appears, the global setting remains available
|
|
||||||
GlobalConfigurationPath = null;
|
|
||||||
|
|
||||||
string localConfigurationPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ReleaseInformation.ConfigName);
|
string localConfigurationPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ReleaseInformation.ConfigName);
|
||||||
string appDataConfigurationPath = Path.Combine(AppDataManager.BaseDirPath, ReleaseInformation.ConfigName);
|
string appDataConfigurationPath = Path.Combine(AppDataManager.BaseDirPath, ReleaseInformation.ConfigName);
|
||||||
|
|
||||||
|
|
||||||
// Now load the configuration as the other subsystems are now registered
|
// Now load the configuration as the other subsystems are now registered
|
||||||
if (File.Exists(localConfigurationPath))
|
if (File.Exists(localConfigurationPath))
|
||||||
{
|
{
|
||||||
@@ -265,35 +232,8 @@ namespace Ryujinx.Ava
|
|||||||
_ => ConfigurationState.Instance.HideCursor,
|
_ => ConfigurationState.Instance.HideCursor,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Check if memoryManagerMode was overridden.
|
|
||||||
if (CommandLineState.OverrideMemoryManagerMode is not null)
|
|
||||||
if (Enum.TryParse(CommandLineState.OverrideMemoryManagerMode, true, out MemoryManagerMode result))
|
|
||||||
{
|
|
||||||
ConfigurationState.Instance.System.MemoryManagerMode.Value = result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if PPTC was overridden.
|
// Check if hardware-acceleration was overridden.
|
||||||
if (CommandLineState.OverridePPTC is not null)
|
|
||||||
if (Enum.TryParse(CommandLineState.OverridePPTC, true, out bool result))
|
|
||||||
{
|
|
||||||
ConfigurationState.Instance.System.EnablePtc.Value = result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if region was overridden.
|
|
||||||
if (CommandLineState.OverrideSystemRegion is not null)
|
|
||||||
if (Enum.TryParse(CommandLineState.OverrideSystemRegion, true, out Ryujinx.HLE.HOS.SystemState.RegionCode result))
|
|
||||||
{
|
|
||||||
ConfigurationState.Instance.System.Region.Value = (Utilities.Configuration.System.Region)result;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Check if language was overridden.
|
|
||||||
if (CommandLineState.OverrideSystemLanguage is not null)
|
|
||||||
if (Enum.TryParse(CommandLineState.OverrideSystemLanguage, true, out Ryujinx.HLE.HOS.SystemState.SystemLanguage result))
|
|
||||||
{
|
|
||||||
ConfigurationState.Instance.System.Language.Value = (Utilities.Configuration.System.Language)result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if hardware-acceleration was overridden. MemoryManagerMode ( outdated! )
|
|
||||||
if (CommandLineState.OverrideHardwareAcceleration != null)
|
if (CommandLineState.OverrideHardwareAcceleration != null)
|
||||||
UseHardwareAcceleration = CommandLineState.OverrideHardwareAcceleration.Value;
|
UseHardwareAcceleration = CommandLineState.OverrideHardwareAcceleration.Value;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ namespace Ryujinx.Ava
|
|||||||
{
|
{
|
||||||
public class RyujinxApp : Application
|
public class RyujinxApp : Application
|
||||||
{
|
{
|
||||||
|
public static event Action ThemeChanged;
|
||||||
|
|
||||||
internal static string FormatTitle(LocaleKeys? windowTitleKey = null, bool includeVersion = true)
|
internal static string FormatTitle(LocaleKeys? windowTitleKey = null, bool includeVersion = true)
|
||||||
=> windowTitleKey is null
|
=> windowTitleKey is null
|
||||||
? $"{FullAppName}{(includeVersion ? $" {Program.Version}" : string.Empty)}"
|
? $"{FullAppName}{(includeVersion ? $" {Program.Version}" : string.Empty)}"
|
||||||
@@ -112,7 +114,7 @@ namespace Ryujinx.Ava
|
|||||||
baseStyle = ConfigurationState.Instance.UI.BaseStyle;
|
baseStyle = ConfigurationState.Instance.UI.BaseStyle;
|
||||||
}
|
}
|
||||||
|
|
||||||
ThemeManager.OnThemeChanged();
|
ThemeChanged?.Invoke();
|
||||||
|
|
||||||
RequestedThemeVariant = baseStyle switch
|
RequestedThemeVariant = baseStyle switch
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ namespace Ryujinx.Ava.UI.Applet
|
|||||||
|
|
||||||
bool okPressed = false;
|
bool okPressed = false;
|
||||||
|
|
||||||
if (ConfigurationState.Instance.System.IgnoreApplet)
|
if (ConfigurationState.Instance.System.IgnoreControllerApplet)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
Dispatcher.UIThread.InvokeAsync(async () =>
|
Dispatcher.UIThread.InvokeAsync(async () =>
|
||||||
@@ -75,31 +75,32 @@ namespace Ryujinx.Ava.UI.Applet
|
|||||||
bool opened = false;
|
bool opened = false;
|
||||||
|
|
||||||
UserResult response = await ContentDialogHelper.ShowDeferredContentDialog(_parent,
|
UserResult response = await ContentDialogHelper.ShowDeferredContentDialog(_parent,
|
||||||
title,
|
title,
|
||||||
message,
|
message,
|
||||||
string.Empty,
|
string.Empty,
|
||||||
LocaleManager.Instance[LocaleKeys.DialogOpenSettingsWindowLabel],
|
LocaleManager.Instance[LocaleKeys.DialogOpenSettingsWindowLabel],
|
||||||
string.Empty,
|
string.Empty,
|
||||||
LocaleManager.Instance[LocaleKeys.SettingsButtonClose],
|
LocaleManager.Instance[LocaleKeys.SettingsButtonClose],
|
||||||
(int)Symbol.Important,
|
(int)Symbol.Important,
|
||||||
deferEvent,
|
deferEvent,
|
||||||
async window =>
|
async window =>
|
||||||
{
|
{
|
||||||
if (opened)
|
if (opened)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
opened = true;
|
opened = true;
|
||||||
|
|
||||||
_parent.SettingsWindow = new SettingsWindow(_parent.VirtualFileSystem, _parent.ContentManager);
|
_parent.SettingsWindow =
|
||||||
|
new SettingsWindow(_parent.VirtualFileSystem, _parent.ContentManager);
|
||||||
|
|
||||||
await _parent.SettingsWindow.ShowDialog(window);
|
await _parent.SettingsWindow.ShowDialog(window);
|
||||||
|
|
||||||
_parent.SettingsWindow = null;
|
_parent.SettingsWindow = null;
|
||||||
|
|
||||||
opened = false;
|
opened = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response == UserResult.Ok)
|
if (response == UserResult.Ok)
|
||||||
{
|
{
|
||||||
@@ -110,7 +111,9 @@ namespace Ryujinx.Ava.UI.Applet
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogMessageDialogErrorExceptionMessage, ex));
|
await ContentDialogHelper.CreateErrorDialog(
|
||||||
|
LocaleManager.Instance.UpdateAndGetDynamicValue(
|
||||||
|
LocaleKeys.DialogMessageDialogErrorExceptionMessage, ex));
|
||||||
|
|
||||||
dialogCloseEvent.Set();
|
dialogCloseEvent.Set();
|
||||||
}
|
}
|
||||||
@@ -134,7 +137,9 @@ namespace Ryujinx.Ava.UI.Applet
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
_parent.ViewModel.AppHost.NpadManager.BlockInputUpdates();
|
_parent.ViewModel.AppHost.NpadManager.BlockInputUpdates();
|
||||||
(UserResult result, string userInput) = await SwkbdAppletDialog.ShowInputDialog(LocaleManager.Instance[LocaleKeys.SoftwareKeyboard], args);
|
(UserResult result, string userInput) =
|
||||||
|
await SwkbdAppletDialog.ShowInputDialog(LocaleManager.Instance[LocaleKeys.SoftwareKeyboard],
|
||||||
|
args);
|
||||||
|
|
||||||
if (result == UserResult.Ok)
|
if (result == UserResult.Ok)
|
||||||
{
|
{
|
||||||
@@ -146,7 +151,9 @@ namespace Ryujinx.Ava.UI.Applet
|
|||||||
{
|
{
|
||||||
error = true;
|
error = true;
|
||||||
|
|
||||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogSoftwareKeyboardErrorExceptionMessage, ex));
|
await ContentDialogHelper.CreateErrorDialog(
|
||||||
|
LocaleManager.Instance.UpdateAndGetDynamicValue(
|
||||||
|
LocaleKeys.DialogSoftwareKeyboardErrorExceptionMessage, ex));
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@@ -177,7 +184,8 @@ namespace Ryujinx.Ava.UI.Applet
|
|||||||
args.InitialText = "Ryujinx";
|
args.InitialText = "Ryujinx";
|
||||||
args.StringLengthMin = 1;
|
args.StringLengthMin = 1;
|
||||||
args.StringLengthMax = 25;
|
args.StringLengthMax = 25;
|
||||||
(UserResult result, string userInput) = await SwkbdAppletDialog.ShowInputDialog(LocaleManager.Instance[LocaleKeys.CabinetDialog], args);
|
(UserResult result, string userInput) =
|
||||||
|
await SwkbdAppletDialog.ShowInputDialog(LocaleManager.Instance[LocaleKeys.CabinetDialog], args);
|
||||||
if (result == UserResult.Ok)
|
if (result == UserResult.Ok)
|
||||||
{
|
{
|
||||||
inputText = userInput;
|
inputText = userInput;
|
||||||
@@ -201,11 +209,13 @@ namespace Ryujinx.Ava.UI.Applet
|
|||||||
Dispatcher.UIThread.InvokeAsync(async () =>
|
Dispatcher.UIThread.InvokeAsync(async () =>
|
||||||
{
|
{
|
||||||
dialogCloseEvent.Set();
|
dialogCloseEvent.Set();
|
||||||
await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.CabinetScanDialog],
|
await ContentDialogHelper.CreateInfoDialog(
|
||||||
string.Empty,
|
LocaleManager.Instance[LocaleKeys.CabinetScanDialog],
|
||||||
LocaleManager.Instance[LocaleKeys.InputDialogOk],
|
string.Empty,
|
||||||
string.Empty,
|
LocaleManager.Instance[LocaleKeys.InputDialogOk],
|
||||||
LocaleManager.Instance[LocaleKeys.CabinetTitle]);
|
string.Empty,
|
||||||
|
LocaleManager.Instance[LocaleKeys.CabinetTitle]
|
||||||
|
);
|
||||||
});
|
});
|
||||||
dialogCloseEvent.WaitOne();
|
dialogCloseEvent.WaitOne();
|
||||||
}
|
}
|
||||||
@@ -217,7 +227,8 @@ namespace Ryujinx.Ava.UI.Applet
|
|||||||
_parent.ViewModel.AppHost?.Stop();
|
_parent.ViewModel.AppHost?.Stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool DisplayErrorAppletDialog(string title, string message, string[] buttons)
|
public bool DisplayErrorAppletDialog(string title, string message, string[] buttons,
|
||||||
|
(uint Module, uint Description)? errorCode = null)
|
||||||
{
|
{
|
||||||
ManualResetEvent dialogCloseEvent = new(false);
|
ManualResetEvent dialogCloseEvent = new(false);
|
||||||
|
|
||||||
@@ -229,9 +240,7 @@ namespace Ryujinx.Ava.UI.Applet
|
|||||||
{
|
{
|
||||||
ErrorAppletWindow msgDialog = new(_parent, buttons, message)
|
ErrorAppletWindow msgDialog = new(_parent, buttons, message)
|
||||||
{
|
{
|
||||||
Title = title,
|
Title = title, WindowStartupLocation = WindowStartupLocation.CenterScreen, Width = 400
|
||||||
WindowStartupLocation = WindowStartupLocation.CenterScreen,
|
|
||||||
Width = 400
|
|
||||||
};
|
};
|
||||||
|
|
||||||
object response = await msgDialog.Run();
|
object response = await msgDialog.Run();
|
||||||
@@ -249,7 +258,9 @@ namespace Ryujinx.Ava.UI.Applet
|
|||||||
{
|
{
|
||||||
dialogCloseEvent.Set();
|
dialogCloseEvent.Set();
|
||||||
|
|
||||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogErrorAppletErrorExceptionMessage, ex));
|
await ContentDialogHelper.CreateErrorDialog(
|
||||||
|
LocaleManager.Instance.UpdateAndGetDynamicValue(
|
||||||
|
LocaleKeys.DialogErrorAppletErrorExceptionMessage, ex));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -259,38 +270,36 @@ namespace Ryujinx.Ava.UI.Applet
|
|||||||
}
|
}
|
||||||
|
|
||||||
public IDynamicTextInputHandler CreateDynamicTextInputHandler() => new AvaloniaDynamicTextInputHandler(_parent);
|
public IDynamicTextInputHandler CreateDynamicTextInputHandler() => new AvaloniaDynamicTextInputHandler(_parent);
|
||||||
|
|
||||||
public UserProfile ShowPlayerSelectDialog()
|
public UserProfile ShowPlayerSelectDialog()
|
||||||
{
|
{
|
||||||
UserId selected = UserId.Null;
|
UserId selected = UserId.Null;
|
||||||
byte[] defaultGuestImage = EmbeddedResources.Read("Ryujinx.HLE/HOS/Services/Account/Acc/GuestUserImage.jpg");
|
byte[] defaultGuestImage = EmbeddedResources.Read("Ryujinx.HLE/HOS/Services/Account/Acc/GuestUserImage.jpg");
|
||||||
UserProfile guest = new(new UserId("00000000000000000000000000000080"), "Guest", defaultGuestImage);
|
UserProfile guest = new(new UserId("00000000000000000000000000000080"), "Guest", defaultGuestImage);
|
||||||
|
|
||||||
ManualResetEvent dialogCloseEvent = new(false);
|
ManualResetEvent dialogCloseEvent = new(false);
|
||||||
|
|
||||||
Dispatcher.UIThread.InvokeAsync(async () =>
|
Dispatcher.UIThread.InvokeAsync(async () =>
|
||||||
{
|
{
|
||||||
ObservableCollection<BaseModel> profiles = [];
|
ObservableCollection<BaseModel> profiles = [];
|
||||||
NavigationDialogHost nav = new();
|
NavigationDialogHost nav = new();
|
||||||
|
|
||||||
_parent.AccountManager.GetAllUsers()
|
_parent.AccountManager.GetAllUsers()
|
||||||
.OrderBy(x => x.Name)
|
.OrderBy(x => x.Name)
|
||||||
.ForEach(profile => profiles.Add(new Models.UserProfile(profile, nav)));
|
.ForEach(profile => profiles.Add(new Models.UserProfile(profile, nav)));
|
||||||
|
|
||||||
profiles.Add(new Models.UserProfile(guest, nav));
|
profiles.Add(new Models.UserProfile(guest, nav));
|
||||||
UserSelectorDialogViewModel viewModel = new()
|
ProfileSelectorDialogViewModel viewModel = new()
|
||||||
{
|
{
|
||||||
Profiles = profiles,
|
Profiles = profiles, SelectedUserId = _parent.AccountManager.LastOpenedUser.UserId
|
||||||
SelectedUserId = _parent.AccountManager.LastOpenedUser.UserId
|
|
||||||
};
|
};
|
||||||
UserSelectorDialog content = new(viewModel);
|
(selected, _) = await ProfileSelectorDialog.ShowInputDialog(viewModel);
|
||||||
(selected, _) = await UserSelectorDialog.ShowInputDialog(content);
|
|
||||||
|
|
||||||
dialogCloseEvent.Set();
|
dialogCloseEvent.Set();
|
||||||
});
|
});
|
||||||
|
|
||||||
dialogCloseEvent.WaitOne();
|
dialogCloseEvent.WaitOne();
|
||||||
|
|
||||||
UserProfile profile = _parent.AccountManager.LastOpenedUser;
|
UserProfile profile = _parent.AccountManager.LastOpenedUser;
|
||||||
if (selected == guest.UserId)
|
if (selected == guest.UserId)
|
||||||
{
|
{
|
||||||
@@ -311,6 +320,7 @@ namespace Ryujinx.Ava.UI.Applet
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return profile;
|
return profile;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<UserControl
|
<UserControl
|
||||||
x:Class="Ryujinx.Ava.UI.Applet.UserSelectorDialog"
|
x:Class="Ryujinx.Ava.UI.Applet.ProfileSelectorDialog"
|
||||||
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:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
@@ -12,9 +12,9 @@
|
|||||||
d:DesignWidth="800"
|
d:DesignWidth="800"
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
Focusable="True"
|
Focusable="True"
|
||||||
x:DataType="viewModels:UserSelectorDialogViewModel">
|
x:DataType="viewModels:ProfileSelectorDialogViewModel">
|
||||||
<Design.DataContext>
|
<Design.DataContext>
|
||||||
<viewModels:UserSelectorDialogViewModel />
|
<viewModels:ProfileSelectorDialogViewModel />
|
||||||
</Design.DataContext>
|
</Design.DataContext>
|
||||||
|
|
||||||
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
|
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
|
||||||
@@ -16,15 +16,15 @@ using UserProfileSft = Ryujinx.HLE.HOS.Services.Account.Acc.UserProfile;
|
|||||||
|
|
||||||
namespace Ryujinx.Ava.UI.Applet
|
namespace Ryujinx.Ava.UI.Applet
|
||||||
{
|
{
|
||||||
public partial class UserSelectorDialog : UserControl, INotifyPropertyChanged
|
public partial class ProfileSelectorDialog : UserControl
|
||||||
{
|
{
|
||||||
public UserSelectorDialogViewModel ViewModel { get; set; }
|
public ProfileSelectorDialogViewModel ViewModel { get; set; }
|
||||||
|
|
||||||
public UserSelectorDialog(UserSelectorDialogViewModel viewModel)
|
public ProfileSelectorDialog(ProfileSelectorDialogViewModel viewModel)
|
||||||
{
|
{
|
||||||
|
DataContext = ViewModel = viewModel;
|
||||||
|
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
ViewModel = viewModel;
|
|
||||||
DataContext = ViewModel;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Grid_PointerEntered(object sender, PointerEventArgs e)
|
private void Grid_PointerEntered(object sender, PointerEventArgs e)
|
||||||
@@ -54,7 +54,7 @@ namespace Ryujinx.Ava.UI.Applet
|
|||||||
if (ViewModel.Profiles[selectedIndex] is UserProfile userProfile)
|
if (ViewModel.Profiles[selectedIndex] is UserProfile userProfile)
|
||||||
{
|
{
|
||||||
ViewModel.SelectedUserId = userProfile.UserId;
|
ViewModel.SelectedUserId = userProfile.UserId;
|
||||||
Logger.Info?.Print(LogClass.UI, $"Selected user: {userProfile.UserId}");
|
Logger.Info?.Print(LogClass.UI, $"Selected: {userProfile.UserId}", "ProfileSelector");
|
||||||
|
|
||||||
ObservableCollection<BaseModel> newProfiles = [];
|
ObservableCollection<BaseModel> newProfiles = [];
|
||||||
|
|
||||||
@@ -79,7 +79,7 @@ namespace Ryujinx.Ava.UI.Applet
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<(UserId Id, bool Result)> ShowInputDialog(UserSelectorDialog content)
|
public static async Task<(UserId Id, bool Result)> ShowInputDialog(ProfileSelectorDialogViewModel viewModel)
|
||||||
{
|
{
|
||||||
ContentDialog contentDialog = new()
|
ContentDialog contentDialog = new()
|
||||||
{
|
{
|
||||||
@@ -87,22 +87,25 @@ namespace Ryujinx.Ava.UI.Applet
|
|||||||
PrimaryButtonText = LocaleManager.Instance[LocaleKeys.Continue],
|
PrimaryButtonText = LocaleManager.Instance[LocaleKeys.Continue],
|
||||||
SecondaryButtonText = string.Empty,
|
SecondaryButtonText = string.Empty,
|
||||||
CloseButtonText = LocaleManager.Instance[LocaleKeys.Cancel],
|
CloseButtonText = LocaleManager.Instance[LocaleKeys.Cancel],
|
||||||
Content = content,
|
Content = new ProfileSelectorDialog(viewModel),
|
||||||
Padding = new Thickness(0)
|
Padding = new Thickness(0)
|
||||||
};
|
};
|
||||||
|
|
||||||
UserId result = UserId.Null;
|
UserId result = UserId.Null;
|
||||||
bool input = false;
|
bool input = false;
|
||||||
|
|
||||||
|
contentDialog.Closed += Handler;
|
||||||
|
|
||||||
|
await ContentDialogHelper.ShowAsync(contentDialog);
|
||||||
|
|
||||||
|
return (result, input);
|
||||||
|
|
||||||
void Handler(ContentDialog sender, ContentDialogClosedEventArgs eventArgs)
|
void Handler(ContentDialog sender, ContentDialogClosedEventArgs eventArgs)
|
||||||
{
|
{
|
||||||
if (eventArgs.Result == ContentDialogResult.Primary)
|
if (eventArgs.Result == ContentDialogResult.Primary)
|
||||||
{
|
{
|
||||||
if (contentDialog.Content is UserSelectorDialog view)
|
result = viewModel.SelectedUserId;
|
||||||
{
|
input = true;
|
||||||
result = view.ViewModel.SelectedUserId;
|
|
||||||
input = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -110,12 +113,6 @@ namespace Ryujinx.Ava.UI.Applet
|
|||||||
input = false;
|
input = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
contentDialog.Closed += Handler;
|
|
||||||
|
|
||||||
await ContentDialogHelper.ShowAsync(contentDialog);
|
|
||||||
|
|
||||||
return (result, input);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -19,11 +19,6 @@
|
|||||||
Header="{ext:Locale GameListContextMenuCreateShortcut}"
|
Header="{ext:Locale GameListContextMenuCreateShortcut}"
|
||||||
Icon="{ext:Icon fa-solid fa-bookmark}"
|
Icon="{ext:Icon fa-solid fa-bookmark}"
|
||||||
ToolTip.Tip="{OnPlatform Default={ext:Locale GameListContextMenuCreateShortcutToolTip}, macOS={ext:Locale GameListContextMenuCreateShortcutToolTipMacOS}}" />
|
ToolTip.Tip="{OnPlatform Default={ext:Locale GameListContextMenuCreateShortcutToolTip}, macOS={ext:Locale GameListContextMenuCreateShortcutToolTipMacOS}}" />
|
||||||
<MenuItem
|
|
||||||
Click="EditGameConfiguration_Click"
|
|
||||||
Header="{ext:Locale GameListContextMenuEditGameConfiguration}"
|
|
||||||
Icon="{ext:Icon fa-solid fa-gear}"
|
|
||||||
ToolTip.Tip="{ext:Locale EditGameConfigurationToolTip}" />
|
|
||||||
<MenuItem
|
<MenuItem
|
||||||
IsVisible="{Binding HasCompatibilityEntry}"
|
IsVisible="{Binding HasCompatibilityEntry}"
|
||||||
Click="OpenApplicationCompatibility_Click"
|
Click="OpenApplicationCompatibility_Click"
|
||||||
|
|||||||
@@ -386,26 +386,13 @@ namespace Ryujinx.Ava.UI.Controls
|
|||||||
viewModel.SelectedApplication.Icon
|
viewModel.SelectedApplication.Icon
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async void EditGameConfiguration_Click(object sender, RoutedEventArgs args)
|
|
||||||
{
|
|
||||||
if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
|
|
||||||
{
|
|
||||||
await new UserConfigWindows(viewModel).ShowDialog((Window)viewModel.TopLevel);
|
|
||||||
|
|
||||||
//just checking for file presence
|
|
||||||
viewModel.SelectedApplication.UserConfig = File.Exists(Program.GetDirGameUserConfig(viewModel.SelectedApplication.IdString,false,false));
|
|
||||||
|
|
||||||
viewModel.RefreshView();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async void OpenApplicationCompatibility_Click(object sender, RoutedEventArgs args)
|
public async void OpenApplicationCompatibility_Click(object sender, RoutedEventArgs args)
|
||||||
{
|
{
|
||||||
if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
|
if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
|
||||||
await CompatibilityList.Show(viewModel.SelectedApplication.IdString);
|
await CompatibilityList.Show(viewModel.SelectedApplication.IdString);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async void OpenApplicationData_Click(object sender, RoutedEventArgs args)
|
public async void OpenApplicationData_Click(object sender, RoutedEventArgs args)
|
||||||
{
|
{
|
||||||
if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
|
if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
|
||||||
|
|||||||
@@ -119,17 +119,23 @@
|
|||||||
TextWrapping="Wrap" >
|
TextWrapping="Wrap" >
|
||||||
</TextBlock>
|
</TextBlock>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<StackPanel Orientation="Horizontal" Spacing="5">
|
<StackPanel Orientation="Horizontal" Spacing="5" ToolTip.Tip="{Binding DynamicRichPresenceDescription}">
|
||||||
<ui:SymbolIcon Foreground="ForestGreen" Symbol="Checkmark" IsVisible="{Binding AppData.HasDynamicRichPresenceSupport}"/>
|
<ui:SymbolIcon
|
||||||
|
Foreground="ForestGreen"
|
||||||
|
Symbol="Checkmark"
|
||||||
|
IsVisible="{Binding AppData.HasDynamicRichPresenceSupport}"/>
|
||||||
<TextBlock
|
<TextBlock
|
||||||
Foreground="ForestGreen"
|
Foreground="ForestGreen"
|
||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
IsVisible="{Binding AppData.HasDynamicRichPresenceSupport}"
|
IsVisible="{Binding AppData.HasDynamicRichPresenceSupport}"
|
||||||
Text="{ext:Locale GameInfoRpcDynamic}"
|
Text="{ext:Locale GameInfoRpcDynamic}"
|
||||||
TextAlignment="Start"
|
TextAlignment="Start"
|
||||||
TextWrapping="Wrap" >
|
TextWrapping="Wrap">
|
||||||
</TextBlock>
|
</TextBlock>
|
||||||
<ui:SymbolIcon Foreground="Red" Symbol="Cancel" IsVisible="{Binding !AppData.HasDynamicRichPresenceSupport}"/>
|
<ui:SymbolIcon
|
||||||
|
Foreground="Red"
|
||||||
|
Symbol="Cancel"
|
||||||
|
IsVisible="{Binding !AppData.HasDynamicRichPresenceSupport}"/>
|
||||||
<TextBlock
|
<TextBlock
|
||||||
Foreground="Red"
|
Foreground="Red"
|
||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
using Avalonia;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Controls;
|
|
||||||
using Avalonia.Input.Platform;
|
using Avalonia.Input.Platform;
|
||||||
using Avalonia.Interactivity;
|
using Avalonia.Interactivity;
|
||||||
using Avalonia.Styling;
|
using Avalonia.Styling;
|
||||||
using FluentAvalonia.UI.Controls;
|
using FluentAvalonia.UI.Controls;
|
||||||
using Ryujinx.Ava;
|
|
||||||
using Ryujinx.Ava.Common.Locale;
|
using Ryujinx.Ava.Common.Locale;
|
||||||
using Ryujinx.Ava.UI.Controls;
|
|
||||||
using Ryujinx.Ava.UI.Helpers;
|
using Ryujinx.Ava.UI.Helpers;
|
||||||
using Ryujinx.Ava.UI.ViewModels;
|
using Ryujinx.Ava.UI.ViewModels;
|
||||||
using Ryujinx.Ava.UI.Windows;
|
using Ryujinx.Ava.UI.Windows;
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
|
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||||
xmlns:ext="clr-namespace:Ryujinx.Ava.Common.Markup"
|
|
||||||
d:DesignHeight="450"
|
d:DesignHeight="450"
|
||||||
d:DesignWidth="800"
|
d:DesignWidth="800"
|
||||||
Focusable="True"
|
Focusable="True"
|
||||||
@@ -74,18 +73,12 @@
|
|||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
VerticalAlignment="Stretch"
|
VerticalAlignment="Stretch"
|
||||||
IsVisible="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).ShowNames}">
|
IsVisible="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).ShowNames}">
|
||||||
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
|
<TextBlock
|
||||||
<TextBlock
|
HorizontalAlignment="Center"
|
||||||
Text="{Binding Name}"
|
VerticalAlignment="Center"
|
||||||
TextAlignment="Center"
|
Text="{Binding Name}"
|
||||||
TextWrapping="Wrap" />
|
TextAlignment="Center"
|
||||||
<TextBlock
|
TextWrapping="Wrap" />
|
||||||
IsVisible="{Binding UserConfig}"
|
|
||||||
Text="{ext:Locale UserConfigurationHeader}"
|
|
||||||
TextAlignment="Center"
|
|
||||||
TextWrapping="Wrap"
|
|
||||||
Foreground="{DynamicResource Warning}" />
|
|
||||||
</StackPanel>
|
|
||||||
</Panel>
|
</Panel>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Border>
|
</Border>
|
||||||
@@ -93,28 +86,10 @@
|
|||||||
Margin="5,5,0,0"
|
Margin="5,5,0,0"
|
||||||
HorizontalAlignment="Left"
|
HorizontalAlignment="Left"
|
||||||
VerticalAlignment="Top"
|
VerticalAlignment="Top"
|
||||||
FontSize="18"
|
FontSize="16"
|
||||||
Foreground="{DynamicResource FavoriteApplicationIconColor}"
|
Foreground="{DynamicResource FavoriteApplicationIconColor}"
|
||||||
IsVisible="{Binding Favorite}"
|
IsVisible="{Binding Favorite}"
|
||||||
Symbol="StarFilled" />
|
Symbol="StarFilled" />
|
||||||
<Grid IsVisible="{Binding !$parent[UserControl].((viewModels:MainWindowViewModel)DataContext).ShowNames}">
|
|
||||||
<Border
|
|
||||||
Margin="15,35,5,15"
|
|
||||||
HorizontalAlignment="Left"
|
|
||||||
VerticalAlignment="Bottom"
|
|
||||||
Width="90"
|
|
||||||
Height="20"
|
|
||||||
CornerRadius="4"
|
|
||||||
IsVisible="{Binding UserConfig}"
|
|
||||||
Background="{DynamicResource ThemeContentBackgroundColor}">
|
|
||||||
<TextBlock
|
|
||||||
HorizontalAlignment="Center"
|
|
||||||
VerticalAlignment="Center"
|
|
||||||
Text="{ext:Locale UserConfigurationHeader}"
|
|
||||||
TextAlignment="Center"
|
|
||||||
TextWrapping="Wrap" />
|
|
||||||
</Border>
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
</Grid>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</ListBox.ItemTemplate>
|
</ListBox.ItemTemplate>
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
|
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
|
||||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:ext="clr-namespace:Ryujinx.Ava.Common.Markup"
|
|
||||||
d:DesignHeight="450"
|
d:DesignHeight="450"
|
||||||
d:DesignWidth="800"
|
d:DesignWidth="800"
|
||||||
Focusable="True"
|
Focusable="True"
|
||||||
@@ -157,13 +156,6 @@
|
|||||||
Text="{Binding Converter={x:Static helpers:MultiplayerInfoConverter.Instance}}"
|
Text="{Binding Converter={x:Static helpers:MultiplayerInfoConverter.Instance}}"
|
||||||
TextAlignment="Start"
|
TextAlignment="Start"
|
||||||
TextWrapping="Wrap"/>
|
TextWrapping="Wrap"/>
|
||||||
<TextBlock
|
|
||||||
HorizontalAlignment="Stretch"
|
|
||||||
IsVisible="{Binding UserConfig}"
|
|
||||||
Text="{ext:Locale UserConfigurationHeader}"
|
|
||||||
TextAlignment="Start"
|
|
||||||
TextWrapping="Wrap"
|
|
||||||
Foreground="{DynamicResource Warning}" />
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<StackPanel
|
<StackPanel
|
||||||
Grid.Column="4"
|
Grid.Column="4"
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ using Ryujinx.Ava.UI.ViewModels;
|
|||||||
using Ryujinx.Ava.Utilities.AppLibrary;
|
using Ryujinx.Ava.Utilities.AppLibrary;
|
||||||
using Ryujinx.Ava.Utilities.Compat;
|
using Ryujinx.Ava.Utilities.Compat;
|
||||||
using System;
|
using System;
|
||||||
using System.Globalization;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.Controls
|
namespace Ryujinx.Ava.UI.Controls
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using Avalonia.Logging;
|
using Avalonia.Logging;
|
||||||
using Avalonia.Utilities;
|
using Avalonia.Utilities;
|
||||||
using Gommon;
|
using Gommon;
|
||||||
|
using Ryujinx.Ava.Utilities.Configuration;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
using System;
|
using System;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
@@ -14,13 +15,19 @@ namespace Ryujinx.Ava.UI.Helpers
|
|||||||
|
|
||||||
internal class LoggerAdapter : ILogSink
|
internal class LoggerAdapter : ILogSink
|
||||||
{
|
{
|
||||||
|
private static bool _avaloniaLogsEnabled = ConfigurationState.Instance.Logger.EnableAvaloniaLog;
|
||||||
|
|
||||||
public static void Register()
|
public static void Register()
|
||||||
{
|
{
|
||||||
AvaLogger.Sink = new LoggerAdapter();
|
AvaLogger.Sink = new LoggerAdapter();
|
||||||
|
ConfigurationState.Instance.Logger.EnableAvaloniaLog.Event
|
||||||
|
+= (_, e) => _avaloniaLogsEnabled = e.NewValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static RyuLogger.Log? GetLog(AvaLogLevel level, string area)
|
private static RyuLogger.Log? GetLog(AvaLogLevel level, string area)
|
||||||
{
|
{
|
||||||
|
if (!_avaloniaLogsEnabled) return null;
|
||||||
|
|
||||||
return level switch
|
return level switch
|
||||||
{
|
{
|
||||||
AvaLogLevel.Verbose => RyuLogger.Debug,
|
AvaLogLevel.Verbose => RyuLogger.Debug,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ using Avalonia.Media.Imaging;
|
|||||||
using Avalonia.Styling;
|
using Avalonia.Styling;
|
||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using Gommon;
|
||||||
using Ryujinx.Ava.Common;
|
using Ryujinx.Ava.Common;
|
||||||
using Ryujinx.Ava.Common.Locale;
|
using Ryujinx.Ava.Common.Locale;
|
||||||
using Ryujinx.Ava.Utilities.Configuration;
|
using Ryujinx.Ava.Utilities.Configuration;
|
||||||
@@ -24,30 +25,35 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
Version = RyujinxApp.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;
|
RyujinxApp.ThemeChanged += Ryujinx_ThemeChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ThemeManager_ThemeChanged()
|
private void Ryujinx_ThemeChanged()
|
||||||
{
|
{
|
||||||
Dispatcher.UIThread.Post(() => UpdateLogoTheme(ConfigurationState.Instance.UI.BaseStyle.Value));
|
Dispatcher.UIThread.Post(() => UpdateLogoTheme(ConfigurationState.Instance.UI.BaseStyle.Value));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private const string LogoPathFormat = "resm:Ryujinx.Assets.UIImages.Logo_{0}_{1}.png?assembly=Ryujinx";
|
||||||
|
|
||||||
private void UpdateLogoTheme(string theme)
|
private void UpdateLogoTheme(string theme)
|
||||||
{
|
{
|
||||||
bool isDarkTheme = theme == "Dark" || (theme == "Auto" && RyujinxApp.DetectSystemTheme() == ThemeVariant.Dark);
|
bool isDarkTheme = theme == "Dark" || (theme == "Auto" && RyujinxApp.DetectSystemTheme() == ThemeVariant.Dark);
|
||||||
|
|
||||||
|
string themeName = isDarkTheme ? "Dark" : "Light";
|
||||||
|
|
||||||
string basePath = "resm:Ryujinx.Assets.UIImages.";
|
GithubLogo = LoadBitmap(LogoPathFormat.Format("GitHub", themeName));
|
||||||
string themeSuffix = isDarkTheme ? "Dark.png" : "Light.png";
|
DiscordLogo = LoadBitmap(LogoPathFormat.Format("Discord", themeName));
|
||||||
|
|
||||||
GithubLogo = LoadBitmap($"{basePath}Logo_GitHub_{themeSuffix}?assembly=Ryujinx");
|
|
||||||
DiscordLogo = LoadBitmap($"{basePath}Logo_Discord_{themeSuffix}?assembly=Ryujinx");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Bitmap LoadBitmap(string uri) => new(Avalonia.Platform.AssetLoader.Open(new Uri(uri)));
|
private static Bitmap LoadBitmap(string uri) => new(Avalonia.Platform.AssetLoader.Open(new Uri(uri)));
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
ThemeManager.ThemeChanged -= ThemeManager_ThemeChanged;
|
RyujinxApp.ThemeChanged -= Ryujinx_ThemeChanged;
|
||||||
|
|
||||||
|
GithubLogo.Dispose();
|
||||||
|
DiscordLogo.Dispose();
|
||||||
|
|
||||||
GC.SuppressFinalize(this);
|
GC.SuppressFinalize(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -264,7 +264,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
Logger.Error?.Print(LogClass.Application, $"Couldn't get valid amiibo data: {exception}");
|
Logger.Error?.Print(LogClass.Application, $"Couldn't get valid amiibo data: {exception}");
|
||||||
|
|
||||||
// Neither local or remote files are valid JSON, close window.
|
// Neither local or remote files are valid JSON, close window.
|
||||||
ShowInfoDialog();
|
await ShowInfoDialog();
|
||||||
Close();
|
Close();
|
||||||
}
|
}
|
||||||
else if (!remoteIsValid)
|
else if (!remoteIsValid)
|
||||||
@@ -273,7 +273,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
// Only the local file is valid, the local one should be used
|
// Only the local file is valid, the local one should be used
|
||||||
// but the user should be warned.
|
// but the user should be warned.
|
||||||
ShowInfoDialog();
|
await ShowInfoDialog();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -525,7 +525,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
AmiiboImage = bitmap;
|
AmiiboImage = bitmap;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async void ShowInfoDialog()
|
private static async Task ShowInfoDialog()
|
||||||
{
|
{
|
||||||
await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogAmiiboApiTitle],
|
await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogAmiiboApiTitle],
|
||||||
LocaleManager.Instance[LocaleKeys.DialogAmiiboApiConnectErrorMessage],
|
LocaleManager.Instance[LocaleKeys.DialogAmiiboApiConnectErrorMessage],
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using Gommon;
|
using Gommon;
|
||||||
using Ryujinx.Ava.Common.Locale;
|
using Ryujinx.Ava.Common.Locale;
|
||||||
using Ryujinx.Ava.Utilities.AppLibrary;
|
using Ryujinx.Ava.Utilities.AppLibrary;
|
||||||
|
using Ryujinx.Ava.Utilities.PlayReport;
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.ViewModels
|
namespace Ryujinx.Ava.UI.ViewModels
|
||||||
{
|
{
|
||||||
@@ -10,6 +11,11 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
public ApplicationDataViewModel(ApplicationData appData) => AppData = appData;
|
public ApplicationDataViewModel(ApplicationData appData) => AppData = appData;
|
||||||
|
|
||||||
|
public string DynamicRichPresenceDescription =>
|
||||||
|
AppData.HasDynamicRichPresenceSupport
|
||||||
|
? AppData.RichPresenceSpec.Value.Description
|
||||||
|
: GameSpec.DefaultDescription;
|
||||||
|
|
||||||
public string FormattedVersion => LocaleManager.Instance[LocaleKeys.GameListHeaderVersion].Format(AppData.Version);
|
public string FormattedVersion => LocaleManager.Instance[LocaleKeys.GameListHeaderVersion].Format(AppData.Version);
|
||||||
public string FormattedDeveloper => LocaleManager.Instance[LocaleKeys.GameListHeaderDeveloper].Format(AppData.Developer);
|
public string FormattedDeveloper => LocaleManager.Instance[LocaleKeys.GameListHeaderDeveloper].Format(AppData.Developer);
|
||||||
public string FormattedFileExtension => LocaleManager.Instance[LocaleKeys.GameListHeaderFileExtension].Format(AppData.FileExtension);
|
public string FormattedFileExtension => LocaleManager.Instance[LocaleKeys.GameListHeaderFileExtension].Format(AppData.FileExtension);
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ using Avalonia.Media.Imaging;
|
|||||||
using Avalonia.Platform.Storage;
|
using Avalonia.Platform.Storage;
|
||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using CommunityToolkit.Mvvm.Input;
|
||||||
using DynamicData;
|
using DynamicData;
|
||||||
using DynamicData.Binding;
|
using DynamicData.Binding;
|
||||||
using FluentAvalonia.UI.Controls;
|
using FluentAvalonia.UI.Controls;
|
||||||
@@ -104,6 +105,13 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
[ObservableProperty] private bool _isSubMenuOpen;
|
[ObservableProperty] private bool _isSubMenuOpen;
|
||||||
[ObservableProperty] private ApplicationContextMenu _listAppContextMenu;
|
[ObservableProperty] private ApplicationContextMenu _listAppContextMenu;
|
||||||
[ObservableProperty] private ApplicationContextMenu _gridAppContextMenu;
|
[ObservableProperty] private ApplicationContextMenu _gridAppContextMenu;
|
||||||
|
[ObservableProperty] private bool _updateAvailable;
|
||||||
|
|
||||||
|
public static AsyncRelayCommand UpdateCommand { get; } = Commands.Create(async () =>
|
||||||
|
{
|
||||||
|
if (Updater.CanUpdate(true))
|
||||||
|
await Updater.BeginUpdateAsync(true);
|
||||||
|
});
|
||||||
|
|
||||||
private bool _showLoadProgress;
|
private bool _showLoadProgress;
|
||||||
private bool _isGameRunning;
|
private bool _isGameRunning;
|
||||||
@@ -1076,7 +1084,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
_rendererWaitEvent.WaitOne();
|
_rendererWaitEvent.WaitOne();
|
||||||
|
|
||||||
AppHost?.Start();
|
AppHost?.Start();
|
||||||
|
|
||||||
AppHost?.DisposeContext();
|
AppHost?.DisposeContext();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1147,10 +1155,10 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
List<string> dirs = result.Select(it => it.Path.LocalPath).ToList();
|
List<string> dirs = result.Select(it => it.Path.LocalPath).ToList();
|
||||||
int numAdded = onDirsSelected(dirs, out int numRemoved);
|
int numAdded = onDirsSelected(dirs, out int numRemoved);
|
||||||
|
|
||||||
string msg = String.Join("\r\n", new string[] {
|
string msg = string.Join("\n",
|
||||||
string.Format(LocaleManager.Instance[localeMessageRemovedKey], numRemoved),
|
string.Format(LocaleManager.Instance[localeMessageRemovedKey], numRemoved),
|
||||||
string.Format(LocaleManager.Instance[localeMessageAddedKey], numAdded)
|
string.Format(LocaleManager.Instance[localeMessageAddedKey], numAdded)
|
||||||
});
|
);
|
||||||
|
|
||||||
await Dispatcher.UIThread.InvokeAsync(async () =>
|
await Dispatcher.UIThread.InvokeAsync(async () =>
|
||||||
{
|
{
|
||||||
@@ -1339,6 +1347,25 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
OpenHelper.OpenFolder(AppDataManager.BaseDirPath);
|
OpenHelper.OpenFolder(AppDataManager.BaseDirPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void OpenScreenshotsFolder()
|
||||||
|
{
|
||||||
|
string screenshotsDir = Path.Combine(AppDataManager.BaseDirPath, "screenshots");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!Directory.Exists(screenshotsDir))
|
||||||
|
Directory.CreateDirectory(screenshotsDir);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.Error?.Print(LogClass.Application, $"Failed to create directory at path {screenshotsDir}. Error : {ex.GetType().Name}", "Screenshot");
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
OpenHelper.OpenFolder(screenshotsDir);
|
||||||
|
}
|
||||||
|
|
||||||
public void OpenLogsFolder()
|
public void OpenLogsFolder()
|
||||||
{
|
{
|
||||||
string logPath = AppDataManager.GetOrCreateLogsDir();
|
string logPath = AppDataManager.GetOrCreateLogsDir();
|
||||||
@@ -1523,34 +1550,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void InitializeUserConfig(ApplicationData application)
|
|
||||||
{
|
|
||||||
// Code where conditions will be met before loading the user configuration
|
|
||||||
BackendThreading backendThreadingValue = ConfigurationState.Instance.Graphics.BackendThreading.Value;
|
|
||||||
|
|
||||||
// If a configuration is found in the "/games/xxxxxxxxxxxxxx" folder, the program will load the user setting.
|
|
||||||
string idGame = application.IdBaseString;
|
|
||||||
if (ConfigurationFileFormat.TryLoad(Program.GetDirGameUserConfig(idGame), out ConfigurationFileFormat configurationFileFormat))
|
|
||||||
{
|
|
||||||
// Loads the user configuration, having previously changed the global configuration to the user configuration
|
|
||||||
ConfigurationState.Instance.Load(configurationFileFormat, Program.GetDirGameUserConfig(idGame, true, true), idGame);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Code where conditions will be executed after loading user configuration
|
|
||||||
if (ConfigurationState.Instance.Graphics.BackendThreading != backendThreadingValue)
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
* The function to restart the emulator together with the selected game
|
|
||||||
Task.Run(async () => await Rebooter.RebootAppWithGame(application.Path));
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task LoadApplication(ApplicationData application, bool startFullscreen = false, BlitStruct<ApplicationControlProperty>? customNacpData = null)
|
public async Task LoadApplication(ApplicationData application, bool startFullscreen = false, BlitStruct<ApplicationControlProperty>? customNacpData = null)
|
||||||
{
|
{
|
||||||
|
|
||||||
InitializeUserConfig(application);
|
|
||||||
|
|
||||||
if (AppHost != null)
|
if (AppHost != null)
|
||||||
{
|
{
|
||||||
await ContentDialogHelper.CreateInfoDialog(
|
await ContentDialogHelper.CreateInfoDialog(
|
||||||
@@ -1566,7 +1567,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
#if RELEASE
|
#if RELEASE
|
||||||
await PerformanceCheck();
|
await PerformanceCheck();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
Logger.RestartTime();
|
Logger.RestartTime();
|
||||||
|
|
||||||
SelectedIcon ??= ApplicationLibrary.GetApplicationIcon(application.Path, ConfigurationState.Instance.System.Language, application.Id);
|
SelectedIcon ??= ApplicationLibrary.GetApplicationIcon(application.Path, ConfigurationState.Instance.System.Language, application.Id);
|
||||||
@@ -1611,7 +1612,6 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
Thread gameThread = new(InitializeGame) { Name = "GUI.WindowThread" };
|
Thread gameThread = new(InitializeGame) { Name = "GUI.WindowThread" };
|
||||||
gameThread.Start();
|
gameThread.Start();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SwitchToRenderer(bool startFullscreen) =>
|
public void SwitchToRenderer(bool startFullscreen) =>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ using System.Collections.ObjectModel;
|
|||||||
|
|
||||||
namespace Ryujinx.Ava.UI.ViewModels
|
namespace Ryujinx.Ava.UI.ViewModels
|
||||||
{
|
{
|
||||||
public partial class UserSelectorDialogViewModel : BaseModel
|
public partial class ProfileSelectorDialogViewModel : BaseModel
|
||||||
{
|
{
|
||||||
|
|
||||||
[ObservableProperty] private UserId _selectedUserId;
|
[ObservableProperty] private UserId _selectedUserId;
|
||||||
@@ -1,10 +1,8 @@
|
|||||||
using Avalonia.Collections;
|
using Avalonia.Collections;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Media.Imaging;
|
|
||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
using Humanizer;
|
|
||||||
using LibHac.Tools.FsSystem;
|
using LibHac.Tools.FsSystem;
|
||||||
using Ryujinx.Audio.Backends.OpenAL;
|
using Ryujinx.Audio.Backends.OpenAL;
|
||||||
using Ryujinx.Audio.Backends.SDL2;
|
using Ryujinx.Audio.Backends.SDL2;
|
||||||
@@ -15,6 +13,7 @@ using Ryujinx.Ava.UI.Models.Input;
|
|||||||
using Ryujinx.Ava.UI.Windows;
|
using Ryujinx.Ava.UI.Windows;
|
||||||
using Ryujinx.Ava.Utilities.Configuration;
|
using Ryujinx.Ava.Utilities.Configuration;
|
||||||
using Ryujinx.Ava.Utilities.Configuration.System;
|
using Ryujinx.Ava.Utilities.Configuration.System;
|
||||||
|
using Ryujinx.Ava.Utilities.Configuration.UI;
|
||||||
using Ryujinx.Common.Configuration;
|
using Ryujinx.Common.Configuration;
|
||||||
using Ryujinx.Common.Configuration.Multiplayer;
|
using Ryujinx.Common.Configuration.Multiplayer;
|
||||||
using Ryujinx.Common.GraphicsDriver;
|
using Ryujinx.Common.GraphicsDriver;
|
||||||
@@ -28,7 +27,6 @@ using Ryujinx.HLE.HOS.Services.Time.TimeZone;
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net.NetworkInformation;
|
using System.Net.NetworkInformation;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@@ -51,8 +49,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
private int _graphicsBackendMultithreadingIndex;
|
private int _graphicsBackendMultithreadingIndex;
|
||||||
private float _volume;
|
private float _volume;
|
||||||
[ObservableProperty] private bool _isVulkanAvailable = true;
|
[ObservableProperty] private bool _isVulkanAvailable = true;
|
||||||
[ObservableProperty] private bool _gameDirectoryChanged;
|
[ObservableProperty] private bool _gameListNeedsRefresh;
|
||||||
[ObservableProperty] private bool _autoloadDirectoryChanged;
|
|
||||||
private readonly List<string> _gpuIds = [];
|
private readonly List<string> _gpuIds = [];
|
||||||
private int _graphicsBackendIndex;
|
private int _graphicsBackendIndex;
|
||||||
private int _scalingFilter;
|
private int _scalingFilter;
|
||||||
@@ -71,46 +68,6 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
public SettingsHacksViewModel DirtyHacks { get; }
|
public SettingsHacksViewModel DirtyHacks { get; }
|
||||||
|
|
||||||
private Bitmap _gameIcon;
|
|
||||||
private string _gameTitle;
|
|
||||||
private string _gameId;
|
|
||||||
public Bitmap GameIcon
|
|
||||||
{
|
|
||||||
get => _gameIcon;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (_gameIcon != value)
|
|
||||||
{
|
|
||||||
_gameIcon = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public string GameTitle
|
|
||||||
{
|
|
||||||
get => _gameTitle;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (_gameTitle != value)
|
|
||||||
{
|
|
||||||
_gameTitle = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public string GameId
|
|
||||||
{
|
|
||||||
get => _gameId;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (_gameId != value)
|
|
||||||
{
|
|
||||||
_gameId = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public int ResolutionScale
|
public int ResolutionScale
|
||||||
{
|
{
|
||||||
get => _resolutionScale;
|
get => _resolutionScale;
|
||||||
@@ -164,9 +121,14 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
public bool RememberWindowState { get; set; }
|
public bool RememberWindowState { get; set; }
|
||||||
public bool ShowTitleBar { get; set; }
|
public bool ShowTitleBar { get; set; }
|
||||||
public int HideCursor { get; set; }
|
public int HideCursor { get; set; }
|
||||||
|
public int UpdateCheckerType { get; set; }
|
||||||
public bool EnableDockedMode { get; set; }
|
public bool EnableDockedMode { get; set; }
|
||||||
public bool EnableKeyboard { get; set; }
|
public bool EnableKeyboard { get; set; }
|
||||||
public bool EnableMouse { get; set; }
|
public bool EnableMouse { get; set; }
|
||||||
|
public bool DisableInputWhenOutOfFocus { get; set; }
|
||||||
|
|
||||||
|
public int FocusLostActionType { get; set; }
|
||||||
|
|
||||||
public VSyncMode VSyncMode
|
public VSyncMode VSyncMode
|
||||||
{
|
{
|
||||||
get => _vSyncMode;
|
get => _vSyncMode;
|
||||||
@@ -247,6 +209,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
public bool EnableTrace { get; set; }
|
public bool EnableTrace { get; set; }
|
||||||
public bool EnableGuest { get; set; }
|
public bool EnableGuest { get; set; }
|
||||||
public bool EnableFsAccessLog { get; set; }
|
public bool EnableFsAccessLog { get; set; }
|
||||||
|
public bool EnableAvaloniaLog { get; set; }
|
||||||
public bool EnableDebug { get; set; }
|
public bool EnableDebug { get; set; }
|
||||||
public bool IsOpenAlEnabled { get; set; }
|
public bool IsOpenAlEnabled { get; set; }
|
||||||
public bool IsSoundIoEnabled { get; set; }
|
public bool IsSoundIoEnabled { get; set; }
|
||||||
@@ -372,7 +335,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
public bool IsInvalidLdnPassphraseVisible { get; set; }
|
public bool IsInvalidLdnPassphraseVisible { get; set; }
|
||||||
|
|
||||||
public SettingsViewModel(VirtualFileSystem virtualFileSystem, ContentManager contentManager) : this(false)
|
public SettingsViewModel(VirtualFileSystem virtualFileSystem, ContentManager contentManager) : this()
|
||||||
{
|
{
|
||||||
_virtualFileSystem = virtualFileSystem;
|
_virtualFileSystem = virtualFileSystem;
|
||||||
_contentManager = contentManager;
|
_contentManager = contentManager;
|
||||||
@@ -385,48 +348,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public SettingsViewModel(VirtualFileSystem virtualFileSystem,
|
public SettingsViewModel()
|
||||||
ContentManager contentManager,
|
|
||||||
string gamePath,
|
|
||||||
string gameName,
|
|
||||||
string gameId,
|
|
||||||
byte[] gameIconData,
|
|
||||||
bool enableToLoadCustomConfig) : this(enableToLoadCustomConfig)
|
|
||||||
{
|
|
||||||
_virtualFileSystem = virtualFileSystem;
|
|
||||||
_contentManager = contentManager;
|
|
||||||
|
|
||||||
if (gameIconData != null && gameIconData.Length > 0)
|
|
||||||
{
|
|
||||||
using (var ms = new MemoryStream(gameIconData))
|
|
||||||
{
|
|
||||||
GameIcon = new Bitmap(ms);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
GameTitle = gameName;
|
|
||||||
GameId = gameId;
|
|
||||||
|
|
||||||
if (enableToLoadCustomConfig) // During the game. If there is no user config, then load the global config window
|
|
||||||
{
|
|
||||||
string gameDir = Program.GetDirGameUserConfig(gameId, false, true);
|
|
||||||
if (ConfigurationFileFormat.TryLoad(gameDir, out ConfigurationFileFormat configurationFileFormat))
|
|
||||||
{
|
|
||||||
ConfigurationState.Instance.Load(configurationFileFormat, gameDir, gameId);
|
|
||||||
}
|
|
||||||
|
|
||||||
LoadCurrentConfiguration(); // Needed to load custom configuration
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Program.PreviewerDetached)
|
|
||||||
{
|
|
||||||
Task.Run(LoadTimeZones);
|
|
||||||
|
|
||||||
DirtyHacks = new SettingsHacksViewModel(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public SettingsViewModel(bool noLoadGlobalConfig = false)
|
|
||||||
{
|
{
|
||||||
GameDirectories = [];
|
GameDirectories = [];
|
||||||
AutoloadDirectories = [];
|
AutoloadDirectories = [];
|
||||||
@@ -441,11 +363,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
if (Program.PreviewerDetached)
|
if (Program.PreviewerDetached)
|
||||||
{
|
{
|
||||||
Task.Run(LoadAvailableGpus);
|
Task.Run(LoadAvailableGpus);
|
||||||
|
LoadCurrentConfiguration();
|
||||||
if (!noLoadGlobalConfig)// Default is false, but loading custom config avoids double call
|
|
||||||
{
|
|
||||||
LoadCurrentConfiguration();
|
|
||||||
}
|
|
||||||
|
|
||||||
DirtyHacks = new SettingsHacksViewModel(this);
|
DirtyHacks = new SettingsHacksViewModel(this);
|
||||||
}
|
}
|
||||||
@@ -556,37 +474,35 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
{
|
{
|
||||||
ConfigurationState config = ConfigurationState.Instance;
|
ConfigurationState config = ConfigurationState.Instance;
|
||||||
|
|
||||||
//It is necessary that the data is used from the global configuration file
|
// User Interface
|
||||||
if (string.IsNullOrEmpty(GameId))
|
EnableDiscordIntegration = config.EnableDiscordIntegration;
|
||||||
|
CheckUpdatesOnStart = config.CheckUpdatesOnStart;
|
||||||
|
ShowConfirmExit = config.ShowConfirmExit;
|
||||||
|
RememberWindowState = config.RememberWindowState;
|
||||||
|
ShowTitleBar = config.ShowTitleBar;
|
||||||
|
HideCursor = (int)config.HideCursor.Value;
|
||||||
|
UpdateCheckerType = (int)config.UpdateCheckerType.Value;
|
||||||
|
FocusLostActionType = (int)config.FocusLostActionType.Value;
|
||||||
|
|
||||||
|
GameDirectories.Clear();
|
||||||
|
GameDirectories.AddRange(config.UI.GameDirs.Value);
|
||||||
|
|
||||||
|
AutoloadDirectories.Clear();
|
||||||
|
AutoloadDirectories.AddRange(config.UI.AutoloadDirs.Value);
|
||||||
|
|
||||||
|
BaseStyleIndex = config.UI.BaseStyle.Value switch
|
||||||
{
|
{
|
||||||
// User Interface
|
"Auto" => 0,
|
||||||
EnableDiscordIntegration = config.EnableDiscordIntegration;
|
"Light" => 1,
|
||||||
CheckUpdatesOnStart = config.CheckUpdatesOnStart;
|
"Dark" => 2,
|
||||||
ShowConfirmExit = config.ShowConfirmExit;
|
_ => 0
|
||||||
RememberWindowState = config.RememberWindowState;
|
};
|
||||||
ShowTitleBar = config.ShowTitleBar;
|
|
||||||
HideCursor = (int)config.HideCursor.Value;
|
|
||||||
|
|
||||||
GameDirectories.Clear();
|
|
||||||
GameDirectories.AddRange(config.UI.GameDirs.Value);
|
|
||||||
|
|
||||||
AutoloadDirectories.Clear();
|
|
||||||
AutoloadDirectories.AddRange(config.UI.AutoloadDirs.Value);
|
|
||||||
|
|
||||||
BaseStyleIndex = config.UI.BaseStyle.Value switch
|
|
||||||
{
|
|
||||||
"Auto" => 0,
|
|
||||||
"Light" => 1,
|
|
||||||
"Dark" => 2,
|
|
||||||
_ => 0
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Input
|
// Input
|
||||||
EnableDockedMode = config.System.EnableDockedMode;
|
EnableDockedMode = config.System.EnableDockedMode;
|
||||||
EnableKeyboard = config.Hid.EnableKeyboard;
|
EnableKeyboard = config.Hid.EnableKeyboard;
|
||||||
EnableMouse = config.Hid.EnableMouse;
|
EnableMouse = config.Hid.EnableMouse;
|
||||||
|
DisableInputWhenOutOfFocus = config.Hid.DisableInputWhenOutOfFocus;
|
||||||
|
|
||||||
// Keyboard Hotkeys
|
// Keyboard Hotkeys
|
||||||
KeyboardHotkey = new HotkeyConfig(config.Hid.Hotkeys.Value);
|
KeyboardHotkey = new HotkeyConfig(config.Hid.Hotkeys.Value);
|
||||||
@@ -601,6 +517,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
DateTime currentDateTime = currentHostDateTime.Add(systemDateTimeOffset);
|
DateTime currentDateTime = currentHostDateTime.Add(systemDateTimeOffset);
|
||||||
CurrentDate = currentDateTime.Date;
|
CurrentDate = currentDateTime.Date;
|
||||||
CurrentTime = currentDateTime.TimeOfDay;
|
CurrentTime = currentDateTime.TimeOfDay;
|
||||||
|
|
||||||
MatchSystemTime = config.System.MatchSystemTime;
|
MatchSystemTime = config.System.MatchSystemTime;
|
||||||
|
|
||||||
EnableCustomVSyncInterval = config.Graphics.EnableCustomVSyncInterval;
|
EnableCustomVSyncInterval = config.Graphics.EnableCustomVSyncInterval;
|
||||||
@@ -609,7 +526,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
EnableFsIntegrityChecks = config.System.EnableFsIntegrityChecks;
|
EnableFsIntegrityChecks = config.System.EnableFsIntegrityChecks;
|
||||||
DramSize = config.System.DramSize;
|
DramSize = config.System.DramSize;
|
||||||
IgnoreMissingServices = config.System.IgnoreMissingServices;
|
IgnoreMissingServices = config.System.IgnoreMissingServices;
|
||||||
IgnoreApplet = config.System.IgnoreApplet;
|
IgnoreApplet = config.System.IgnoreControllerApplet;
|
||||||
|
|
||||||
// CPU
|
// CPU
|
||||||
EnablePptc = config.System.EnablePtc;
|
EnablePptc = config.System.EnablePtc;
|
||||||
@@ -652,6 +569,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
EnableGuest = config.Logger.EnableGuest;
|
EnableGuest = config.Logger.EnableGuest;
|
||||||
EnableDebug = config.Logger.EnableDebug;
|
EnableDebug = config.Logger.EnableDebug;
|
||||||
EnableFsAccessLog = config.Logger.EnableFsAccessLog;
|
EnableFsAccessLog = config.Logger.EnableFsAccessLog;
|
||||||
|
EnableAvaloniaLog = config.Logger.EnableAvaloniaLog;
|
||||||
FsGlobalAccessLogMode = config.System.FsGlobalAccessLogMode;
|
FsGlobalAccessLogMode = config.System.FsGlobalAccessLogMode;
|
||||||
OpenglDebugLevel = (int)config.Logger.GraphicsDebugLevel.Value;
|
OpenglDebugLevel = (int)config.Logger.GraphicsDebugLevel.Value;
|
||||||
|
|
||||||
@@ -660,53 +578,47 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
LdnPassphrase = config.Multiplayer.LdnPassphrase;
|
LdnPassphrase = config.Multiplayer.LdnPassphrase;
|
||||||
LdnServer = config.Multiplayer.LdnServer;
|
LdnServer = config.Multiplayer.LdnServer;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SaveSettings()
|
public void SaveSettings()
|
||||||
{
|
{
|
||||||
ConfigurationState config = ConfigurationState.Instance;
|
ConfigurationState config = ConfigurationState.Instance;
|
||||||
bool userConfigFile = string.IsNullOrEmpty(GameId);
|
|
||||||
|
|
||||||
if (userConfigFile)
|
// User Interface
|
||||||
|
config.EnableDiscordIntegration.Value = EnableDiscordIntegration;
|
||||||
|
config.CheckUpdatesOnStart.Value = CheckUpdatesOnStart;
|
||||||
|
config.ShowConfirmExit.Value = ShowConfirmExit;
|
||||||
|
config.RememberWindowState.Value = RememberWindowState;
|
||||||
|
config.ShowTitleBar.Value = ShowTitleBar;
|
||||||
|
config.HideCursor.Value = (HideCursorMode)HideCursor;
|
||||||
|
config.UpdateCheckerType.Value = (UpdaterType)UpdateCheckerType;
|
||||||
|
config.FocusLostActionType.Value = (FocusLostType)FocusLostActionType;
|
||||||
|
config.UI.GameDirs.Value = [..GameDirectories];
|
||||||
|
config.UI.AutoloadDirs.Value = [..AutoloadDirectories];
|
||||||
|
|
||||||
|
config.UI.BaseStyle.Value = BaseStyleIndex switch
|
||||||
{
|
{
|
||||||
// User Interface
|
0 => "Auto",
|
||||||
config.EnableDiscordIntegration.Value = EnableDiscordIntegration;
|
1 => "Light",
|
||||||
config.CheckUpdatesOnStart.Value = CheckUpdatesOnStart;
|
2 => "Dark",
|
||||||
config.ShowConfirmExit.Value = ShowConfirmExit;
|
_ => "Auto"
|
||||||
config.RememberWindowState.Value = RememberWindowState;
|
};
|
||||||
config.ShowTitleBar.Value = ShowTitleBar;
|
|
||||||
config.HideCursor.Value = (HideCursorMode)HideCursor;
|
|
||||||
|
|
||||||
if (GameDirectoryChanged)
|
|
||||||
{
|
|
||||||
config.UI.GameDirs.Value = [.. GameDirectories];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (AutoloadDirectoryChanged)
|
|
||||||
{
|
|
||||||
config.UI.AutoloadDirs.Value = [.. AutoloadDirectories];
|
|
||||||
}
|
|
||||||
|
|
||||||
config.UI.BaseStyle.Value = BaseStyleIndex switch
|
|
||||||
{
|
|
||||||
0 => "Auto",
|
|
||||||
1 => "Light",
|
|
||||||
2 => "Dark",
|
|
||||||
_ => "Auto"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Input
|
// Input
|
||||||
config.System.EnableDockedMode.Value = EnableDockedMode;
|
config.System.EnableDockedMode.Value = EnableDockedMode;
|
||||||
config.Hid.EnableKeyboard.Value = EnableKeyboard;
|
config.Hid.EnableKeyboard.Value = EnableKeyboard;
|
||||||
config.Hid.EnableMouse.Value = EnableMouse;
|
config.Hid.EnableMouse.Value = EnableMouse;
|
||||||
|
config.Hid.DisableInputWhenOutOfFocus.Value = DisableInputWhenOutOfFocus;
|
||||||
|
|
||||||
// Keyboard Hotkeys
|
// Keyboard Hotkeys
|
||||||
config.Hid.Hotkeys.Value = KeyboardHotkey.GetConfig();
|
config.Hid.Hotkeys.Value = KeyboardHotkey.GetConfig();
|
||||||
|
|
||||||
// System
|
// System
|
||||||
config.System.Region.Value = (Region)Region;
|
config.System.Region.Value = (Region)Region;
|
||||||
|
|
||||||
|
if (config.System.Language.Value != (Language)Language)
|
||||||
|
GameListNeedsRefresh = true;
|
||||||
|
|
||||||
config.System.Language.Value = (Language)Language;
|
config.System.Language.Value = (Language)Language;
|
||||||
|
|
||||||
if (_validTzRegions.Contains(TimeZone))
|
if (_validTzRegions.Contains(TimeZone))
|
||||||
{
|
{
|
||||||
config.System.TimeZone.Value = TimeZone;
|
config.System.TimeZone.Value = TimeZone;
|
||||||
@@ -717,7 +629,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
config.System.EnableFsIntegrityChecks.Value = EnableFsIntegrityChecks;
|
config.System.EnableFsIntegrityChecks.Value = EnableFsIntegrityChecks;
|
||||||
config.System.DramSize.Value = DramSize;
|
config.System.DramSize.Value = DramSize;
|
||||||
config.System.IgnoreMissingServices.Value = IgnoreMissingServices;
|
config.System.IgnoreMissingServices.Value = IgnoreMissingServices;
|
||||||
config.System.IgnoreApplet.Value = IgnoreApplet;
|
config.System.IgnoreControllerApplet.Value = IgnoreApplet;
|
||||||
|
|
||||||
// CPU
|
// CPU
|
||||||
config.System.EnablePtc.Value = EnablePptc;
|
config.System.EnablePtc.Value = EnablePptc;
|
||||||
@@ -775,6 +687,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
config.Logger.EnableGuest.Value = EnableGuest;
|
config.Logger.EnableGuest.Value = EnableGuest;
|
||||||
config.Logger.EnableDebug.Value = EnableDebug;
|
config.Logger.EnableDebug.Value = EnableDebug;
|
||||||
config.Logger.EnableFsAccessLog.Value = EnableFsAccessLog;
|
config.Logger.EnableFsAccessLog.Value = EnableFsAccessLog;
|
||||||
|
config.Logger.EnableAvaloniaLog.Value = EnableAvaloniaLog;
|
||||||
config.System.FsGlobalAccessLogMode.Value = FsGlobalAccessLogMode;
|
config.System.FsGlobalAccessLogMode.Value = FsGlobalAccessLogMode;
|
||||||
config.Logger.GraphicsDebugLevel.Value = (GraphicsDebugLevel)OpenglDebugLevel;
|
config.Logger.GraphicsDebugLevel.Value = (GraphicsDebugLevel)OpenglDebugLevel;
|
||||||
|
|
||||||
@@ -789,26 +702,19 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
config.Hacks.EnableShaderTranslationDelay.Value = DirtyHacks.ShaderTranslationDelayEnabled;
|
config.Hacks.EnableShaderTranslationDelay.Value = DirtyHacks.ShaderTranslationDelayEnabled;
|
||||||
config.Hacks.ShaderTranslationDelay.Value = DirtyHacks.ShaderTranslationDelay;
|
config.Hacks.ShaderTranslationDelay.Value = DirtyHacks.ShaderTranslationDelay;
|
||||||
|
|
||||||
|
config.ToFileFormat().SaveConfig(Program.ConfigurationPath);
|
||||||
config.ToFileFormat().SaveConfig(Program.ConfigurationPath);
|
|
||||||
|
|
||||||
|
|
||||||
MainWindow.UpdateGraphicsConfig();
|
MainWindow.UpdateGraphicsConfig();
|
||||||
RyujinxApp.MainWindow.ViewModel.VSyncModeSettingChanged();
|
RyujinxApp.MainWindow.ViewModel.VSyncModeSettingChanged();
|
||||||
|
|
||||||
SaveSettingsEvent?.Invoke();
|
SaveSettingsEvent?.Invoke();
|
||||||
|
|
||||||
GameDirectoryChanged = false;
|
GameListNeedsRefresh = false;
|
||||||
AutoloadDirectoryChanged = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void RevertIfNotSaved()
|
private static void RevertIfNotSaved()
|
||||||
{
|
{
|
||||||
// maybe this is an unnecessary check(all options need to be tested)
|
Program.ReloadConfig();
|
||||||
if (string.IsNullOrEmpty(Program.GlobalConfigurationPath))
|
|
||||||
{
|
|
||||||
Program.ReloadConfig();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ApplyButton()
|
public void ApplyButton()
|
||||||
@@ -816,26 +722,6 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
SaveSettings();
|
SaveSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DeleteConfigGame()
|
|
||||||
{
|
|
||||||
string gameDir = Program.GetDirGameUserConfig(GameId,false,false);
|
|
||||||
|
|
||||||
if (File.Exists(gameDir))
|
|
||||||
{
|
|
||||||
File.Delete(gameDir);
|
|
||||||
}
|
|
||||||
|
|
||||||
RevertIfNotSaved();
|
|
||||||
CloseWindow?.Invoke();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SaveUserConfig()
|
|
||||||
{
|
|
||||||
SaveSettings();
|
|
||||||
RevertIfNotSaved(); // Revert global configuration after saving user configuration
|
|
||||||
CloseWindow?.Invoke();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OkButton()
|
public void OkButton()
|
||||||
{
|
{
|
||||||
SaveSettings();
|
SaveSettings();
|
||||||
|
|||||||
@@ -66,6 +66,10 @@
|
|||||||
Command="{Binding OpenRyujinxFolder}"
|
Command="{Binding OpenRyujinxFolder}"
|
||||||
Header="{ext:Locale MenuBarFileOpenEmuFolder}"
|
Header="{ext:Locale MenuBarFileOpenEmuFolder}"
|
||||||
ToolTip.Tip="{ext:Locale OpenRyujinxFolderTooltip}" />
|
ToolTip.Tip="{ext:Locale OpenRyujinxFolderTooltip}" />
|
||||||
|
<MenuItem
|
||||||
|
Command="{Binding OpenScreenshotsFolder}"
|
||||||
|
Header="{ext:Locale MenuBarFileOpenScreenshotsFolder}"
|
||||||
|
ToolTip.Tip="{ext:Locale OpenScreenshotFolderTooltip}"/>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
Command="{Binding OpenLogsFolder}"
|
Command="{Binding OpenLogsFolder}"
|
||||||
Header="{ext:Locale MenuBarFileOpenLogsFolder}"
|
Header="{ext:Locale MenuBarFileOpenLogsFolder}"
|
||||||
|
|||||||
@@ -51,12 +51,8 @@ namespace Ryujinx.Ava.UI.Views.Main
|
|||||||
XciTrimmerMenuItem.Command = Commands.Create(XCITrimmerWindow.Show);
|
XciTrimmerMenuItem.Command = Commands.Create(XCITrimmerWindow.Show);
|
||||||
AboutWindowMenuItem.Command = Commands.Create(AboutWindow.Show);
|
AboutWindowMenuItem.Command = Commands.Create(AboutWindow.Show);
|
||||||
CompatibilityListMenuItem.Command = Commands.Create(() => CompatibilityList.Show());
|
CompatibilityListMenuItem.Command = Commands.Create(() => CompatibilityList.Show());
|
||||||
|
|
||||||
UpdateMenuItem.Command = Commands.Create(async () =>
|
UpdateMenuItem.Command = MainWindowViewModel.UpdateCommand;
|
||||||
{
|
|
||||||
if (Updater.CanUpdate(true))
|
|
||||||
await Updater.BeginUpdateAsync(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
FaqMenuItem.Command =
|
FaqMenuItem.Command =
|
||||||
SetupGuideMenuItem.Command =
|
SetupGuideMenuItem.Command =
|
||||||
@@ -134,26 +130,9 @@ namespace Ryujinx.Ava.UI.Views.Main
|
|||||||
Window.SettingsWindow = new(Window.VirtualFileSystem, Window.ContentManager);
|
Window.SettingsWindow = new(Window.VirtualFileSystem, Window.ContentManager);
|
||||||
|
|
||||||
Rainbow.Enable();
|
Rainbow.Enable();
|
||||||
|
|
||||||
if (ViewModel.SelectedApplication is null) // Checks if game data exists
|
await Window.SettingsWindow.ShowDialog(Window);
|
||||||
{
|
|
||||||
await Window.SettingsWindow.ShowDialog(Window);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
bool userConfigExist = Program.FindGameConfig(Program.GetDirGameUserConfig(ViewModel.SelectedApplication.IdString, false, false));
|
|
||||||
|
|
||||||
if (!ViewModel.IsGameRunning || !userConfigExist)
|
|
||||||
{
|
|
||||||
await Window.SettingsWindow.ShowDialog(Window); // The game is not running, or if the user configuration does not exist
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// If there is a custom configuration in the folder
|
|
||||||
await new UserConfigWindows(ViewModel, userConfigExist).ShowDialog((Window)ViewModel.TopLevel);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rainbow.Disable();
|
Rainbow.Disable();
|
||||||
Rainbow.Reset();
|
Rainbow.Reset();
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
Background="{DynamicResource ThemeContentBackgroundColor}"
|
Background="{DynamicResource ThemeContentBackgroundColor}"
|
||||||
DockPanel.Dock="Bottom"
|
DockPanel.Dock="Bottom"
|
||||||
IsVisible="{Binding ShowMenuAndStatusBar}"
|
IsVisible="{Binding ShowMenuAndStatusBar}"
|
||||||
ColumnDefinitions="Auto,Auto,*,Auto,Auto">
|
ColumnDefinitions="Auto,Auto,*,Auto,Auto,Auto">
|
||||||
<StackPanel
|
<StackPanel
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
Margin="5"
|
Margin="5"
|
||||||
@@ -280,9 +280,31 @@
|
|||||||
Text="{Binding GpuNameText}"
|
Text="{Binding GpuNameText}"
|
||||||
TextAlignment="Start" />
|
TextAlignment="Start" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<StackPanel
|
<StackPanel
|
||||||
Grid.Column="4"
|
Grid.Column="4"
|
||||||
Margin="0,0,5,0"
|
Margin="0,0,5,0"
|
||||||
|
Orientation="Horizontal">
|
||||||
|
<StackPanel.IsVisible>
|
||||||
|
<MultiBinding Converter="{x:Static BoolConverters.And}">
|
||||||
|
<Binding Path="EnableNonGameRunningControls" />
|
||||||
|
<Binding Path="UpdateAvailable" />
|
||||||
|
</MultiBinding>
|
||||||
|
</StackPanel.IsVisible>
|
||||||
|
<Button Margin="0, 0, 5, -2"
|
||||||
|
Command="{Binding UpdateCommand}"
|
||||||
|
Background="{DynamicResource SystemAccentColor}">
|
||||||
|
<TextBlock
|
||||||
|
Margin="-5"
|
||||||
|
Foreground="{StaticResource SystemColorButtonTextColor}"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Text="{ext:Locale UpdaterBackgroundStatusBarButtonText}" />
|
||||||
|
</Button>
|
||||||
|
<controls:MiniVerticalSeparator Margin="5,0,0,0"/>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel
|
||||||
|
Grid.Column="5"
|
||||||
|
Margin="0,0,5,0"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
IsVisible="{Binding ShowFirmwareStatus}"
|
IsVisible="{Binding ShowFirmwareStatus}"
|
||||||
Orientation="Horizontal">
|
Orientation="Horizontal">
|
||||||
|
|||||||
@@ -74,6 +74,10 @@
|
|||||||
ToolTip.Tip="{ext:Locale DebugLogTooltip}">
|
ToolTip.Tip="{ext:Locale DebugLogTooltip}">
|
||||||
<TextBlock Text="{ext:Locale SettingsTabLoggingEnableDebugLogs}" />
|
<TextBlock Text="{ext:Locale SettingsTabLoggingEnableDebugLogs}" />
|
||||||
</CheckBox>
|
</CheckBox>
|
||||||
|
<CheckBox IsChecked="{Binding EnableAvaloniaLog}"
|
||||||
|
ToolTip.Tip="{ext:Locale AvaloniaLogTooltip}">
|
||||||
|
<TextBlock Text="{ext:Locale SettingsTabLoggingEnableAvaloniaLogs}" />
|
||||||
|
</CheckBox>
|
||||||
<StackPanel Margin="0,10,0,0" Orientation="Horizontal" VerticalAlignment="Stretch">
|
<StackPanel Margin="0,10,0,0" Orientation="Horizontal" VerticalAlignment="Stretch">
|
||||||
<TextBlock VerticalAlignment="Center"
|
<TextBlock VerticalAlignment="Center"
|
||||||
ToolTip.Tip="{ext:Locale FSAccessLogModeTooltip}"
|
ToolTip.Tip="{ext:Locale FSAccessLogModeTooltip}"
|
||||||
|
|||||||
@@ -316,8 +316,8 @@
|
|||||||
</CheckBox>
|
</CheckBox>
|
||||||
<CheckBox
|
<CheckBox
|
||||||
IsChecked="{Binding IgnoreApplet}"
|
IsChecked="{Binding IgnoreApplet}"
|
||||||
ToolTip.Tip="{ext:Locale IgnoreAppletTooltip}">
|
ToolTip.Tip="{ext:Locale IgnoreControllerAppletTooltip}">
|
||||||
<TextBlock Text="{ext:Locale SettingsTabSystemIgnoreApplet}" />
|
<TextBlock Text="{ext:Locale SettingsTabSystemIgnoreControllerApplet}" />
|
||||||
</CheckBox>
|
</CheckBox>
|
||||||
<CheckBox
|
<CheckBox
|
||||||
IsChecked="{Binding EnableCustomVSyncInterval}"
|
IsChecked="{Binding EnableCustomVSyncInterval}"
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Interactivity;
|
|
||||||
using Ryujinx.Ava.UI.ViewModels;
|
using Ryujinx.Ava.UI.ViewModels;
|
||||||
using TimeZone = Ryujinx.Ava.UI.Models.TimeZone;
|
using TimeZone = Ryujinx.Ava.UI.Models.TimeZone;
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:ext="clr-namespace:Ryujinx.Ava.Common.Markup"
|
xmlns:ext="clr-namespace:Ryujinx.Ava.Common.Markup"
|
||||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
|
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
|
||||||
|
xmlns:helper="clr-namespace:Ryujinx.Common.Helper;assembly=Ryujinx.Common"
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
x:DataType="viewModels:SettingsViewModel">
|
x:DataType="viewModels:SettingsViewModel">
|
||||||
<Design.DataContext>
|
<Design.DataContext>
|
||||||
@@ -30,18 +31,57 @@
|
|||||||
ToolTip.Tip="{ext:Locale ToggleDiscordTooltip}"
|
ToolTip.Tip="{ext:Locale ToggleDiscordTooltip}"
|
||||||
Text="{ext:Locale SettingsTabGeneralEnableDiscordRichPresence}" />
|
Text="{ext:Locale SettingsTabGeneralEnableDiscordRichPresence}" />
|
||||||
</CheckBox>
|
</CheckBox>
|
||||||
<CheckBox IsChecked="{Binding CheckUpdatesOnStart}">
|
|
||||||
<TextBlock Text="{ext:Locale SettingsTabGeneralCheckUpdatesOnLaunch}" />
|
|
||||||
</CheckBox>
|
|
||||||
<CheckBox IsChecked="{Binding ShowConfirmExit}">
|
<CheckBox IsChecked="{Binding ShowConfirmExit}">
|
||||||
<TextBlock Text="{ext:Locale SettingsTabGeneralShowConfirmExitDialog}" />
|
<TextBlock Text="{ext:Locale SettingsTabGeneralShowConfirmExitDialog}" />
|
||||||
</CheckBox>
|
</CheckBox>
|
||||||
<CheckBox IsChecked="{Binding RememberWindowState}">
|
<CheckBox IsChecked="{Binding RememberWindowState}">
|
||||||
<TextBlock Text="{ext:Locale SettingsTabGeneralRememberWindowState}" />
|
<TextBlock Text="{ext:Locale SettingsTabGeneralRememberWindowState}" />
|
||||||
</CheckBox>
|
</CheckBox>
|
||||||
<CheckBox IsChecked="{Binding ShowTitleBar}" Name="ShowTitleBarBox">
|
<CheckBox IsChecked="{Binding ShowTitleBar}" IsVisible="{x:Static helper:RunningPlatform.IsWindows}">
|
||||||
<TextBlock Text="{ext:Locale SettingsTabGeneralShowTitleBar}" />
|
<TextBlock Text="{ext:Locale SettingsTabGeneralShowTitleBar}" />
|
||||||
</CheckBox>
|
</CheckBox>
|
||||||
|
<StackPanel Margin="0, 15, 0, 0" Orientation="Horizontal">
|
||||||
|
<TextBlock VerticalAlignment="Center"
|
||||||
|
Text="{ext:Locale SettingsTabGeneralFocusLossType}"
|
||||||
|
Width="150" />
|
||||||
|
<ComboBox SelectedIndex="{Binding FocusLostActionType}"
|
||||||
|
HorizontalContentAlignment="Left"
|
||||||
|
MinWidth="100">
|
||||||
|
<ComboBoxItem>
|
||||||
|
<TextBlock Text="{ext:Locale SettingsTabGeneralFocusLossTypeDoNothing}" />
|
||||||
|
</ComboBoxItem>
|
||||||
|
<ComboBoxItem>
|
||||||
|
<TextBlock Text="{ext:Locale SettingsTabGeneralFocusLossTypeBlockInput}" />
|
||||||
|
</ComboBoxItem>
|
||||||
|
<ComboBoxItem>
|
||||||
|
<TextBlock Text="{ext:Locale SettingsTabGeneralFocusLossTypeMuteAudio}" />
|
||||||
|
</ComboBoxItem>
|
||||||
|
<ComboBoxItem>
|
||||||
|
<TextBlock Text="{ext:Locale SettingsTabGeneralFocusLossTypeBlockInputAndMuteAudio}" />
|
||||||
|
</ComboBoxItem>
|
||||||
|
<ComboBoxItem>
|
||||||
|
<TextBlock Text="{ext:Locale SettingsTabGeneralFocusLossTypePauseEmulation}" />
|
||||||
|
</ComboBoxItem>
|
||||||
|
</ComboBox>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Margin="0, 15, 0, 0" Orientation="Horizontal">
|
||||||
|
<TextBlock VerticalAlignment="Center"
|
||||||
|
Text="{ext:Locale SettingsTabGeneralCheckUpdatesOnLaunch}"
|
||||||
|
Width="150" />
|
||||||
|
<ComboBox SelectedIndex="{Binding UpdateCheckerType}"
|
||||||
|
HorizontalContentAlignment="Left"
|
||||||
|
MinWidth="100">
|
||||||
|
<ComboBoxItem>
|
||||||
|
<TextBlock Text="{ext:Locale SettingsTabGeneralCheckUpdatesOnLaunchOff}" />
|
||||||
|
</ComboBoxItem>
|
||||||
|
<ComboBoxItem>
|
||||||
|
<TextBlock Text="{ext:Locale SettingsTabGeneralCheckUpdatesOnLaunchPromptAtStartup}" />
|
||||||
|
</ComboBoxItem>
|
||||||
|
<ComboBoxItem>
|
||||||
|
<TextBlock Text="{ext:Locale SettingsTabGeneralCheckUpdatesOnLaunchBackground}" />
|
||||||
|
</ComboBoxItem>
|
||||||
|
</ComboBox>
|
||||||
|
</StackPanel>
|
||||||
<StackPanel Margin="0, 15, 0, 0" Orientation="Horizontal">
|
<StackPanel Margin="0, 15, 0, 0" Orientation="Horizontal">
|
||||||
<TextBlock VerticalAlignment="Center"
|
<TextBlock VerticalAlignment="Center"
|
||||||
Text="{ext:Locale SettingsTabGeneralHideCursor}"
|
Text="{ext:Locale SettingsTabGeneralHideCursor}"
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ namespace Ryujinx.Ava.UI.Views.Settings
|
|||||||
public SettingsUiView()
|
public SettingsUiView()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
ShowTitleBarBox.IsVisible = OperatingSystem.IsWindows();
|
|
||||||
AddGameDirButton.Command =
|
AddGameDirButton.Command =
|
||||||
Commands.Create(() => AddDirButton(GameDirPathBox, ViewModel.GameDirectories, true));
|
Commands.Create(() => AddDirButton(GameDirPathBox, ViewModel.GameDirectories, true));
|
||||||
AddAutoloadDirButton.Command =
|
AddAutoloadDirButton.Command =
|
||||||
@@ -37,11 +36,8 @@ namespace Ryujinx.Ava.UI.Views.Settings
|
|||||||
directories.Add(path);
|
directories.Add(path);
|
||||||
|
|
||||||
addDirBox.Clear();
|
addDirBox.Clear();
|
||||||
|
|
||||||
if (isGameList)
|
ViewModel.GameListNeedsRefresh = true;
|
||||||
ViewModel.GameDirectoryChanged = true;
|
|
||||||
else
|
|
||||||
ViewModel.AutoloadDirectoryChanged = true;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -51,10 +47,7 @@ namespace Ryujinx.Ava.UI.Views.Settings
|
|||||||
{
|
{
|
||||||
directories.Add(folder.Value.Path.LocalPath);
|
directories.Add(folder.Value.Path.LocalPath);
|
||||||
|
|
||||||
if (isGameList)
|
ViewModel.GameListNeedsRefresh = true;
|
||||||
ViewModel.GameDirectoryChanged = true;
|
|
||||||
else
|
|
||||||
ViewModel.AutoloadDirectoryChanged = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -66,7 +59,7 @@ namespace Ryujinx.Ava.UI.Views.Settings
|
|||||||
foreach (string path in new List<string>(GameDirsList.SelectedItems.Cast<string>()))
|
foreach (string path in new List<string>(GameDirsList.SelectedItems.Cast<string>()))
|
||||||
{
|
{
|
||||||
ViewModel.GameDirectories.Remove(path);
|
ViewModel.GameDirectories.Remove(path);
|
||||||
ViewModel.GameDirectoryChanged = true;
|
ViewModel.GameListNeedsRefresh = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (GameDirsList.ItemCount > 0)
|
if (GameDirsList.ItemCount > 0)
|
||||||
@@ -82,7 +75,7 @@ namespace Ryujinx.Ava.UI.Views.Settings
|
|||||||
foreach (string path in new List<string>(AutoloadDirsList.SelectedItems.Cast<string>()))
|
foreach (string path in new List<string>(AutoloadDirsList.SelectedItems.Cast<string>()))
|
||||||
{
|
{
|
||||||
ViewModel.AutoloadDirectories.Remove(path);
|
ViewModel.AutoloadDirectories.Remove(path);
|
||||||
ViewModel.AutoloadDirectoryChanged = true;
|
ViewModel.GameListNeedsRefresh = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (AutoloadDirsList.ItemCount > 0)
|
if (AutoloadDirsList.ItemCount > 0)
|
||||||
|
|||||||
@@ -125,7 +125,7 @@
|
|||||||
Background="Transparent"
|
Background="Transparent"
|
||||||
Click="Button_OnClick"
|
Click="Button_OnClick"
|
||||||
CornerRadius="15"
|
CornerRadius="15"
|
||||||
Tag="https://discord.gg/dHPrkBkkyA"
|
Tag="https://discord.gg/PEuzjrFXUA"
|
||||||
ToolTip.Tip="{ext:Locale AboutDiscordUrlTooltipMessage}">
|
ToolTip.Tip="{ext:Locale AboutDiscordUrlTooltipMessage}">
|
||||||
<Image Source="{Binding DiscordLogo}" />
|
<Image Source="{Binding DiscordLogo}" />
|
||||||
</Button>
|
</Button>
|
||||||
@@ -142,42 +142,40 @@
|
|||||||
<Grid
|
<Grid
|
||||||
Grid.Column="2"
|
Grid.Column="2"
|
||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
VerticalAlignment="Stretch" RowDefinitions="Auto,Auto">
|
VerticalAlignment="Stretch" RowDefinitions="Auto,Auto,Auto">
|
||||||
<StackPanel
|
<StackPanel
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
Margin="0,10,0,0"
|
|
||||||
Spacing="2">
|
Spacing="2">
|
||||||
<TextBlock
|
<TextBlock
|
||||||
FontSize="15"
|
Classes="h1"
|
||||||
FontWeight="Bold"
|
FontWeight="Bold"
|
||||||
Text="{ext:Locale AboutRyujinxAboutTitle}" />
|
Text="{ext:Locale AboutRyujinxAboutTitle}" />
|
||||||
<TextBlock
|
<TextBlock
|
||||||
FontSize="10"
|
|
||||||
Text="{ext:Locale AboutRyujinxAboutContent}"
|
Text="{ext:Locale AboutRyujinxAboutContent}"
|
||||||
TextWrapping="Wrap" />
|
TextWrapping="Wrap" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
<Separator Grid.Row="1" Margin="0,20" />
|
||||||
<StackPanel
|
<StackPanel
|
||||||
Grid.Row="1"
|
Grid.Row="2"
|
||||||
Margin="0,10,0,0"
|
|
||||||
Spacing="2">
|
Spacing="2">
|
||||||
<TextBlock
|
<TextBlock
|
||||||
FontSize="15"
|
Classes="h1"
|
||||||
FontWeight="Bold"
|
FontWeight="Bold"
|
||||||
Text="{ext:Locale AboutRyujinxMaintainersTitle}" />
|
Text="{ext:Locale AboutRyujinxMaintainersTitle}" />
|
||||||
<TextBlock
|
<TextBlock
|
||||||
FontSize="10"
|
|
||||||
Margin="0, 0, 0, 5"
|
Margin="0, 0, 0, 5"
|
||||||
TextWrapping="Wrap"
|
TextWrapping="Wrap"
|
||||||
Text="{Binding Developers}"/>
|
Text="{Binding Developers}"/>
|
||||||
<TextBlock
|
<TextBlock
|
||||||
FontSize="15"
|
Classes="h1"
|
||||||
FontWeight="Bold"
|
FontWeight="Bold"
|
||||||
Text="{ext:Locale AboutRyujinxFormerMaintainersTitle}" />
|
Text="{ext:Locale AboutRyujinxFormerMaintainersTitle}" />
|
||||||
<TextBlock
|
<TextBlock
|
||||||
FontSize="10"
|
FontSize="11"
|
||||||
Text="{Binding FormerDevelopers}"
|
Text="{Binding FormerDevelopers}"
|
||||||
TextWrapping="Wrap" />
|
TextWrapping="Wrap" />
|
||||||
<Button
|
<Button
|
||||||
|
Margin="0, 5, 0, 0"
|
||||||
Padding="5"
|
Padding="5"
|
||||||
HorizontalAlignment="Left"
|
HorizontalAlignment="Left"
|
||||||
Background="Transparent"
|
Background="Transparent"
|
||||||
|
|||||||
@@ -18,8 +18,6 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
{
|
{
|
||||||
public AboutWindow()
|
public AboutWindow()
|
||||||
{
|
{
|
||||||
DataContext = new AboutWindowViewModel();
|
|
||||||
|
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
GitHubRepoButton.Tag =
|
GitHubRepoButton.Tag =
|
||||||
@@ -28,12 +26,14 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
|
|
||||||
public static async Task Show()
|
public static async Task Show()
|
||||||
{
|
{
|
||||||
|
using AboutWindowViewModel viewModel = new();
|
||||||
|
|
||||||
ContentDialog contentDialog = new()
|
ContentDialog contentDialog = new()
|
||||||
{
|
{
|
||||||
PrimaryButtonText = string.Empty,
|
PrimaryButtonText = string.Empty,
|
||||||
SecondaryButtonText = string.Empty,
|
SecondaryButtonText = string.Empty,
|
||||||
CloseButtonText = LocaleManager.Instance[LocaleKeys.UserProfilesClose],
|
CloseButtonText = LocaleManager.Instance[LocaleKeys.UserProfilesClose],
|
||||||
Content = new AboutWindow()
|
Content = new AboutWindow { DataContext = viewModel }
|
||||||
};
|
};
|
||||||
|
|
||||||
Style closeButton = new(x => x.Name("CloseButton"));
|
Style closeButton = new(x => x.Name("CloseButton"));
|
||||||
|
|||||||
@@ -21,7 +21,9 @@
|
|||||||
x:DataType="viewModels:MainWindowViewModel"
|
x:DataType="viewModels:MainWindowViewModel"
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
WindowStartupLocation="Manual"
|
WindowStartupLocation="Manual"
|
||||||
Focusable="True">
|
Focusable="True"
|
||||||
|
GotFocus="InputElement_OnGotFocus"
|
||||||
|
LostFocus="InputElement_OnLostFocus">
|
||||||
<Window.Styles>
|
<Window.Styles>
|
||||||
<Style Selector="TitleBar:fullscreen">
|
<Style Selector="TitleBar:fullscreen">
|
||||||
<Setter Property="Background" Value="#000000" />
|
<Setter Property="Background" Value="#000000" />
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Controls.Primitives;
|
using Avalonia.Controls.Primitives;
|
||||||
|
using Avalonia.Input;
|
||||||
using Avalonia.Interactivity;
|
using Avalonia.Interactivity;
|
||||||
using Avalonia.Platform;
|
using Avalonia.Platform;
|
||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
@@ -19,6 +20,7 @@ using Ryujinx.Ava.UI.ViewModels;
|
|||||||
using Ryujinx.Ava.Utilities;
|
using Ryujinx.Ava.Utilities;
|
||||||
using Ryujinx.Ava.Utilities.AppLibrary;
|
using Ryujinx.Ava.Utilities.AppLibrary;
|
||||||
using Ryujinx.Ava.Utilities.Configuration;
|
using Ryujinx.Ava.Utilities.Configuration;
|
||||||
|
using Ryujinx.Ava.Utilities.Configuration.UI;
|
||||||
using Ryujinx.Common;
|
using Ryujinx.Common;
|
||||||
using Ryujinx.Common.Helper;
|
using Ryujinx.Common.Helper;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
@@ -400,10 +402,21 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
await Dispatcher.UIThread.InvokeAsync(async () => await UserErrorDialog.ShowUserErrorDialog(UserError.NoKeys));
|
await Dispatcher.UIThread.InvokeAsync(async () => await UserErrorDialog.ShowUserErrorDialog(UserError.NoKeys));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ConfigurationState.Instance.CheckUpdatesOnStart && !CommandLineState.HideAvailableUpdates && Updater.CanUpdate())
|
if (!Updater.CanUpdate() || CommandLineState.HideAvailableUpdates)
|
||||||
|
return;
|
||||||
|
|
||||||
|
switch (ConfigurationState.Instance.UpdateCheckerType.Value)
|
||||||
{
|
{
|
||||||
await Updater.BeginUpdateAsync()
|
case UpdaterType.PromptAtStartup:
|
||||||
.Catch(task => Logger.Error?.Print(LogClass.Application, $"Updater Error: {task.Exception}"));
|
await Updater.BeginUpdateAsync()
|
||||||
|
.Catch(task => Logger.Error?.Print(LogClass.Application, $"Updater Error: {task.Exception}"));
|
||||||
|
break;
|
||||||
|
case UpdaterType.CheckInBackground:
|
||||||
|
if ((await Updater.CheckVersionAsync()).TryGet(out (Version Current, Version Incoming) versions))
|
||||||
|
{
|
||||||
|
Dispatcher.UIThread.Post(() => RyujinxApp.MainWindow.ViewModel.UpdateAvailable = versions.Current < versions.Incoming);
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -645,11 +658,6 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
ReloadGameList();
|
ReloadGameList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void GameListUpdate()
|
|
||||||
{
|
|
||||||
ReloadGameList();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ToggleFileType(string fileType)
|
public void ToggleFileType(string fileType)
|
||||||
{
|
{
|
||||||
switch (fileType)
|
switch (fileType)
|
||||||
@@ -754,5 +762,119 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
|
|
||||||
_intelMacWarningShown = true;
|
_intelMacWarningShown = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void InputElement_OnGotFocus(object sender, GotFocusEventArgs e)
|
||||||
|
{
|
||||||
|
if (ViewModel.AppHost is null) return;
|
||||||
|
|
||||||
|
if (!_focusLoss.Active)
|
||||||
|
return;
|
||||||
|
|
||||||
|
switch (_focusLoss.Type)
|
||||||
|
{
|
||||||
|
case FocusLostType.BlockInput:
|
||||||
|
{
|
||||||
|
if (!ViewModel.AppHost.NpadManager.InputUpdatesBlocked)
|
||||||
|
{
|
||||||
|
_focusLoss = default;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ViewModel.AppHost.NpadManager.UnblockInputUpdates();
|
||||||
|
_focusLoss = default;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case FocusLostType.MuteAudio:
|
||||||
|
{
|
||||||
|
if (!ViewModel.AppHost.Device.IsAudioMuted())
|
||||||
|
{
|
||||||
|
_focusLoss = default;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ViewModel.AppHost.Device.SetVolume(ViewModel.VolumeBeforeMute);
|
||||||
|
|
||||||
|
_focusLoss = default;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case FocusLostType.BlockInputAndMuteAudio:
|
||||||
|
{
|
||||||
|
if (!ViewModel.AppHost.Device.IsAudioMuted())
|
||||||
|
goto case FocusLostType.BlockInput;
|
||||||
|
|
||||||
|
ViewModel.AppHost.Device.SetVolume(ViewModel.VolumeBeforeMute);
|
||||||
|
ViewModel.AppHost.NpadManager.UnblockInputUpdates();
|
||||||
|
|
||||||
|
_focusLoss = default;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case FocusLostType.PauseEmulation:
|
||||||
|
{
|
||||||
|
if (!ViewModel.AppHost.Device.System.IsPaused)
|
||||||
|
{
|
||||||
|
_focusLoss = default;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ViewModel.AppHost.Resume();
|
||||||
|
|
||||||
|
_focusLoss = default;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private (FocusLostType Type, bool Active) _focusLoss;
|
||||||
|
|
||||||
|
private void InputElement_OnLostFocus(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (ConfigurationState.Instance.FocusLostActionType.Value is FocusLostType.DoNothing)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (ViewModel.AppHost is null) return;
|
||||||
|
|
||||||
|
switch (ConfigurationState.Instance.FocusLostActionType.Value)
|
||||||
|
{
|
||||||
|
case FocusLostType.BlockInput:
|
||||||
|
{
|
||||||
|
if (ViewModel.AppHost.NpadManager.InputUpdatesBlocked)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ViewModel.AppHost.NpadManager.BlockInputUpdates();
|
||||||
|
_focusLoss = (FocusLostType.BlockInput, ViewModel.AppHost.NpadManager.InputUpdatesBlocked);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case FocusLostType.MuteAudio:
|
||||||
|
{
|
||||||
|
if (ViewModel.AppHost.Device.GetVolume() is 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ViewModel.VolumeBeforeMute = ViewModel.AppHost.Device.GetVolume();
|
||||||
|
ViewModel.AppHost.Device.SetVolume(0);
|
||||||
|
_focusLoss = (FocusLostType.MuteAudio, ViewModel.AppHost.Device.GetVolume() is 0f);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case FocusLostType.BlockInputAndMuteAudio:
|
||||||
|
{
|
||||||
|
if (ViewModel.AppHost.Device.GetVolume() is 0)
|
||||||
|
goto case FocusLostType.BlockInput;
|
||||||
|
|
||||||
|
ViewModel.VolumeBeforeMute = ViewModel.AppHost.Device.GetVolume();
|
||||||
|
ViewModel.AppHost.Device.SetVolume(0);
|
||||||
|
ViewModel.AppHost.NpadManager.BlockInputUpdates();
|
||||||
|
_focusLoss = (FocusLostType.BlockInputAndMuteAudio, ViewModel.AppHost.Device.GetVolume() is 0f && ViewModel.AppHost.NpadManager.InputUpdatesBlocked);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case FocusLostType.PauseEmulation:
|
||||||
|
{
|
||||||
|
if (ViewModel.AppHost.Device.System.IsPaused)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ViewModel.AppHost.Pause();
|
||||||
|
_focusLoss = (FocusLostType.PauseEmulation, ViewModel.AppHost.Device.System.IsPaused);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
{
|
{
|
||||||
InputPage.InputView?.SaveCurrentProfile();
|
InputPage.InputView?.SaveCurrentProfile();
|
||||||
|
|
||||||
if (Owner is MainWindow window && (ViewModel.GameDirectoryChanged || ViewModel.AutoloadDirectoryChanged))
|
if (Owner is MainWindow window && ViewModel.GameListNeedsRefresh)
|
||||||
{
|
{
|
||||||
window.LoadApplications();
|
window.LoadApplications();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,144 +0,0 @@
|
|||||||
<window:StyleableAppWindow
|
|
||||||
x:Class="Ryujinx.Ava.UI.Windows.UserConfigWindows"
|
|
||||||
xmlns="https://github.com/avaloniaui"
|
|
||||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
||||||
xmlns:ext="clr-namespace:Ryujinx.Ava.Common.Markup"
|
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
|
||||||
xmlns:window="clr-namespace:Ryujinx.Ava.UI.Windows"
|
|
||||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
|
|
||||||
xmlns:settings="clr-namespace:Ryujinx.Ava.UI.Views.Settings"
|
|
||||||
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
|
|
||||||
xmlns:helper="clr-namespace:Ryujinx.Common.Helper;assembly=Ryujinx.Common"
|
|
||||||
Width="1100"
|
|
||||||
Height="768"
|
|
||||||
MinWidth="800"
|
|
||||||
MinHeight="480"
|
|
||||||
WindowStartupLocation="CenterOwner"
|
|
||||||
x:DataType="viewModels:SettingsViewModel"
|
|
||||||
mc:Ignorable="d"
|
|
||||||
Focusable="True">
|
|
||||||
<Design.DataContext>
|
|
||||||
<viewModels:SettingsViewModel />
|
|
||||||
</Design.DataContext>
|
|
||||||
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" MinWidth="600" RowDefinitions="Auto,*,Auto">
|
|
||||||
<ContentPresenter
|
|
||||||
x:Name="ContentPresenter"
|
|
||||||
Grid.Row="1"
|
|
||||||
IsVisible="False"
|
|
||||||
KeyboardNavigation.IsTabStop="False"/>
|
|
||||||
<Grid Name="Pages" IsVisible="False" Grid.Row="2">
|
|
||||||
<settings:SettingsInputView Name="InputPage" />
|
|
||||||
<settings:SettingsSystemView Name="SystemPage" />
|
|
||||||
<settings:SettingsCPUView Name="CpuPage" />
|
|
||||||
<settings:SettingsGraphicsView Name="GraphicsPage" />
|
|
||||||
<settings:SettingsAudioView Name="AudioPage" />
|
|
||||||
<settings:SettingsNetworkView Name="NetworkPage" />
|
|
||||||
<settings:SettingsLoggingView Name="LoggingPage" />
|
|
||||||
<settings:SettingsHacksView Name="HacksPage" />
|
|
||||||
</Grid>
|
|
||||||
<ui:NavigationView
|
|
||||||
Grid.Row="1"
|
|
||||||
IsSettingsVisible="False"
|
|
||||||
Name="NavPanel"
|
|
||||||
IsBackEnabled="False"
|
|
||||||
PaneDisplayMode="Left"
|
|
||||||
Margin="2,10,10,0"
|
|
||||||
VerticalAlignment="Stretch"
|
|
||||||
HorizontalAlignment="Stretch"
|
|
||||||
OpenPaneLength="200"
|
|
||||||
IsPaneToggleButtonVisible="False">
|
|
||||||
|
|
||||||
<!-- For image -->
|
|
||||||
<ui:NavigationView.PaneHeader>
|
|
||||||
<Grid HorizontalAlignment="Center" VerticalAlignment="Center">
|
|
||||||
<Grid.RowDefinitions>
|
|
||||||
<RowDefinition Height="Auto"/>
|
|
||||||
<RowDefinition Height="Auto"/>
|
|
||||||
<RowDefinition Height="Auto"/>
|
|
||||||
<RowDefinition Height="5"/>
|
|
||||||
</Grid.RowDefinitions>
|
|
||||||
<TextBlock Text="{Binding GameId}"
|
|
||||||
HorizontalAlignment="Center"
|
|
||||||
VerticalAlignment="Center"
|
|
||||||
Margin="0,0,0,10"
|
|
||||||
TextAlignment="Center" Grid.Row="0" />
|
|
||||||
<Image Source="{Binding GameIcon}"
|
|
||||||
Width="160"
|
|
||||||
Height="160"
|
|
||||||
Grid.Row="1"
|
|
||||||
Margin="0,0,0,10"
|
|
||||||
HorizontalAlignment="Center" />
|
|
||||||
<TextBlock Text="{Binding GameTitle}"
|
|
||||||
HorizontalAlignment="Center"
|
|
||||||
VerticalAlignment="Center"
|
|
||||||
Margin="0,0,0,10"
|
|
||||||
TextAlignment="Center" Grid.Row="2" />
|
|
||||||
<Separator Height="1" Grid.Row="3" Margin="0,0,0,10" HorizontalAlignment="Stretch"/>
|
|
||||||
</Grid>
|
|
||||||
</ui:NavigationView.PaneHeader>
|
|
||||||
|
|
||||||
<ui:NavigationView.MenuItems>
|
|
||||||
<ui:NavigationViewItem
|
|
||||||
Content="{ext:Locale SettingsTabInput}"
|
|
||||||
Tag="InputPage"
|
|
||||||
IconSource="Games" />
|
|
||||||
<ui:NavigationViewItem
|
|
||||||
Content="{ext:Locale SettingsTabSystem}"
|
|
||||||
Tag="SystemPage"
|
|
||||||
IconSource="Settings" />
|
|
||||||
<ui:NavigationViewItem
|
|
||||||
Content="{ext:Locale SettingsTabCpu}"
|
|
||||||
Tag="CpuPage">
|
|
||||||
<ui:NavigationViewItem.IconSource>
|
|
||||||
<ui:FontIconSource
|
|
||||||
FontFamily="avares://Ryujinx/Assets/Fonts#Segoe Fluent Icons"
|
|
||||||
Glyph="{helpers:GlyphValueConverter Chip}" />
|
|
||||||
</ui:NavigationViewItem.IconSource>
|
|
||||||
</ui:NavigationViewItem>
|
|
||||||
<ui:NavigationViewItem
|
|
||||||
Content="{ext:Locale SettingsTabGraphics}"
|
|
||||||
Tag="GraphicsPage"
|
|
||||||
IconSource="Image" />
|
|
||||||
<ui:NavigationViewItem
|
|
||||||
Content="{ext:Locale SettingsTabAudio}"
|
|
||||||
IconSource="Audio"
|
|
||||||
Tag="AudioPage" />
|
|
||||||
<ui:NavigationViewItem
|
|
||||||
Content="{ext:Locale SettingsTabNetwork}"
|
|
||||||
Tag="NetworkPage"
|
|
||||||
IconSource="Globe" />
|
|
||||||
<ui:NavigationViewItem
|
|
||||||
Content="{ext:Locale SettingsTabLogging}"
|
|
||||||
Tag="LoggingPage"
|
|
||||||
IconSource="Document" />
|
|
||||||
<ui:NavigationViewItem
|
|
||||||
IsVisible="{Binding ShowDirtyHacks}"
|
|
||||||
Content="Dirty Hacks"
|
|
||||||
Tag="HacksPage"
|
|
||||||
IconSource="Code" />
|
|
||||||
</ui:NavigationView.MenuItems>
|
|
||||||
</ui:NavigationView>
|
|
||||||
|
|
||||||
<ReversibleStackPanel
|
|
||||||
Grid.Row="2"
|
|
||||||
Margin="10"
|
|
||||||
Spacing="10"
|
|
||||||
Orientation="Horizontal"
|
|
||||||
HorizontalAlignment="Right"
|
|
||||||
ReverseOrder="{x:Static helper:RunningPlatform.IsMacOS}">
|
|
||||||
<Button
|
|
||||||
Content="{ext:Locale SettingsButtonSave}"
|
|
||||||
Command="{Binding SaveUserConfig}" />
|
|
||||||
<Button
|
|
||||||
HotKey="Escape"
|
|
||||||
Content="{ext:Locale SettingsButtonClose}"
|
|
||||||
Command="{Binding CancelButton}" />
|
|
||||||
<Button
|
|
||||||
Content="{ext:Locale UserProfilesDelete}"
|
|
||||||
Command="{Binding DeleteConfigGame}"
|
|
||||||
Classes="red"/>
|
|
||||||
</ReversibleStackPanel>
|
|
||||||
</Grid>
|
|
||||||
</window:StyleableAppWindow>
|
|
||||||
@@ -1,116 +0,0 @@
|
|||||||
using Avalonia;
|
|
||||||
using Avalonia.Controls;
|
|
||||||
using Avalonia.Controls.Shapes;
|
|
||||||
using Avalonia.Input;
|
|
||||||
using Avalonia.Media.Imaging;
|
|
||||||
using FluentAvalonia.Core;
|
|
||||||
using FluentAvalonia.UI.Controls;
|
|
||||||
using Projektanker.Icons.Avalonia;
|
|
||||||
using Ryujinx.Ava.Common.Locale;
|
|
||||||
using Ryujinx.Ava.UI.Models;
|
|
||||||
using Ryujinx.Ava.UI.ViewModels;
|
|
||||||
using Ryujinx.Ava.UI.ViewModels.Input;
|
|
||||||
using Ryujinx.Ava.Utilities;
|
|
||||||
using Ryujinx.Ava.Utilities.Configuration;
|
|
||||||
using Ryujinx.Common.Configuration;
|
|
||||||
using Ryujinx.HLE.FileSystem;
|
|
||||||
using Ryujinx.HLE.HOS.SystemState;
|
|
||||||
using Ryujinx.Input;
|
|
||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using Key = Avalonia.Input.Key;
|
|
||||||
|
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.Windows
|
|
||||||
{
|
|
||||||
public partial class UserConfigWindows : StyleableAppWindow
|
|
||||||
{
|
|
||||||
internal readonly SettingsViewModel ViewModel;
|
|
||||||
|
|
||||||
public UserConfigWindows(MainWindowViewModel viewModel, bool findUserConfigDir = true)
|
|
||||||
{
|
|
||||||
Title = string.Format(LocaleManager.Instance[LocaleKeys.SettingsWithInfo], viewModel.SelectedApplication.Name, viewModel.SelectedApplication.IdString);
|
|
||||||
|
|
||||||
DataContext = ViewModel = new SettingsViewModel(
|
|
||||||
viewModel.VirtualFileSystem,
|
|
||||||
viewModel.ContentManager,
|
|
||||||
viewModel.SelectedApplication.Path,
|
|
||||||
viewModel.SelectedApplication.Name,
|
|
||||||
viewModel.SelectedApplication.IdString,
|
|
||||||
viewModel.SelectedApplication.Icon,
|
|
||||||
findUserConfigDir);
|
|
||||||
|
|
||||||
ViewModel.CloseWindow += Close;
|
|
||||||
ViewModel.SaveSettingsEvent += SaveSettings;
|
|
||||||
|
|
||||||
InitializeComponent();
|
|
||||||
Load();
|
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
this.AttachDevTools(new KeyGesture(Key.F12, KeyModifiers.Alt));
|
|
||||||
#endif
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SaveSettings()
|
|
||||||
{
|
|
||||||
InputPage.InputView?.SaveCurrentProfile();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void Load()
|
|
||||||
{
|
|
||||||
Pages.Children.Clear();
|
|
||||||
NavPanel.SelectionChanged += NavPanelOnSelectionChanged;
|
|
||||||
NavPanel.SelectedItem = NavPanel.MenuItems.ElementAt(0);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private void NavPanelOnSelectionChanged(object sender, NavigationViewSelectionChangedEventArgs e)
|
|
||||||
{
|
|
||||||
|
|
||||||
if (e.SelectedItem is NavigationViewItem navItem && navItem.Tag is not null)
|
|
||||||
{
|
|
||||||
switch (navItem.Tag.ToString())
|
|
||||||
{
|
|
||||||
case nameof(InputPage):
|
|
||||||
NavPanel.Content = InputPage;
|
|
||||||
break;
|
|
||||||
case nameof(SystemPage):
|
|
||||||
SystemPage.ViewModel = ViewModel;
|
|
||||||
NavPanel.Content = SystemPage;
|
|
||||||
break;
|
|
||||||
case nameof(CpuPage):
|
|
||||||
NavPanel.Content = CpuPage;
|
|
||||||
break;
|
|
||||||
case nameof(GraphicsPage):
|
|
||||||
NavPanel.Content = GraphicsPage;
|
|
||||||
break;
|
|
||||||
case nameof(AudioPage):
|
|
||||||
NavPanel.Content = AudioPage;
|
|
||||||
break;
|
|
||||||
case nameof(NetworkPage):
|
|
||||||
NetworkPage.ViewModel = ViewModel;
|
|
||||||
NavPanel.Content = NetworkPage;
|
|
||||||
break;
|
|
||||||
case nameof(LoggingPage):
|
|
||||||
NavPanel.Content = LoggingPage;
|
|
||||||
break;
|
|
||||||
case nameof(HacksPage):
|
|
||||||
HacksPage.DataContext = ViewModel;
|
|
||||||
NavPanel.Content = HacksPage;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnClosing(WindowClosingEventArgs e)
|
|
||||||
{
|
|
||||||
InputPage.Dispose(); // You need to unload the gamepad settings, otherwise the controls will be blocked
|
|
||||||
base.OnClosing(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -43,7 +43,18 @@ namespace Ryujinx.Ava
|
|||||||
private const int ConnectionCount = 4;
|
private const int ConnectionCount = 4;
|
||||||
|
|
||||||
private static string _buildVer;
|
private static string _buildVer;
|
||||||
private static string _platformExt;
|
|
||||||
|
private static readonly string _platformExt =
|
||||||
|
RunningPlatform.IsMacOS
|
||||||
|
? "macos_universal.app.tar.gz"
|
||||||
|
: RunningPlatform.IsWindows
|
||||||
|
? "win_x64.zip"
|
||||||
|
: RunningPlatform.IsX64Linux
|
||||||
|
? "linux_x64.tar.gz"
|
||||||
|
: RunningPlatform.IsArmLinux
|
||||||
|
? "linux_arm64.tar.gz"
|
||||||
|
: throw new PlatformNotSupportedException();
|
||||||
|
|
||||||
private static string _buildUrl;
|
private static string _buildUrl;
|
||||||
private static long _buildSize;
|
private static long _buildSize;
|
||||||
private static bool _updateSuccessful;
|
private static bool _updateSuccessful;
|
||||||
@@ -51,30 +62,8 @@ namespace Ryujinx.Ava
|
|||||||
|
|
||||||
private static readonly string[] _windowsDependencyDirs = [];
|
private static readonly string[] _windowsDependencyDirs = [];
|
||||||
|
|
||||||
public static async Task BeginUpdateAsync(bool showVersionUpToDate = false)
|
public static async Task<Optional<(Version Current, Version Incoming)>> CheckVersionAsync(bool showVersionUpToDate = false)
|
||||||
{
|
{
|
||||||
if (_running)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_running = true;
|
|
||||||
|
|
||||||
// Detect current platform
|
|
||||||
if (OperatingSystem.IsMacOS())
|
|
||||||
{
|
|
||||||
_platformExt = "macos_universal.app.tar.gz";
|
|
||||||
}
|
|
||||||
else if (OperatingSystem.IsWindows())
|
|
||||||
{
|
|
||||||
_platformExt = "win_x64.zip";
|
|
||||||
}
|
|
||||||
else if (OperatingSystem.IsLinux())
|
|
||||||
{
|
|
||||||
string arch = RuntimeInformation.OSArchitecture == Architecture.Arm64 ? "arm64" : "x64";
|
|
||||||
_platformExt = $"linux_{arch}.tar.gz";
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {RyujinxApp.FullAppName} version!");
|
Logger.Error?.Print(LogClass.Application, $"Failed to convert the current {RyujinxApp.FullAppName} version!");
|
||||||
@@ -85,7 +74,7 @@ namespace Ryujinx.Ava
|
|||||||
|
|
||||||
_running = false;
|
_running = false;
|
||||||
|
|
||||||
return;
|
return default;
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.Info?.Print(LogClass.Application, "Checking for updates.");
|
Logger.Info?.Print(LogClass.Application, "Checking for updates.");
|
||||||
@@ -123,7 +112,7 @@ namespace Ryujinx.Ava
|
|||||||
|
|
||||||
_running = false;
|
_running = false;
|
||||||
|
|
||||||
return;
|
return default;
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@@ -149,7 +138,7 @@ namespace Ryujinx.Ava
|
|||||||
|
|
||||||
_running = false;
|
_running = false;
|
||||||
|
|
||||||
return;
|
return default;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
@@ -161,7 +150,7 @@ namespace Ryujinx.Ava
|
|||||||
|
|
||||||
_running = false;
|
_running = false;
|
||||||
|
|
||||||
return;
|
return default;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Version.TryParse(_buildVer, out Version newVersion))
|
if (!Version.TryParse(_buildVer, out Version newVersion))
|
||||||
@@ -174,9 +163,27 @@ namespace Ryujinx.Ava
|
|||||||
|
|
||||||
_running = false;
|
_running = false;
|
||||||
|
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (currentVersion, newVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task BeginUpdateAsync(bool showVersionUpToDate = false)
|
||||||
|
{
|
||||||
|
if (_running)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_running = true;
|
||||||
|
|
||||||
|
Optional<(Version, Version)> versionTuple = await CheckVersionAsync(showVersionUpToDate);
|
||||||
|
|
||||||
|
if (_running is false || !versionTuple.HasValue) return;
|
||||||
|
|
||||||
|
(Version currentVersion, Version newVersion) = versionTuple.Value;
|
||||||
|
|
||||||
if (newVersion <= currentVersion)
|
if (newVersion <= currentVersion)
|
||||||
{
|
{
|
||||||
if (showVersionUpToDate)
|
if (showVersionUpToDate)
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ using LibHac.Tools.FsSystem;
|
|||||||
using LibHac.Tools.FsSystem.NcaUtils;
|
using LibHac.Tools.FsSystem.NcaUtils;
|
||||||
using Ryujinx.Ava.Common.Locale;
|
using Ryujinx.Ava.Common.Locale;
|
||||||
using Ryujinx.Ava.Utilities.Compat;
|
using Ryujinx.Ava.Utilities.Compat;
|
||||||
|
using Ryujinx.Ava.Utilities.PlayReport;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.HLE.FileSystem;
|
using Ryujinx.HLE.FileSystem;
|
||||||
using Ryujinx.HLE.Loaders.Processes.Extensions;
|
using Ryujinx.HLE.Loaders.Processes.Extensions;
|
||||||
@@ -23,7 +24,6 @@ namespace Ryujinx.Ava.Utilities.AppLibrary
|
|||||||
public class ApplicationData
|
public class ApplicationData
|
||||||
{
|
{
|
||||||
public bool Favorite { get; set; }
|
public bool Favorite { get; set; }
|
||||||
public bool UserConfig { get; set; }
|
|
||||||
public byte[] Icon { get; set; }
|
public byte[] Icon { get; set; }
|
||||||
public string Name { get; set; } = "Unknown";
|
public string Name { get; set; } = "Unknown";
|
||||||
|
|
||||||
@@ -36,9 +36,14 @@ namespace Ryujinx.Ava.Utilities.AppLibrary
|
|||||||
{
|
{
|
||||||
_id = value;
|
_id = value;
|
||||||
|
|
||||||
Compatibility = CompatibilityCsv.Find(Id);
|
Compatibility = CompatibilityCsv.Find(value);
|
||||||
|
RichPresenceSpec = PlayReports.Analyzer.TryGetSpec(IdString, out GameSpec gameSpec)
|
||||||
|
? gameSpec
|
||||||
|
: default(Optional<GameSpec>);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public Optional<GameSpec> RichPresenceSpec { get; set; }
|
||||||
|
|
||||||
public string Developer { get; set; } = "Unknown";
|
public string Developer { get; set; } = "Unknown";
|
||||||
public string Version { get; set; } = "0";
|
public string Version { get; set; } = "0";
|
||||||
public int PlayerCount { get; set; }
|
public int PlayerCount { get; set; }
|
||||||
@@ -47,7 +52,7 @@ namespace Ryujinx.Ava.Utilities.AppLibrary
|
|||||||
public bool HasLdnGames => PlayerCount != 0 && GameCount != 0;
|
public bool HasLdnGames => PlayerCount != 0 && GameCount != 0;
|
||||||
|
|
||||||
public bool HasRichPresenceAsset => DiscordIntegrationModule.HasAssetImage(IdString);
|
public bool HasRichPresenceAsset => DiscordIntegrationModule.HasAssetImage(IdString);
|
||||||
public bool HasDynamicRichPresenceSupport => DiscordIntegrationModule.HasAnalyzer(IdString);
|
public bool HasDynamicRichPresenceSupport => RichPresenceSpec.HasValue;
|
||||||
|
|
||||||
public TimeSpan TimePlayed { get; set; }
|
public TimeSpan TimePlayed { get; set; }
|
||||||
public DateTime? LastPlayed { get; set; }
|
public DateTime? LastPlayed { get; set; }
|
||||||
@@ -60,7 +65,7 @@ namespace Ryujinx.Ava.Utilities.AppLibrary
|
|||||||
|
|
||||||
public string TimePlayedString => ValueFormatUtils.FormatTimeSpan(TimePlayed);
|
public string TimePlayedString => ValueFormatUtils.FormatTimeSpan(TimePlayed);
|
||||||
|
|
||||||
public bool HasPlayedPreviously => TimePlayedString != string.Empty;
|
public bool HasPlayedPreviously => TimePlayed.TotalSeconds > 1;
|
||||||
|
|
||||||
public string LastPlayedString => ValueFormatUtils.FormatDateTime(LastPlayed)?.Replace(" ", "\n");
|
public string LastPlayedString => ValueFormatUtils.FormatDateTime(LastPlayed)?.Replace(" ", "\n");
|
||||||
|
|
||||||
@@ -79,28 +84,9 @@ namespace Ryujinx.Ava.Utilities.AppLibrary
|
|||||||
|
|
||||||
public LocaleKeys? PlayabilityStatus => Compatibility.Convert(x => x.Status).OrElse(null);
|
public LocaleKeys? PlayabilityStatus => Compatibility.Convert(x => x.Status).OrElse(null);
|
||||||
|
|
||||||
public string CompatibilityToolTip
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
StringBuilder sb = new(LocalizedStatusTooltip);
|
|
||||||
|
|
||||||
string formattedCompatibilityLabels = FormattedCompatibilityLabels;
|
|
||||||
if (!string.IsNullOrEmpty(formattedCompatibilityLabels))
|
|
||||||
{
|
|
||||||
sb.AppendLine()
|
|
||||||
.AppendLine()
|
|
||||||
.Append(formattedCompatibilityLabels);
|
|
||||||
}
|
|
||||||
|
|
||||||
return sb.ToString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public string LocalizedStatusTooltip =>
|
public string LocalizedStatusTooltip =>
|
||||||
Compatibility.Convert(x =>
|
Compatibility.Convert(x =>
|
||||||
#pragma warning disable CS8509 It is exhaustive for all possible values this can contain.
|
#pragma warning disable CS8509 // It is exhaustive for all possible values this can contain.
|
||||||
LocaleManager.Instance[x.Status switch
|
LocaleManager.Instance[x.Status switch
|
||||||
#pragma warning restore CS8509
|
#pragma warning restore CS8509
|
||||||
{
|
{
|
||||||
@@ -121,16 +107,16 @@ namespace Ryujinx.Ava.Utilities.AppLibrary
|
|||||||
|
|
||||||
public static string GetBuildId(VirtualFileSystem virtualFileSystem, IntegrityCheckLevel checkLevel, string titleFilePath)
|
public static string GetBuildId(VirtualFileSystem virtualFileSystem, IntegrityCheckLevel checkLevel, string titleFilePath)
|
||||||
{
|
{
|
||||||
using FileStream file = new(titleFilePath, FileMode.Open, FileAccess.Read);
|
|
||||||
|
|
||||||
Nca mainNca = null;
|
|
||||||
Nca patchNca = null;
|
|
||||||
|
|
||||||
if (!System.IO.Path.Exists(titleFilePath))
|
if (!System.IO.Path.Exists(titleFilePath))
|
||||||
{
|
{
|
||||||
Logger.Error?.Print(LogClass.Application, $"File \"{titleFilePath}\" does not exist.");
|
Logger.Error?.Print(LogClass.Application, $"File \"{titleFilePath}\" does not exist.");
|
||||||
return string.Empty;
|
return string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
using FileStream file = new(titleFilePath, FileMode.Open, FileAccess.Read);
|
||||||
|
|
||||||
|
Nca mainNca = null;
|
||||||
|
Nca patchNca = null;
|
||||||
|
|
||||||
string extension = System.IO.Path.GetExtension(titleFilePath).ToLower();
|
string extension = System.IO.Path.GetExtension(titleFilePath).ToLower();
|
||||||
|
|
||||||
|
|||||||
@@ -505,7 +505,7 @@ namespace Ryujinx.Ava.Utilities.AppLibrary
|
|||||||
if (data.Id != 0)
|
if (data.Id != 0)
|
||||||
{
|
{
|
||||||
ApplicationMetadata appMetadata = LoadAndSaveMetaData(data.IdString, appMetadata =>
|
ApplicationMetadata appMetadata = LoadAndSaveMetaData(data.IdString, appMetadata =>
|
||||||
{
|
{
|
||||||
appMetadata.Title = data.Name;
|
appMetadata.Title = data.Name;
|
||||||
|
|
||||||
// Only do the migration if time_played has a value and timespan_played hasn't been updated yet.
|
// Only do the migration if time_played has a value and timespan_played hasn't been updated yet.
|
||||||
@@ -529,11 +529,10 @@ namespace Ryujinx.Ava.Utilities.AppLibrary
|
|||||||
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
data.Favorite = appMetadata.Favorite;
|
data.Favorite = appMetadata.Favorite;
|
||||||
data.TimePlayed = appMetadata.TimePlayed;
|
data.TimePlayed = appMetadata.TimePlayed;
|
||||||
data.LastPlayed = appMetadata.LastPlayed;
|
data.LastPlayed = appMetadata.LastPlayed;
|
||||||
data.UserConfig = File.Exists(Program.GetDirGameUserConfig(data.IdBaseString, false, false)); // Just check user config
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data.FileExtension = Path.GetExtension(applicationPath).TrimStart('.').ToUpper();
|
data.FileExtension = Path.GetExtension(applicationPath).TrimStart('.').ToUpper();
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ namespace Ryujinx.Ava.Utilities
|
|||||||
if (string.IsNullOrEmpty(contentPath))
|
if (string.IsNullOrEmpty(contentPath))
|
||||||
goto BadData;
|
goto BadData;
|
||||||
|
|
||||||
appData = new() { Name = Name, Id = ProgramId, Path = GetContentPath(contentManager) };
|
appData = new() { Name = Name, Id = ProgramId, Path = contentPath };
|
||||||
appControl = StructHelpers.CreateCustomNacpData(Name, Version);
|
appControl = StructHelpers.CreateCustomNacpData(Name, Version);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
|||||||
@@ -6,15 +6,11 @@ namespace Ryujinx.Ava.Utilities
|
|||||||
public static class CommandLineState
|
public static class CommandLineState
|
||||||
{
|
{
|
||||||
public static string[] Arguments { get; private set; }
|
public static string[] Arguments { get; private set; }
|
||||||
public static int CountArguments { get; private set; }
|
|
||||||
public static bool? OverrideDockedMode { get; private set; }
|
public static bool? OverrideDockedMode { get; private set; }
|
||||||
public static bool? OverrideHardwareAcceleration { get; private set; }
|
public static bool? OverrideHardwareAcceleration { get; private set; }
|
||||||
public static string OverrideGraphicsBackend { get; private set; }
|
public static string OverrideGraphicsBackend { get; private set; }
|
||||||
public static string OverrideBackendThreading { get; private set; }
|
public static string OverrideBackendThreading { get; private set; }
|
||||||
public static string OverridePPTC { get; private set; }
|
|
||||||
public static string OverrideMemoryManagerMode { get; private set; }
|
|
||||||
public static string OverrideSystemRegion { get; private set; }
|
|
||||||
public static string OverrideSystemLanguage { get; private set; }
|
|
||||||
public static string OverrideHideCursor { get; private set; }
|
public static string OverrideHideCursor { get; private set; }
|
||||||
public static string BaseDirPathArg { get; private set; }
|
public static string BaseDirPathArg { get; private set; }
|
||||||
public static string Profile { get; private set; }
|
public static string Profile { get; private set; }
|
||||||
@@ -23,11 +19,6 @@ namespace Ryujinx.Ava.Utilities
|
|||||||
public static bool StartFullscreenArg { get; private set; }
|
public static bool StartFullscreenArg { get; private set; }
|
||||||
public static bool HideAvailableUpdates { get; private set; }
|
public static bool HideAvailableUpdates { get; private set; }
|
||||||
|
|
||||||
public static void ArgumentsClean()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void ParseArguments(string[] args)
|
public static void ParseArguments(string[] args)
|
||||||
{
|
{
|
||||||
List<string> arguments = [];
|
List<string> arguments = [];
|
||||||
@@ -37,11 +28,6 @@ namespace Ryujinx.Ava.Utilities
|
|||||||
{
|
{
|
||||||
string arg = args[i];
|
string arg = args[i];
|
||||||
|
|
||||||
if (arg.Contains("-") || arg.Contains("--"))
|
|
||||||
{
|
|
||||||
CountArguments++;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (arg)
|
switch (arg)
|
||||||
{
|
{
|
||||||
case "-r":
|
case "-r":
|
||||||
@@ -99,47 +85,6 @@ namespace Ryujinx.Ava.Utilities
|
|||||||
|
|
||||||
OverrideBackendThreading = args[++i];
|
OverrideBackendThreading = args[++i];
|
||||||
break;
|
break;
|
||||||
case "--pptc":
|
|
||||||
if (i + 1 >= args.Length)
|
|
||||||
{
|
|
||||||
Logger.Error?.Print(LogClass.Application, $"Invalid option '{arg}'");
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
OverridePPTC = args[++i];
|
|
||||||
break;
|
|
||||||
case "-m":
|
|
||||||
case "--memory-manager-mode":
|
|
||||||
if (i + 1 >= args.Length)
|
|
||||||
{
|
|
||||||
Logger.Error?.Print(LogClass.Application, $"Invalid option '{arg}'");
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
OverrideMemoryManagerMode = args[++i];
|
|
||||||
break;
|
|
||||||
case "--system-region":
|
|
||||||
if (i + 1 >= args.Length)
|
|
||||||
{
|
|
||||||
Logger.Error?.Print(LogClass.Application, $"Invalid option '{arg}'");
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
OverrideSystemRegion = args[++i];
|
|
||||||
break;
|
|
||||||
case "--system-language":
|
|
||||||
if (i + 1 >= args.Length)
|
|
||||||
{
|
|
||||||
Logger.Error?.Print(LogClass.Application, $"Invalid option '{arg}'");
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
OverrideSystemLanguage = args[++i];
|
|
||||||
break;
|
|
||||||
case "-i":
|
case "-i":
|
||||||
case "--application-id":
|
case "--application-id":
|
||||||
LaunchApplicationId = args[++i];
|
LaunchApplicationId = args[++i];
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The current version of the file format
|
/// The current version of the file format
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const int CurrentVersion = 63;
|
public const int CurrentVersion = 67;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Version of the configuration file format
|
/// Version of the configuration file format
|
||||||
@@ -111,6 +111,11 @@ namespace Ryujinx.Ava.Utilities.Configuration
|
|||||||
/// Enables printing FS access log messages
|
/// Enables printing FS access log messages
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool LoggingEnableFsAccessLog { get; set; }
|
public bool LoggingEnableFsAccessLog { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enables log messages from Avalonia
|
||||||
|
/// </summary>
|
||||||
|
public bool LoggingEnableAvalonia { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Controls which log messages are written to the log targets
|
/// Controls which log messages are written to the log targets
|
||||||
@@ -158,9 +163,19 @@ namespace Ryujinx.Ava.Utilities.Configuration
|
|||||||
public bool EnableDiscordIntegration { get; set; }
|
public bool EnableDiscordIntegration { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Checks for updates when Ryujinx starts when enabled
|
/// DEPRECATED: Checks for updates when Ryujinx starts when enabled
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool CheckUpdatesOnStart { get; set; }
|
public bool CheckUpdatesOnStart { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks for updates when Ryujinx starts when enabled, either prompting when an update is found or just showing a notification.
|
||||||
|
/// </summary>
|
||||||
|
public UpdaterType UpdateCheckerType { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// How the emulator should behave when you click off/on the window.
|
||||||
|
/// </summary>
|
||||||
|
public FocusLostType FocusLostActionType { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Show "Confirm Exit" Dialog
|
/// Show "Confirm Exit" Dialog
|
||||||
@@ -168,7 +183,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
|
|||||||
public bool ShowConfirmExit { get; set; }
|
public bool ShowConfirmExit { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// ignore "Applet" dialog
|
/// Ignore Controller Applet dialog
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IgnoreApplet { get; set; }
|
public bool IgnoreApplet { get; set; }
|
||||||
|
|
||||||
@@ -373,6 +388,11 @@ namespace Ryujinx.Ava.Utilities.Configuration
|
|||||||
/// Enable or disable mouse support (Independent from controllers binding)
|
/// Enable or disable mouse support (Independent from controllers binding)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool EnableMouse { get; set; }
|
public bool EnableMouse { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enable/disable the ability to control Ryujinx when it's not the currently focused window.
|
||||||
|
/// </summary>
|
||||||
|
public bool DisableInputWhenOutOfFocus { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Hotkey Keyboard Bindings
|
/// Hotkey Keyboard Bindings
|
||||||
|
|||||||
@@ -18,15 +18,9 @@ namespace Ryujinx.Ava.Utilities.Configuration
|
|||||||
{
|
{
|
||||||
public partial class ConfigurationState
|
public partial class ConfigurationState
|
||||||
{
|
{
|
||||||
public void Load(ConfigurationFileFormat cff, string configurationFilePath, string gameId="")
|
public void Load(ConfigurationFileFormat cff, string configurationFilePath)
|
||||||
{
|
{
|
||||||
bool configurationFileUpdated = false;
|
bool configurationFileUpdated = false;
|
||||||
bool LoadSetting = true;
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(gameId))
|
|
||||||
{
|
|
||||||
LoadSetting = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cff.Version is < 0 or > ConfigurationFileFormat.CurrentVersion)
|
if (cff.Version is < 0 or > ConfigurationFileFormat.CurrentVersion)
|
||||||
{
|
{
|
||||||
@@ -49,14 +43,16 @@ namespace Ryujinx.Ava.Utilities.Configuration
|
|||||||
configurationFileUpdated = true;
|
configurationFileUpdated = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
EnableDiscordIntegration.Value = LoadSetting ? cff.EnableDiscordIntegration : EnableDiscordIntegration.Value; // Get from global config only
|
EnableDiscordIntegration.Value = cff.EnableDiscordIntegration;
|
||||||
CheckUpdatesOnStart.Value = LoadSetting ? cff.CheckUpdatesOnStart : CheckUpdatesOnStart.Value; // Get from global config only
|
CheckUpdatesOnStart.Value = cff.CheckUpdatesOnStart;
|
||||||
ShowConfirmExit.Value = LoadSetting ? cff.ShowConfirmExit : ShowConfirmExit.Value; // Get from global config only
|
UpdateCheckerType.Value = cff.UpdateCheckerType;
|
||||||
RememberWindowState.Value = LoadSetting ? cff.RememberWindowState : RememberWindowState.Value; // Get from global config only
|
FocusLostActionType.Value = cff.FocusLostActionType;
|
||||||
ShowTitleBar.Value = LoadSetting ? cff.ShowTitleBar : ShowTitleBar.Value; // Get from global config only
|
ShowConfirmExit.Value = cff.ShowConfirmExit;
|
||||||
EnableHardwareAcceleration.Value = LoadSetting ? cff.EnableHardwareAcceleration : EnableHardwareAcceleration.Value; // Get from global config only
|
RememberWindowState.Value = cff.RememberWindowState;
|
||||||
HideCursor.Value = LoadSetting ? cff.HideCursor : HideCursor.Value; // Get from global config only
|
ShowTitleBar.Value = cff.ShowTitleBar;
|
||||||
|
EnableHardwareAcceleration.Value = cff.EnableHardwareAcceleration;
|
||||||
|
HideCursor.Value = cff.HideCursor;
|
||||||
|
|
||||||
Logger.EnableFileLog.Value = cff.EnableFileLog;
|
Logger.EnableFileLog.Value = cff.EnableFileLog;
|
||||||
Logger.EnableDebug.Value = cff.LoggingEnableDebug;
|
Logger.EnableDebug.Value = cff.LoggingEnableDebug;
|
||||||
Logger.EnableStub.Value = cff.LoggingEnableStub;
|
Logger.EnableStub.Value = cff.LoggingEnableStub;
|
||||||
@@ -91,7 +87,8 @@ namespace Ryujinx.Ava.Utilities.Configuration
|
|||||||
System.Language.Value = cff.SystemLanguage;
|
System.Language.Value = cff.SystemLanguage;
|
||||||
System.Region.Value = cff.SystemRegion;
|
System.Region.Value = cff.SystemRegion;
|
||||||
System.TimeZone.Value = cff.SystemTimeZone;
|
System.TimeZone.Value = cff.SystemTimeZone;
|
||||||
System.SystemTimeOffset.Value = LoadSetting ? cff.SystemTimeOffset : System.SystemTimeOffset.Value; // Get from global config only
|
System.SystemTimeOffset.Value = cff.SystemTimeOffset;
|
||||||
|
System.MatchSystemTime.Value = cff.MatchSystemTime;
|
||||||
System.EnableDockedMode.Value = cff.DockedMode;
|
System.EnableDockedMode.Value = cff.DockedMode;
|
||||||
System.EnablePtc.Value = cff.EnablePtc;
|
System.EnablePtc.Value = cff.EnablePtc;
|
||||||
System.EnableLowPowerPtc.Value = cff.EnableLowPowerPtc;
|
System.EnableLowPowerPtc.Value = cff.EnableLowPowerPtc;
|
||||||
@@ -103,49 +100,49 @@ namespace Ryujinx.Ava.Utilities.Configuration
|
|||||||
System.MemoryManagerMode.Value = cff.MemoryManagerMode;
|
System.MemoryManagerMode.Value = cff.MemoryManagerMode;
|
||||||
System.DramSize.Value = cff.DramSize;
|
System.DramSize.Value = cff.DramSize;
|
||||||
System.IgnoreMissingServices.Value = cff.IgnoreMissingServices;
|
System.IgnoreMissingServices.Value = cff.IgnoreMissingServices;
|
||||||
System.IgnoreApplet.Value = cff.IgnoreApplet;
|
System.IgnoreControllerApplet.Value = cff.IgnoreApplet;
|
||||||
System.UseHypervisor.Value = cff.UseHypervisor;
|
System.UseHypervisor.Value = cff.UseHypervisor;
|
||||||
|
|
||||||
UI.GuiColumns.FavColumn.Value = LoadSetting ? cff.GuiColumns.FavColumn : UI.GuiColumns.FavColumn.Value;
|
UI.GuiColumns.FavColumn.Value = cff.GuiColumns.FavColumn;
|
||||||
UI.GuiColumns.IconColumn.Value = LoadSetting ? cff.GuiColumns.IconColumn : UI.GuiColumns.IconColumn.Value;
|
UI.GuiColumns.IconColumn.Value = cff.GuiColumns.IconColumn;
|
||||||
UI.GuiColumns.AppColumn.Value = LoadSetting ? cff.GuiColumns.AppColumn : UI.GuiColumns.AppColumn.Value;
|
UI.GuiColumns.AppColumn.Value = cff.GuiColumns.AppColumn;
|
||||||
UI.GuiColumns.DevColumn.Value = LoadSetting ? cff.GuiColumns.DevColumn : UI.GuiColumns.DevColumn.Value;
|
UI.GuiColumns.DevColumn.Value = cff.GuiColumns.DevColumn;
|
||||||
UI.GuiColumns.VersionColumn.Value = LoadSetting ? cff.GuiColumns.VersionColumn : UI.GuiColumns.VersionColumn.Value;
|
UI.GuiColumns.VersionColumn.Value = cff.GuiColumns.VersionColumn;
|
||||||
UI.GuiColumns.TimePlayedColumn.Value = LoadSetting ? cff.GuiColumns.TimePlayedColumn : UI.GuiColumns.TimePlayedColumn.Value;
|
UI.GuiColumns.TimePlayedColumn.Value = cff.GuiColumns.TimePlayedColumn;
|
||||||
UI.GuiColumns.LastPlayedColumn.Value = LoadSetting ? cff.GuiColumns.LastPlayedColumn : UI.GuiColumns.LastPlayedColumn.Value;
|
UI.GuiColumns.LastPlayedColumn.Value = cff.GuiColumns.LastPlayedColumn;
|
||||||
UI.GuiColumns.FileExtColumn.Value = LoadSetting ? cff.GuiColumns.FileExtColumn : UI.GuiColumns.FileExtColumn.Value;
|
UI.GuiColumns.FileExtColumn.Value = cff.GuiColumns.FileExtColumn;
|
||||||
UI.GuiColumns.FileSizeColumn.Value = LoadSetting ? cff.GuiColumns.FileSizeColumn : UI.GuiColumns.FileSizeColumn.Value;
|
UI.GuiColumns.FileSizeColumn.Value = cff.GuiColumns.FileSizeColumn;
|
||||||
UI.GuiColumns.PathColumn.Value = LoadSetting ? cff.GuiColumns.PathColumn : UI.GuiColumns.PathColumn.Value;
|
UI.GuiColumns.PathColumn.Value = cff.GuiColumns.PathColumn;
|
||||||
UI.ColumnSort.SortColumnId.Value = LoadSetting ? cff.ColumnSort.SortColumnId : UI.ColumnSort.SortColumnId.Value;
|
UI.ColumnSort.SortColumnId.Value = cff.ColumnSort.SortColumnId;
|
||||||
UI.ColumnSort.SortAscending.Value = LoadSetting ? cff.ColumnSort.SortAscending : UI.ColumnSort.SortAscending.Value;
|
UI.ColumnSort.SortAscending.Value = cff.ColumnSort.SortAscending;
|
||||||
UI.GameDirs.Value = LoadSetting ? cff.GameDirs : UI.GameDirs.Value;
|
UI.GameDirs.Value = cff.GameDirs;
|
||||||
UI.AutoloadDirs.Value = LoadSetting ? (cff.AutoloadDirs ?? []) : UI.AutoloadDirs.Value;
|
UI.AutoloadDirs.Value = cff.AutoloadDirs ?? [];
|
||||||
UI.ShownFileTypes.NSP.Value = LoadSetting ? cff.ShownFileTypes.NSP : UI.ShownFileTypes.NSP.Value;
|
UI.ShownFileTypes.NSP.Value = cff.ShownFileTypes.NSP;
|
||||||
UI.ShownFileTypes.PFS0.Value = LoadSetting ? cff.ShownFileTypes.PFS0 : UI.ShownFileTypes.PFS0.Value;
|
UI.ShownFileTypes.PFS0.Value = cff.ShownFileTypes.PFS0;
|
||||||
UI.ShownFileTypes.XCI.Value = LoadSetting ? cff.ShownFileTypes.XCI : UI.ShownFileTypes.XCI.Value;
|
UI.ShownFileTypes.XCI.Value = cff.ShownFileTypes.XCI;
|
||||||
UI.ShownFileTypes.NCA.Value = LoadSetting ? cff.ShownFileTypes.NCA : UI.ShownFileTypes.NCA.Value;
|
UI.ShownFileTypes.NCA.Value = cff.ShownFileTypes.NCA;
|
||||||
UI.ShownFileTypes.NRO.Value = LoadSetting ? cff.ShownFileTypes.NRO : UI.ShownFileTypes.NRO.Value;
|
UI.ShownFileTypes.NRO.Value = cff.ShownFileTypes.NRO;
|
||||||
UI.ShownFileTypes.NSO.Value = LoadSetting ? cff.ShownFileTypes.NSO : UI.ShownFileTypes.NSO.Value;
|
UI.ShownFileTypes.NSO.Value = cff.ShownFileTypes.NSO;
|
||||||
UI.LanguageCode.Value = LoadSetting ? cff.LanguageCode : UI.LanguageCode.Value;
|
UI.LanguageCode.Value = cff.LanguageCode;
|
||||||
UI.BaseStyle.Value = LoadSetting ? cff.BaseStyle : UI.BaseStyle.Value;
|
UI.BaseStyle.Value = cff.BaseStyle;
|
||||||
UI.GameListViewMode.Value = LoadSetting ? cff.GameListViewMode : UI.GameListViewMode.Value;
|
UI.GameListViewMode.Value = cff.GameListViewMode;
|
||||||
UI.ShowNames.Value = LoadSetting ? cff.ShowNames : UI.ShowNames.Value;
|
UI.ShowNames.Value = cff.ShowNames;
|
||||||
UI.IsAscendingOrder.Value = LoadSetting ? cff.IsAscendingOrder : UI.IsAscendingOrder.Value;
|
UI.IsAscendingOrder.Value = cff.IsAscendingOrder;
|
||||||
UI.GridSize.Value = LoadSetting ? cff.GridSize : UI.GridSize.Value;
|
UI.GridSize.Value = cff.GridSize;
|
||||||
UI.ApplicationSort.Value = LoadSetting ? cff.ApplicationSort : UI.ApplicationSort.Value;
|
UI.ApplicationSort.Value = cff.ApplicationSort;
|
||||||
UI.StartFullscreen.Value = LoadSetting ? cff.StartFullscreen : UI.StartFullscreen.Value;
|
UI.StartFullscreen.Value = cff.StartFullscreen;
|
||||||
UI.StartNoUI.Value = LoadSetting ? cff.StartNoUI : UI.StartNoUI.Value;
|
UI.StartNoUI.Value = cff.StartNoUI;
|
||||||
UI.ShowConsole.Value = LoadSetting ? cff.ShowConsole : UI.ShowConsole.Value;
|
UI.ShowConsole.Value = cff.ShowConsole;
|
||||||
UI.WindowStartup.WindowSizeWidth.Value = LoadSetting ? cff.WindowStartup.WindowSizeWidth : UI.WindowStartup.WindowSizeWidth.Value;
|
UI.WindowStartup.WindowSizeWidth.Value = cff.WindowStartup.WindowSizeWidth;
|
||||||
UI.WindowStartup.WindowSizeHeight.Value = LoadSetting ? cff.WindowStartup.WindowSizeHeight : UI.WindowStartup.WindowSizeHeight.Value;
|
UI.WindowStartup.WindowSizeHeight.Value = cff.WindowStartup.WindowSizeHeight;
|
||||||
UI.WindowStartup.WindowPositionX.Value = LoadSetting ? cff.WindowStartup.WindowPositionX : UI.WindowStartup.WindowPositionX.Value;
|
UI.WindowStartup.WindowPositionX.Value = cff.WindowStartup.WindowPositionX;
|
||||||
UI.WindowStartup.WindowPositionY.Value = LoadSetting ? cff.WindowStartup.WindowPositionY : UI.WindowStartup.WindowPositionY.Value;
|
UI.WindowStartup.WindowPositionY.Value = cff.WindowStartup.WindowPositionY;
|
||||||
UI.WindowStartup.WindowMaximized.Value = LoadSetting ? cff.WindowStartup.WindowMaximized : UI.WindowStartup.WindowMaximized.Value;
|
UI.WindowStartup.WindowMaximized.Value = cff.WindowStartup.WindowMaximized;
|
||||||
|
|
||||||
|
|
||||||
Hid.EnableKeyboard.Value = cff.EnableKeyboard;
|
Hid.EnableKeyboard.Value = cff.EnableKeyboard;
|
||||||
Hid.EnableMouse.Value = cff.EnableMouse;
|
Hid.EnableMouse.Value = cff.EnableMouse;
|
||||||
Hid.Hotkeys.Value = LoadSetting ? cff.Hotkeys : Hid.Hotkeys.Value; // Get from global config only
|
Hid.DisableInputWhenOutOfFocus.Value = cff.DisableInputWhenOutOfFocus;
|
||||||
|
Hid.Hotkeys.Value = cff.Hotkeys;
|
||||||
Hid.InputConfig.Value = cff.InputConfig ?? [];
|
Hid.InputConfig.Value = cff.InputConfig ?? [];
|
||||||
Hid.RainbowSpeed.Value = cff.RainbowSpeed;
|
Hid.RainbowSpeed.Value = cff.RainbowSpeed;
|
||||||
|
|
||||||
@@ -437,7 +434,11 @@ namespace Ryujinx.Ava.Utilities.Configuration
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
(62, static cff => cff.RainbowSpeed = 1f),
|
(62, static cff => cff.RainbowSpeed = 1f),
|
||||||
(63, static cff => cff.MatchSystemTime = false)
|
(63, static cff => cff.MatchSystemTime = false),
|
||||||
|
(64, static cff => cff.LoggingEnableAvalonia = false),
|
||||||
|
(65, static cff => cff.UpdateCheckerType = cff.CheckUpdatesOnStart ? UpdaterType.PromptAtStartup : UpdaterType.Off),
|
||||||
|
(66, static cff => cff.DisableInputWhenOutOfFocus = false),
|
||||||
|
(67, static cff => cff.FocusLostActionType = cff.DisableInputWhenOutOfFocus ? FocusLostType.BlockInput : FocusLostType.DoNothing)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using ARMeilleure;
|
using ARMeilleure;
|
||||||
using Gommon;
|
using Gommon;
|
||||||
using Ryujinx.Ava.Utilities.Configuration.System;
|
using Ryujinx.Ava.Utilities.Configuration.System;
|
||||||
|
using Ryujinx.Ava.Utilities.Configuration.UI;
|
||||||
using Ryujinx.Common;
|
using Ryujinx.Common;
|
||||||
using Ryujinx.Common.Configuration;
|
using Ryujinx.Common.Configuration;
|
||||||
using Ryujinx.Common.Configuration.Hid;
|
using Ryujinx.Common.Configuration.Hid;
|
||||||
@@ -254,6 +255,11 @@ namespace Ryujinx.Ava.Utilities.Configuration
|
|||||||
/// Enables printing FS access log messages
|
/// Enables printing FS access log messages
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ReactiveObject<bool> EnableFsAccessLog { get; private set; }
|
public ReactiveObject<bool> EnableFsAccessLog { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enables log messages from Avalonia
|
||||||
|
/// </summary>
|
||||||
|
public ReactiveObject<bool> EnableAvaloniaLog { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Controls which log messages are written to the log targets
|
/// Controls which log messages are written to the log targets
|
||||||
@@ -281,6 +287,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
|
|||||||
EnableTrace = new ReactiveObject<bool>();
|
EnableTrace = new ReactiveObject<bool>();
|
||||||
EnableGuest = new ReactiveObject<bool>();
|
EnableGuest = new ReactiveObject<bool>();
|
||||||
EnableFsAccessLog = new ReactiveObject<bool>();
|
EnableFsAccessLog = new ReactiveObject<bool>();
|
||||||
|
EnableAvaloniaLog = new ReactiveObject<bool>();
|
||||||
FilteredClasses = new ReactiveObject<LogClass[]>();
|
FilteredClasses = new ReactiveObject<LogClass[]>();
|
||||||
EnableFileLog = new ReactiveObject<bool>();
|
EnableFileLog = new ReactiveObject<bool>();
|
||||||
EnableFileLog.LogChangesToValue(nameof(EnableFileLog));
|
EnableFileLog.LogChangesToValue(nameof(EnableFileLog));
|
||||||
@@ -376,7 +383,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Ignore Controller Applet
|
/// Ignore Controller Applet
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ReactiveObject<bool> IgnoreApplet { get; private set; }
|
public ReactiveObject<bool> IgnoreControllerApplet { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Uses Hypervisor over JIT if available
|
/// Uses Hypervisor over JIT if available
|
||||||
@@ -417,8 +424,8 @@ namespace Ryujinx.Ava.Utilities.Configuration
|
|||||||
DramSize.LogChangesToValue(nameof(DramSize));
|
DramSize.LogChangesToValue(nameof(DramSize));
|
||||||
IgnoreMissingServices = new ReactiveObject<bool>();
|
IgnoreMissingServices = new ReactiveObject<bool>();
|
||||||
IgnoreMissingServices.LogChangesToValue(nameof(IgnoreMissingServices));
|
IgnoreMissingServices.LogChangesToValue(nameof(IgnoreMissingServices));
|
||||||
IgnoreApplet = new ReactiveObject<bool>();
|
IgnoreControllerApplet = new ReactiveObject<bool>();
|
||||||
IgnoreApplet.LogChangesToValue(nameof(IgnoreApplet));
|
IgnoreControllerApplet.LogChangesToValue(nameof(IgnoreControllerApplet));
|
||||||
AudioVolume = new ReactiveObject<float>();
|
AudioVolume = new ReactiveObject<float>();
|
||||||
AudioVolume.LogChangesToValue(nameof(AudioVolume));
|
AudioVolume.LogChangesToValue(nameof(AudioVolume));
|
||||||
UseHypervisor = new ReactiveObject<bool>();
|
UseHypervisor = new ReactiveObject<bool>();
|
||||||
@@ -440,6 +447,11 @@ namespace Ryujinx.Ava.Utilities.Configuration
|
|||||||
/// Enable or disable mouse support (Independent from controllers binding)
|
/// Enable or disable mouse support (Independent from controllers binding)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ReactiveObject<bool> EnableMouse { get; private set; }
|
public ReactiveObject<bool> EnableMouse { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enable/disable the ability to control Ryujinx when it's not the currently focused window.
|
||||||
|
/// </summary>
|
||||||
|
public ReactiveObject<bool> DisableInputWhenOutOfFocus { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Hotkey Keyboard Bindings
|
/// Hotkey Keyboard Bindings
|
||||||
@@ -462,6 +474,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
|
|||||||
{
|
{
|
||||||
EnableKeyboard = new ReactiveObject<bool>();
|
EnableKeyboard = new ReactiveObject<bool>();
|
||||||
EnableMouse = new ReactiveObject<bool>();
|
EnableMouse = new ReactiveObject<bool>();
|
||||||
|
DisableInputWhenOutOfFocus = new ReactiveObject<bool>();
|
||||||
Hotkeys = new ReactiveObject<KeyboardHotkeys>();
|
Hotkeys = new ReactiveObject<KeyboardHotkeys>();
|
||||||
InputConfig = new ReactiveObject<List<InputConfig>>();
|
InputConfig = new ReactiveObject<List<InputConfig>>();
|
||||||
RainbowSpeed = new ReactiveObject<float>();
|
RainbowSpeed = new ReactiveObject<float>();
|
||||||
@@ -761,6 +774,16 @@ namespace Ryujinx.Ava.Utilities.Configuration
|
|||||||
/// Checks for updates when Ryujinx starts when enabled
|
/// Checks for updates when Ryujinx starts when enabled
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ReactiveObject<bool> CheckUpdatesOnStart { get; private set; }
|
public ReactiveObject<bool> CheckUpdatesOnStart { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks for updates when Ryujinx starts when enabled, either prompting when an update is found or just showing a notification.
|
||||||
|
/// </summary>
|
||||||
|
public ReactiveObject<UpdaterType> UpdateCheckerType { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// How the emulator should behave when you click off/on the window.
|
||||||
|
/// </summary>
|
||||||
|
public ReactiveObject<FocusLostType> FocusLostActionType { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Show "Confirm Exit" Dialog
|
/// Show "Confirm Exit" Dialog
|
||||||
@@ -798,6 +821,8 @@ namespace Ryujinx.Ava.Utilities.Configuration
|
|||||||
Hacks = new HacksSection();
|
Hacks = new HacksSection();
|
||||||
EnableDiscordIntegration = new ReactiveObject<bool>();
|
EnableDiscordIntegration = new ReactiveObject<bool>();
|
||||||
CheckUpdatesOnStart = new ReactiveObject<bool>();
|
CheckUpdatesOnStart = new ReactiveObject<bool>();
|
||||||
|
UpdateCheckerType = new ReactiveObject<UpdaterType>();
|
||||||
|
FocusLostActionType = new ReactiveObject<FocusLostType>();
|
||||||
ShowConfirmExit = new ReactiveObject<bool>();
|
ShowConfirmExit = new ReactiveObject<bool>();
|
||||||
RememberWindowState = new ReactiveObject<bool>();
|
RememberWindowState = new ReactiveObject<bool>();
|
||||||
ShowTitleBar = new ReactiveObject<bool>();
|
ShowTitleBar = new ReactiveObject<bool>();
|
||||||
|
|||||||
@@ -46,15 +46,19 @@ namespace Ryujinx.Ava.Utilities.Configuration
|
|||||||
LoggingEnableTrace = Logger.EnableTrace,
|
LoggingEnableTrace = Logger.EnableTrace,
|
||||||
LoggingEnableGuest = Logger.EnableGuest,
|
LoggingEnableGuest = Logger.EnableGuest,
|
||||||
LoggingEnableFsAccessLog = Logger.EnableFsAccessLog,
|
LoggingEnableFsAccessLog = Logger.EnableFsAccessLog,
|
||||||
|
LoggingEnableAvalonia = Logger.EnableAvaloniaLog,
|
||||||
LoggingFilteredClasses = Logger.FilteredClasses,
|
LoggingFilteredClasses = Logger.FilteredClasses,
|
||||||
LoggingGraphicsDebugLevel = Logger.GraphicsDebugLevel,
|
LoggingGraphicsDebugLevel = Logger.GraphicsDebugLevel,
|
||||||
SystemLanguage = System.Language,
|
SystemLanguage = System.Language,
|
||||||
SystemRegion = System.Region,
|
SystemRegion = System.Region,
|
||||||
SystemTimeZone = System.TimeZone,
|
SystemTimeZone = System.TimeZone,
|
||||||
SystemTimeOffset = System.SystemTimeOffset,
|
SystemTimeOffset = System.SystemTimeOffset,
|
||||||
|
MatchSystemTime = System.MatchSystemTime,
|
||||||
DockedMode = System.EnableDockedMode,
|
DockedMode = System.EnableDockedMode,
|
||||||
EnableDiscordIntegration = EnableDiscordIntegration,
|
EnableDiscordIntegration = EnableDiscordIntegration,
|
||||||
CheckUpdatesOnStart = CheckUpdatesOnStart,
|
CheckUpdatesOnStart = CheckUpdatesOnStart,
|
||||||
|
UpdateCheckerType = UpdateCheckerType,
|
||||||
|
FocusLostActionType = FocusLostActionType,
|
||||||
ShowConfirmExit = ShowConfirmExit,
|
ShowConfirmExit = ShowConfirmExit,
|
||||||
RememberWindowState = RememberWindowState,
|
RememberWindowState = RememberWindowState,
|
||||||
ShowTitleBar = ShowTitleBar,
|
ShowTitleBar = ShowTitleBar,
|
||||||
@@ -77,7 +81,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
|
|||||||
MemoryManagerMode = System.MemoryManagerMode,
|
MemoryManagerMode = System.MemoryManagerMode,
|
||||||
DramSize = System.DramSize,
|
DramSize = System.DramSize,
|
||||||
IgnoreMissingServices = System.IgnoreMissingServices,
|
IgnoreMissingServices = System.IgnoreMissingServices,
|
||||||
IgnoreApplet = System.IgnoreApplet,
|
IgnoreApplet = System.IgnoreControllerApplet,
|
||||||
UseHypervisor = System.UseHypervisor,
|
UseHypervisor = System.UseHypervisor,
|
||||||
GuiColumns = new GuiColumns
|
GuiColumns = new GuiColumns
|
||||||
{
|
{
|
||||||
@@ -129,6 +133,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
|
|||||||
ShowConsole = UI.ShowConsole,
|
ShowConsole = UI.ShowConsole,
|
||||||
EnableKeyboard = Hid.EnableKeyboard,
|
EnableKeyboard = Hid.EnableKeyboard,
|
||||||
EnableMouse = Hid.EnableMouse,
|
EnableMouse = Hid.EnableMouse,
|
||||||
|
DisableInputWhenOutOfFocus = Hid.DisableInputWhenOutOfFocus,
|
||||||
Hotkeys = Hid.Hotkeys,
|
Hotkeys = Hid.Hotkeys,
|
||||||
InputConfig = Hid.InputConfig,
|
InputConfig = Hid.InputConfig,
|
||||||
RainbowSpeed = Hid.RainbowSpeed,
|
RainbowSpeed = Hid.RainbowSpeed,
|
||||||
@@ -165,6 +170,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
|
|||||||
Logger.EnableTrace.Value = false;
|
Logger.EnableTrace.Value = false;
|
||||||
Logger.EnableGuest.Value = true;
|
Logger.EnableGuest.Value = true;
|
||||||
Logger.EnableFsAccessLog.Value = false;
|
Logger.EnableFsAccessLog.Value = false;
|
||||||
|
Logger.EnableAvaloniaLog.Value = false;
|
||||||
Logger.FilteredClasses.Value = [];
|
Logger.FilteredClasses.Value = [];
|
||||||
Logger.GraphicsDebugLevel.Value = GraphicsDebugLevel.None;
|
Logger.GraphicsDebugLevel.Value = GraphicsDebugLevel.None;
|
||||||
System.Language.Value = Language.AmericanEnglish;
|
System.Language.Value = Language.AmericanEnglish;
|
||||||
@@ -173,7 +179,8 @@ namespace Ryujinx.Ava.Utilities.Configuration
|
|||||||
System.SystemTimeOffset.Value = 0;
|
System.SystemTimeOffset.Value = 0;
|
||||||
System.EnableDockedMode.Value = true;
|
System.EnableDockedMode.Value = true;
|
||||||
EnableDiscordIntegration.Value = true;
|
EnableDiscordIntegration.Value = true;
|
||||||
CheckUpdatesOnStart.Value = true;
|
UpdateCheckerType.Value = UpdaterType.PromptAtStartup;
|
||||||
|
FocusLostActionType.Value = FocusLostType.DoNothing;
|
||||||
ShowConfirmExit.Value = true;
|
ShowConfirmExit.Value = true;
|
||||||
RememberWindowState.Value = true;
|
RememberWindowState.Value = true;
|
||||||
ShowTitleBar.Value = !OperatingSystem.IsWindows();
|
ShowTitleBar.Value = !OperatingSystem.IsWindows();
|
||||||
@@ -198,7 +205,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
|
|||||||
System.MemoryManagerMode.Value = MemoryManagerMode.HostMappedUnsafe;
|
System.MemoryManagerMode.Value = MemoryManagerMode.HostMappedUnsafe;
|
||||||
System.DramSize.Value = MemoryConfiguration.MemoryConfiguration4GiB;
|
System.DramSize.Value = MemoryConfiguration.MemoryConfiguration4GiB;
|
||||||
System.IgnoreMissingServices.Value = false;
|
System.IgnoreMissingServices.Value = false;
|
||||||
System.IgnoreApplet.Value = false;
|
System.IgnoreControllerApplet.Value = false;
|
||||||
System.UseHypervisor.Value = true;
|
System.UseHypervisor.Value = true;
|
||||||
Multiplayer.LanInterfaceId.Value = "0";
|
Multiplayer.LanInterfaceId.Value = "0";
|
||||||
Multiplayer.Mode.Value = MultiplayerMode.Disabled;
|
Multiplayer.Mode.Value = MultiplayerMode.Disabled;
|
||||||
@@ -242,6 +249,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
|
|||||||
UI.WindowStartup.WindowMaximized.Value = false;
|
UI.WindowStartup.WindowMaximized.Value = false;
|
||||||
Hid.EnableKeyboard.Value = false;
|
Hid.EnableKeyboard.Value = false;
|
||||||
Hid.EnableMouse.Value = false;
|
Hid.EnableMouse.Value = false;
|
||||||
|
Hid.DisableInputWhenOutOfFocus.Value = false;
|
||||||
Hid.Hotkeys.Value = new KeyboardHotkeys
|
Hid.Hotkeys.Value = new KeyboardHotkeys
|
||||||
{
|
{
|
||||||
ToggleVSyncMode = Key.F1,
|
ToggleVSyncMode = Key.F1,
|
||||||
|
|||||||
15
src/Ryujinx/Utilities/Configuration/UI/FocusLostType.cs
Normal file
15
src/Ryujinx/Utilities/Configuration/UI/FocusLostType.cs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
using Ryujinx.Common.Utilities;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ava.Utilities.Configuration.UI
|
||||||
|
{
|
||||||
|
[JsonConverter(typeof(TypedStringEnumConverter<FocusLostType>))]
|
||||||
|
public enum FocusLostType
|
||||||
|
{
|
||||||
|
DoNothing,
|
||||||
|
BlockInput,
|
||||||
|
MuteAudio,
|
||||||
|
BlockInputAndMuteAudio,
|
||||||
|
PauseEmulation
|
||||||
|
}
|
||||||
|
}
|
||||||
13
src/Ryujinx/Utilities/Configuration/UI/UpdaterType.cs
Normal file
13
src/Ryujinx/Utilities/Configuration/UI/UpdaterType.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
using Ryujinx.Common.Utilities;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ava.Utilities.Configuration.UI
|
||||||
|
{
|
||||||
|
[JsonConverter(typeof(TypedStringEnumConverter<UpdaterType>))]
|
||||||
|
public enum UpdaterType
|
||||||
|
{
|
||||||
|
Off,
|
||||||
|
PromptAtStartup,
|
||||||
|
CheckInBackground
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
using Gommon;
|
using Gommon;
|
||||||
using MsgPack;
|
|
||||||
using Ryujinx.Ava.Utilities.AppLibrary;
|
using Ryujinx.Ava.Utilities.AppLibrary;
|
||||||
|
using Ryujinx.Common.Logging;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
@@ -20,6 +20,11 @@ namespace Ryujinx.Ava.Utilities.PlayReport
|
|||||||
|
|
||||||
public IReadOnlyList<GameSpec> Specs => new ReadOnlyCollection<GameSpec>(_specs);
|
public IReadOnlyList<GameSpec> Specs => new ReadOnlyCollection<GameSpec>(_specs);
|
||||||
|
|
||||||
|
public GameSpec GetSpec(string titleId) => _specs.First(x => x.TitleIds.ContainsIgnoreCase(titleId));
|
||||||
|
|
||||||
|
public bool TryGetSpec(string titleId, out GameSpec gameSpec)
|
||||||
|
=> (gameSpec = _specs.FirstOrDefault(x => x.TitleIds.ContainsIgnoreCase(titleId))) != null;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Add an analysis spec matching a specific game by title ID, with the provided spec configuration.
|
/// Add an analysis spec matching a specific game by title ID, with the provided spec configuration.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -28,10 +33,12 @@ namespace Ryujinx.Ava.Utilities.PlayReport
|
|||||||
/// <returns>The current <see cref="Analyzer"/>, for chaining convenience.</returns>
|
/// <returns>The current <see cref="Analyzer"/>, for chaining convenience.</returns>
|
||||||
public Analyzer AddSpec(string titleId, Func<GameSpec, GameSpec> transform)
|
public Analyzer AddSpec(string titleId, Func<GameSpec, GameSpec> transform)
|
||||||
{
|
{
|
||||||
Guard.Ensure(ulong.TryParse(titleId, NumberStyles.HexNumber, null, out _),
|
if (ulong.TryParse(titleId, NumberStyles.HexNumber, null, out _))
|
||||||
$"Cannot use a non-hexadecimal string as the Title ID for a {nameof(GameSpec)}.");
|
return AddSpec(transform(GameSpec.Create(titleId)));
|
||||||
|
|
||||||
return AddSpec(transform(GameSpec.Create(titleId)));
|
Logger.Notice.PrintMsg(LogClass.Application,
|
||||||
|
$"Tried to add a {nameof(GameSpec)} with a non-hexadecimal title ID value. Input: '{titleId}'");
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -42,10 +49,12 @@ namespace Ryujinx.Ava.Utilities.PlayReport
|
|||||||
/// <returns>The current <see cref="Analyzer"/>, for chaining convenience.</returns>
|
/// <returns>The current <see cref="Analyzer"/>, for chaining convenience.</returns>
|
||||||
public Analyzer AddSpec(string titleId, Action<GameSpec> transform)
|
public Analyzer AddSpec(string titleId, Action<GameSpec> transform)
|
||||||
{
|
{
|
||||||
Guard.Ensure(ulong.TryParse(titleId, NumberStyles.HexNumber, null, out _),
|
if (ulong.TryParse(titleId, NumberStyles.HexNumber, null, out _))
|
||||||
$"Cannot use a non-hexadecimal string as the Title ID for a {nameof(GameSpec)}.");
|
return AddSpec(GameSpec.Create(titleId).Apply(transform));
|
||||||
|
|
||||||
return AddSpec(GameSpec.Create(titleId).Apply(transform));
|
Logger.Notice.PrintMsg(LogClass.Application,
|
||||||
|
$"Tried to add a {nameof(GameSpec)} with a non-hexadecimal title ID value. Input: '{titleId}'");
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -58,10 +67,19 @@ namespace Ryujinx.Ava.Utilities.PlayReport
|
|||||||
Func<GameSpec, GameSpec> transform)
|
Func<GameSpec, GameSpec> transform)
|
||||||
{
|
{
|
||||||
string[] tids = titleIds.ToArray();
|
string[] tids = titleIds.ToArray();
|
||||||
Guard.Ensure(tids.All(x => ulong.TryParse(x, NumberStyles.HexNumber, null, out _)),
|
if (tids.All(x => ulong.TryParse(x, NumberStyles.HexNumber, null, out _) && !string.IsNullOrEmpty(x)))
|
||||||
$"Cannot use a non-hexadecimal string as the Title ID for a {nameof(GameSpec)}.");
|
return AddSpec(transform(GameSpec.Create(tids)));
|
||||||
|
|
||||||
return AddSpec(transform(GameSpec.Create(tids)));
|
Logger.Notice.PrintMsg(LogClass.Application,
|
||||||
|
$"Tried to add a {nameof(GameSpec)} with a non-hexadecimal title ID value. Input: '{
|
||||||
|
tids.FormatCollection(
|
||||||
|
x => x,
|
||||||
|
separator: ", ",
|
||||||
|
prefix: "[",
|
||||||
|
suffix: "]"
|
||||||
|
)
|
||||||
|
}'");
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -73,12 +91,21 @@ namespace Ryujinx.Ava.Utilities.PlayReport
|
|||||||
public Analyzer AddSpec(IEnumerable<string> titleIds, Action<GameSpec> transform)
|
public Analyzer AddSpec(IEnumerable<string> titleIds, Action<GameSpec> transform)
|
||||||
{
|
{
|
||||||
string[] tids = titleIds.ToArray();
|
string[] tids = titleIds.ToArray();
|
||||||
Guard.Ensure(tids.All(x => ulong.TryParse(x, NumberStyles.HexNumber, null, out _)),
|
if (tids.All(x => ulong.TryParse(x, NumberStyles.HexNumber, null, out _) && !string.IsNullOrEmpty(x)))
|
||||||
$"Cannot use a non-hexadecimal string as the Title ID for a {nameof(GameSpec)}.");
|
return AddSpec(GameSpec.Create(tids).Apply(transform));
|
||||||
|
|
||||||
return AddSpec(GameSpec.Create(tids).Apply(transform));
|
Logger.Notice.PrintMsg(LogClass.Application,
|
||||||
|
$"Tried to add a {nameof(GameSpec)} with a non-hexadecimal title ID value. Input: '{
|
||||||
|
tids.FormatCollection(
|
||||||
|
x => x,
|
||||||
|
separator: ", ",
|
||||||
|
prefix: "[",
|
||||||
|
suffix: "]"
|
||||||
|
)
|
||||||
|
}'");
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Add an analysis spec matching a specific game by title ID, with the provided pre-configured spec.
|
/// Add an analysis spec matching a specific game by title ID, with the provided pre-configured spec.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -106,13 +133,13 @@ namespace Ryujinx.Ava.Utilities.PlayReport
|
|||||||
{
|
{
|
||||||
if (!playReport.ReportData.IsDictionary)
|
if (!playReport.ReportData.IsDictionary)
|
||||||
return FormattedValue.Unhandled;
|
return FormattedValue.Unhandled;
|
||||||
|
|
||||||
if (!_specs.TryGetFirst(s => runningGameId.EqualsAnyIgnoreCase(s.TitleIds), out GameSpec spec))
|
if (!TryGetSpec(runningGameId, out GameSpec spec))
|
||||||
return FormattedValue.Unhandled;
|
return FormattedValue.Unhandled;
|
||||||
|
|
||||||
foreach (FormatterSpecBase formatSpec in spec.ValueFormatters.OrderBy(x => x.Priority))
|
foreach (FormatterSpecBase formatSpec in spec.ValueFormatters.OrderBy(x => x.Priority))
|
||||||
{
|
{
|
||||||
if (!formatSpec.Format(appMeta, playReport, out FormattedValue value))
|
if (!formatSpec.TryFormat(appMeta, playReport, out FormattedValue value))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
using MsgPack;
|
using MsgPack;
|
||||||
using Ryujinx.Ava.Utilities.AppLibrary;
|
using Ryujinx.Ava.Utilities.AppLibrary;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace Ryujinx.Ava.Utilities.PlayReport
|
namespace Ryujinx.Ava.Utilities.PlayReport
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
using System;
|
using Gommon;
|
||||||
|
using Humanizer;
|
||||||
|
using System;
|
||||||
using System.Buffers.Binary;
|
using System.Buffers.Binary;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@@ -17,6 +19,9 @@ namespace Ryujinx.Ava.Utilities.PlayReport
|
|||||||
< -201d => "Exploring the Depths",
|
< -201d => "Exploring the Depths",
|
||||||
_ => "Roaming Hyrule"
|
_ => "Roaming Hyrule"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private static FormattedValue SkywardSwordHD_Rupees(SingleValue value)
|
||||||
|
=> "rupee".ToQuantity(value.Matched.IntValue);
|
||||||
|
|
||||||
private static FormattedValue SuperMarioOdyssey_AssistMode(SingleValue value)
|
private static FormattedValue SuperMarioOdyssey_AssistMode(SingleValue value)
|
||||||
=> value.Matched.BoxedValue is 1 ? "Playing in Assist Mode" : "Playing in Regular Mode";
|
=> value.Matched.BoxedValue is 1 ? "Playing in Assist Mode" : "Playing in Regular Mode";
|
||||||
@@ -281,9 +286,14 @@ namespace Ryujinx.Ava.Utilities.PlayReport
|
|||||||
players = players.OrderBy(p => p.Rank ?? int.MaxValue).ToList();
|
players = players.OrderBy(p => p.Rank ?? int.MaxValue).ToList();
|
||||||
|
|
||||||
return players.Count > 4
|
return players.Count > 4
|
||||||
? $"{players.Count} Players - " + string.Join(", ",
|
? $"{players.Count} Players - {
|
||||||
players.Take(3).Select(p => $"{p.Character}({p.PlayerNumber}){RankMedal(p.Rank)}"))
|
players.Take(3)
|
||||||
: string.Join(", ", players.Select(p => $"{p.Character}({p.PlayerNumber}){RankMedal(p.Rank)}"));
|
.Select(p => $"{p.Character}({p.PlayerNumber}){RankMedal(p.Rank)}")
|
||||||
|
.JoinToString(", ")
|
||||||
|
}"
|
||||||
|
: players
|
||||||
|
.Select(p => $"{p.Character}({p.PlayerNumber}){RankMedal(p.Rank)}")
|
||||||
|
.JoinToString(", ");
|
||||||
|
|
||||||
string RankMedal(int? rank) => rank switch
|
string RankMedal(int? rank) => rank switch
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,16 +1,22 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Buffers.Binary;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace Ryujinx.Ava.Utilities.PlayReport
|
namespace Ryujinx.Ava.Utilities.PlayReport
|
||||||
{
|
{
|
||||||
public static partial class PlayReports
|
public static partial class PlayReports
|
||||||
{
|
{
|
||||||
public static Analyzer Analyzer { get; } = new Analyzer()
|
public static void Initialize()
|
||||||
|
{
|
||||||
|
// init lazy value
|
||||||
|
_ = Analyzer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Analyzer Analyzer => _analyzerLazy.Value;
|
||||||
|
|
||||||
|
private static readonly Lazy<Analyzer> _analyzerLazy = new(() => new Analyzer()
|
||||||
.AddSpec(
|
.AddSpec(
|
||||||
"01007ef00011e000",
|
"01007ef00011e000",
|
||||||
spec => spec
|
spec => spec
|
||||||
|
.WithDescription("based on being in Master Mode.")
|
||||||
.AddValueFormatter("IsHardMode", BreathOfTheWild_MasterMode)
|
.AddValueFormatter("IsHardMode", BreathOfTheWild_MasterMode)
|
||||||
// reset to normal status when switching between normal & master mode in title screen
|
// reset to normal status when switching between normal & master mode in title screen
|
||||||
.AddValueFormatter("AoCVer", FormattedValue.SingleAlwaysResets)
|
.AddValueFormatter("AoCVer", FormattedValue.SingleAlwaysResets)
|
||||||
@@ -18,34 +24,49 @@ namespace Ryujinx.Ava.Utilities.PlayReport
|
|||||||
.AddSpec(
|
.AddSpec(
|
||||||
"0100f2c0115b6000",
|
"0100f2c0115b6000",
|
||||||
spec => spec
|
spec => spec
|
||||||
|
.WithDescription("based on where you are in Hyrule (Depths, Surface, Sky).")
|
||||||
.AddValueFormatter("PlayerPosY", TearsOfTheKingdom_CurrentField))
|
.AddValueFormatter("PlayerPosY", TearsOfTheKingdom_CurrentField))
|
||||||
|
.AddSpec(
|
||||||
|
"01002da013484000",
|
||||||
|
spec => spec
|
||||||
|
.WithDescription("based on how many Rupees you have.")
|
||||||
|
.AddValueFormatter("rupees", SkywardSwordHD_Rupees))
|
||||||
.AddSpec(
|
.AddSpec(
|
||||||
"0100000000010000",
|
"0100000000010000",
|
||||||
spec =>
|
spec => spec
|
||||||
spec.AddValueFormatter("is_kids_mode", SuperMarioOdyssey_AssistMode)
|
.WithDescription("based on if you're playing with Assist Mode.")
|
||||||
|
.AddValueFormatter("is_kids_mode", SuperMarioOdyssey_AssistMode)
|
||||||
)
|
)
|
||||||
.AddSpec(
|
.AddSpec(
|
||||||
"010075000ecbe000",
|
"010075000ecbe000",
|
||||||
spec =>
|
spec => spec
|
||||||
spec.AddValueFormatter("is_kids_mode", SuperMarioOdysseyChina_AssistMode)
|
.WithDescription("based on if you're playing with Assist Mode.")
|
||||||
|
.AddValueFormatter("is_kids_mode", SuperMarioOdysseyChina_AssistMode)
|
||||||
)
|
)
|
||||||
.AddSpec(
|
.AddSpec(
|
||||||
"010028600ebda000",
|
"010028600ebda000",
|
||||||
spec => spec.AddValueFormatter("mode", SuperMario3DWorldOrBowsersFury)
|
spec => spec
|
||||||
|
.WithDescription("based on being in either Super Mario 3D World or Bowser's Fury.")
|
||||||
|
.AddValueFormatter("mode", SuperMario3DWorldOrBowsersFury)
|
||||||
)
|
)
|
||||||
.AddSpec( // Global & China IDs
|
.AddSpec( // Global & China IDs
|
||||||
["0100152000022000", "010075100e8ec000"],
|
["0100152000022000", "010075100e8ec000"],
|
||||||
spec => spec.AddValueFormatter("To", MarioKart8Deluxe_Mode)
|
spec => spec
|
||||||
|
.WithDescription(
|
||||||
|
"based on what modes you're selecting in the menu & whether or not you're in a race.")
|
||||||
|
.AddValueFormatter("To", MarioKart8Deluxe_Mode)
|
||||||
)
|
)
|
||||||
.AddSpec(
|
.AddSpec(
|
||||||
["0100a3d008c5c000", "01008f6008c5e000"],
|
["0100a3d008c5c000", "01008f6008c5e000"],
|
||||||
spec => spec
|
spec => spec
|
||||||
|
.WithDescription("based on what area of Paldea you're exploring.")
|
||||||
.AddValueFormatter("area_no", PokemonSVArea)
|
.AddValueFormatter("area_no", PokemonSVArea)
|
||||||
.AddValueFormatter("team_circle", PokemonSVUnionCircle)
|
.AddValueFormatter("team_circle", PokemonSVUnionCircle)
|
||||||
)
|
)
|
||||||
.AddSpec(
|
.AddSpec(
|
||||||
"01006a800016e000",
|
"01006a800016e000",
|
||||||
spec => spec
|
spec => spec
|
||||||
|
.WithDescription("based on what mode you're playing, who won, and what characters were present.")
|
||||||
.AddSparseMultiValueFormatter(
|
.AddSparseMultiValueFormatter(
|
||||||
[
|
[
|
||||||
// Metadata to figure out what PlayReport we have.
|
// Metadata to figure out what PlayReport we have.
|
||||||
@@ -63,10 +84,15 @@ namespace Ryujinx.Ava.Utilities.PlayReport
|
|||||||
)
|
)
|
||||||
.AddSpec(
|
.AddSpec(
|
||||||
[
|
[
|
||||||
"0100c9a00ece6000", "01008d300c50c000", "0100d870045b6000",
|
"0100c9a00ece6000", "01008d300c50c000", "0100d870045b6000",
|
||||||
"010012f017576000", "0100c62011050000", "0100b3c014bda000"],
|
"010012f017576000", "0100c62011050000", "0100b3c014bda000"
|
||||||
spec => spec.AddValueFormatter("launch_title_id", NsoEmulator_LaunchedGame)
|
],
|
||||||
);
|
spec => spec
|
||||||
|
.WithDescription(
|
||||||
|
"based on what game you first launch.\n\nNSO emulators do not print any Play Report information past the first game launch so it's all we got.")
|
||||||
|
.AddValueFormatter("launch_title_id", NsoEmulator_LaunchedGame)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
private static string Playing(string game) => $"Playing {game}";
|
private static string Playing(string game) => $"Playing {game}";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using FluentAvalonia.Core;
|
using MsgPack;
|
||||||
using MsgPack;
|
|
||||||
using Ryujinx.Ava.Utilities.AppLibrary;
|
using Ryujinx.Ava.Utilities.AppLibrary;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@@ -24,6 +23,20 @@ namespace Ryujinx.Ava.Utilities.PlayReport
|
|||||||
|
|
||||||
public required string[] TitleIds { get; init; }
|
public required string[] TitleIds { get; init; }
|
||||||
|
|
||||||
|
public const string DefaultDescription = "Formats the details on your Discord presence based on logged data from the game.";
|
||||||
|
|
||||||
|
private string _valueDescription;
|
||||||
|
|
||||||
|
public string Description => _valueDescription ?? DefaultDescription;
|
||||||
|
|
||||||
|
public GameSpec WithDescription(string description)
|
||||||
|
{
|
||||||
|
_valueDescription = description != null
|
||||||
|
? $"Formats the details on your Discord presence {description}"
|
||||||
|
: null;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public List<FormatterSpecBase> ValueFormatters { get; } = [];
|
public List<FormatterSpecBase> ValueFormatters { get; } = [];
|
||||||
|
|
||||||
|
|
||||||
@@ -153,7 +166,7 @@ namespace Ryujinx.Ava.Utilities.PlayReport
|
|||||||
public override bool GetData(Horizon.Prepo.Types.PlayReport playReport, out object result)
|
public override bool GetData(Horizon.Prepo.Types.PlayReport playReport, out object result)
|
||||||
{
|
{
|
||||||
List<MessagePackObject> packedObjects = [];
|
List<MessagePackObject> packedObjects = [];
|
||||||
foreach (var reportKey in ReportKeys)
|
foreach (string reportKey in ReportKeys)
|
||||||
{
|
{
|
||||||
if (!playReport.ReportData.AsDictionary().TryGetValue(reportKey, out MessagePackObject valuePackObject))
|
if (!playReport.ReportData.AsDictionary().TryGetValue(reportKey, out MessagePackObject valuePackObject))
|
||||||
{
|
{
|
||||||
@@ -177,7 +190,7 @@ namespace Ryujinx.Ava.Utilities.PlayReport
|
|||||||
public override bool GetData(Horizon.Prepo.Types.PlayReport playReport, out object result)
|
public override bool GetData(Horizon.Prepo.Types.PlayReport playReport, out object result)
|
||||||
{
|
{
|
||||||
Dictionary<string, MessagePackObject> packedObjects = [];
|
Dictionary<string, MessagePackObject> packedObjects = [];
|
||||||
foreach (var reportKey in ReportKeys)
|
foreach (string reportKey in ReportKeys)
|
||||||
{
|
{
|
||||||
if (!playReport.ReportData.AsDictionary().TryGetValue(reportKey, out MessagePackObject valuePackObject))
|
if (!playReport.ReportData.AsDictionary().TryGetValue(reportKey, out MessagePackObject valuePackObject))
|
||||||
continue;
|
continue;
|
||||||
@@ -198,7 +211,7 @@ namespace Ryujinx.Ava.Utilities.PlayReport
|
|||||||
public string[] ReportKeys { get; init; }
|
public string[] ReportKeys { get; init; }
|
||||||
public Delegate Formatter { get; init; }
|
public Delegate Formatter { get; init; }
|
||||||
|
|
||||||
public bool Format(ApplicationMetadata appMeta, Horizon.Prepo.Types.PlayReport playReport,
|
public bool TryFormat(ApplicationMetadata appMeta, Horizon.Prepo.Types.PlayReport playReport,
|
||||||
out FormattedValue formattedValue)
|
out FormattedValue formattedValue)
|
||||||
{
|
{
|
||||||
formattedValue = default;
|
formattedValue = default;
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ namespace Ryujinx.Ava.Utilities
|
|||||||
public static class ShortcutHelper
|
public static class ShortcutHelper
|
||||||
{
|
{
|
||||||
[SupportedOSPlatform("windows")]
|
[SupportedOSPlatform("windows")]
|
||||||
private static void CreateShortcutWindows(string applicationFilePath, string applicationId, byte[] iconData, string iconPath, string cleanedAppName, string desktopPath, string args = "")
|
private static void CreateShortcutWindows(string applicationFilePath, string applicationId, byte[] iconData, string iconPath, string cleanedAppName, string desktopPath)
|
||||||
{
|
{
|
||||||
string basePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, AppDomain.CurrentDomain.FriendlyName + ".exe");
|
string basePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, AppDomain.CurrentDomain.FriendlyName + ".exe");
|
||||||
iconPath += ".ico";
|
iconPath += ".ico";
|
||||||
@@ -22,13 +22,13 @@ namespace Ryujinx.Ava.Utilities
|
|||||||
image.Resize(new SKImageInfo(128, 128), SKFilterQuality.High);
|
image.Resize(new SKImageInfo(128, 128), SKFilterQuality.High);
|
||||||
SaveBitmapAsIcon(image, iconPath);
|
SaveBitmapAsIcon(image, iconPath);
|
||||||
|
|
||||||
Shortcut shortcut = Shortcut.CreateShortcut(basePath, GetArgsString(applicationFilePath, applicationId, args), iconPath, 0);
|
Shortcut shortcut = Shortcut.CreateShortcut(basePath, GetArgsString(applicationFilePath, applicationId), iconPath, 0);
|
||||||
shortcut.StringData.NameString = cleanedAppName;
|
shortcut.StringData.NameString = cleanedAppName;
|
||||||
shortcut.WriteToFile(Path.Combine(desktopPath, cleanedAppName + ".lnk"));
|
shortcut.WriteToFile(Path.Combine(desktopPath, cleanedAppName + ".lnk"));
|
||||||
}
|
}
|
||||||
|
|
||||||
[SupportedOSPlatform("linux")]
|
[SupportedOSPlatform("linux")]
|
||||||
private static void CreateShortcutLinux(string applicationFilePath, string applicationId, byte[] iconData, string iconPath, string desktopPath, string cleanedAppName, string args = "")
|
private static void CreateShortcutLinux(string applicationFilePath, string applicationId, byte[] iconData, string iconPath, string desktopPath, string cleanedAppName)
|
||||||
{
|
{
|
||||||
string basePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Ryujinx.sh");
|
string basePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Ryujinx.sh");
|
||||||
string desktopFile = EmbeddedResources.ReadAllText("Ryujinx/Assets/ShortcutFiles/shortcut-template.desktop");
|
string desktopFile = EmbeddedResources.ReadAllText("Ryujinx/Assets/ShortcutFiles/shortcut-template.desktop");
|
||||||
@@ -40,11 +40,11 @@ namespace Ryujinx.Ava.Utilities
|
|||||||
data.SaveTo(file);
|
data.SaveTo(file);
|
||||||
|
|
||||||
using StreamWriter outputFile = new(Path.Combine(desktopPath, cleanedAppName + ".desktop"));
|
using StreamWriter outputFile = new(Path.Combine(desktopPath, cleanedAppName + ".desktop"));
|
||||||
outputFile.Write(desktopFile, cleanedAppName, iconPath, $"{basePath} {GetArgsString(applicationFilePath, applicationId, args)}");
|
outputFile.Write(desktopFile, cleanedAppName, iconPath, $"{basePath} {GetArgsString(applicationFilePath, applicationId)}");
|
||||||
}
|
}
|
||||||
|
|
||||||
[SupportedOSPlatform("macos")]
|
[SupportedOSPlatform("macos")]
|
||||||
private static void CreateShortcutMacos(string appFilePath, string applicationId, byte[] iconData, string desktopPath, string cleanedAppName, string args = "")
|
private static void CreateShortcutMacos(string appFilePath, string applicationId, byte[] iconData, string desktopPath, string cleanedAppName)
|
||||||
{
|
{
|
||||||
string basePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Ryujinx");
|
string basePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Ryujinx");
|
||||||
string plistFile = EmbeddedResources.ReadAllText("Ryujinx/Assets/ShortcutFiles/shortcut-template.plist");
|
string plistFile = EmbeddedResources.ReadAllText("Ryujinx/Assets/ShortcutFiles/shortcut-template.plist");
|
||||||
@@ -63,7 +63,7 @@ namespace Ryujinx.Ava.Utilities
|
|||||||
string scriptPath = Path.Combine(scriptFolderPath, ScriptName);
|
string scriptPath = Path.Combine(scriptFolderPath, ScriptName);
|
||||||
using StreamWriter scriptFile = new(scriptPath);
|
using StreamWriter scriptFile = new(scriptPath);
|
||||||
|
|
||||||
scriptFile.Write(shortcutScript, basePath, GetArgsString(appFilePath, applicationId, args));
|
scriptFile.Write(shortcutScript, basePath, GetArgsString(appFilePath, applicationId));
|
||||||
|
|
||||||
// Set execute permission
|
// Set execute permission
|
||||||
FileInfo fileInfo = new(scriptPath);
|
FileInfo fileInfo = new(scriptPath);
|
||||||
@@ -87,7 +87,7 @@ namespace Ryujinx.Ava.Utilities
|
|||||||
outputFile.Write(plistFile, ScriptName, cleanedAppName, IconName);
|
outputFile.Write(plistFile, ScriptName, cleanedAppName, IconName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void CreateAppShortcut(string applicationFilePath, string applicationName, string applicationId, byte[] iconData, string args = "")
|
public static void CreateAppShortcut(string applicationFilePath, string applicationName, string applicationId, byte[] iconData)
|
||||||
{
|
{
|
||||||
string desktopPath = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory);
|
string desktopPath = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory);
|
||||||
string cleanedAppName = string.Join("_", applicationName.Split(Path.GetInvalidFileNameChars()));
|
string cleanedAppName = string.Join("_", applicationName.Split(Path.GetInvalidFileNameChars()));
|
||||||
@@ -96,7 +96,7 @@ namespace Ryujinx.Ava.Utilities
|
|||||||
{
|
{
|
||||||
string iconPath = Path.Combine(AppDataManager.BaseDirPath, "games", applicationId, "app");
|
string iconPath = Path.Combine(AppDataManager.BaseDirPath, "games", applicationId, "app");
|
||||||
|
|
||||||
CreateShortcutWindows(applicationFilePath, applicationId, iconData, iconPath, cleanedAppName, desktopPath, args);
|
CreateShortcutWindows(applicationFilePath, applicationId, iconData, iconPath, cleanedAppName, desktopPath);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -106,14 +106,14 @@ namespace Ryujinx.Ava.Utilities
|
|||||||
string iconPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".local", "share", "icons", "Ryujinx");
|
string iconPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".local", "share", "icons", "Ryujinx");
|
||||||
|
|
||||||
Directory.CreateDirectory(iconPath);
|
Directory.CreateDirectory(iconPath);
|
||||||
CreateShortcutLinux(applicationFilePath, applicationId, iconData, Path.Combine(iconPath, applicationId), desktopPath, cleanedAppName, args);
|
CreateShortcutLinux(applicationFilePath, applicationId, iconData, Path.Combine(iconPath, applicationId), desktopPath, cleanedAppName);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (OperatingSystem.IsMacOS())
|
if (OperatingSystem.IsMacOS())
|
||||||
{
|
{
|
||||||
CreateShortcutMacos(applicationFilePath, applicationId, iconData, desktopPath, cleanedAppName, args);
|
CreateShortcutMacos(applicationFilePath, applicationId, iconData, desktopPath, cleanedAppName);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -121,7 +121,7 @@ namespace Ryujinx.Ava.Utilities
|
|||||||
throw new NotImplementedException("Shortcut support has not been implemented yet for this OS.");
|
throw new NotImplementedException("Shortcut support has not been implemented yet for this OS.");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string GetArgsString(string appFilePath, string applicationId, string config = "")
|
private static string GetArgsString(string appFilePath, string applicationId)
|
||||||
{
|
{
|
||||||
// args are first defined as a list, for easier adjustments in the future
|
// args are first defined as a list, for easier adjustments in the future
|
||||||
List<string> argsList = [];
|
List<string> argsList = [];
|
||||||
@@ -132,11 +132,6 @@ namespace Ryujinx.Ava.Utilities
|
|||||||
argsList.Add($"\"{CommandLineState.BaseDirPathArg}\"");
|
argsList.Add($"\"{CommandLineState.BaseDirPathArg}\"");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(config))
|
|
||||||
{
|
|
||||||
argsList.Add(config);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (appFilePath.ToLower().EndsWith(".xci"))
|
if (appFilePath.ToLower().EndsWith(".xci"))
|
||||||
{
|
{
|
||||||
argsList.Add("--application-id");
|
argsList.Add("--application-id");
|
||||||
|
|||||||
Reference in New Issue
Block a user