Compare commits

..

7 Commits

Author SHA1 Message Date
Evan Husted
06abba25c1 UI: Adapt accent color to the user's system.
https://amwx.github.io/FluentAvaloniaDocs/pages/FATheme/Accents#using-the-systems-accent-color
2024-12-07 06:22:46 -06:00
Evan Husted
de00a71690 UI: Fix missing total DLC count.
Fixes #347.
2024-12-07 05:48:11 -06:00
Evan Husted
315a1819c0 Attempt #2 2024-12-07 05:31:37 -06:00
Evan Husted
4ffb8aef12 Try and fix nullref 2024-12-07 05:21:16 -06:00
Evan Husted
290a6ad5de HLE: extract custom NACP data functionality into a static helper for generic reuse elsewhere, and clarify magic numbers. 2024-12-07 04:30:04 -06:00
Evan Husted
eda4f4349b headless: Actually log the command line errors 2024-12-07 04:06:22 -06:00
Evan Husted
5fbcb1f3a7 misc: chore: Cleanups & unused parameter removal 2024-12-07 04:06:22 -06:00
31 changed files with 117 additions and 134 deletions

View File

@@ -1,6 +1,8 @@
namespace Ryujinx.Common.Configuration.Hid.Controller
{
public class JoyconConfigControllerStick<TButton, TStick> where TButton : unmanaged where TStick : unmanaged
public class JoyconConfigControllerStick<TButton, TStick>
where TButton : unmanaged
where TStick : unmanaged
{
public TStick Joystick { get; set; }
public bool InvertStickX { get; set; }

View File

@@ -0,0 +1,37 @@
using LibHac.Common;
using LibHac.Ns;
using System;
using System.Text;
namespace Ryujinx.HLE
{
public static class StructHelpers
{
public static BlitStruct<ApplicationControlProperty> CreateCustomNacpData(string name, string version)
{
// https://switchbrew.org/wiki/NACP
const int OffsetOfDisplayVersion = 0x3060;
// https://switchbrew.org/wiki/NACP#ApplicationTitle
const int TotalApplicationTitles = 0x10;
const int SizeOfApplicationTitle = 0x300;
const int OffsetOfApplicationPublisherStrings = 0x200;
var nacpData = new BlitStruct<ApplicationControlProperty>(1);
// name and publisher buffer
// repeat once for each locale (the ApplicationControlProperty has 16 locales)
for (int i = 0; i < TotalApplicationTitles; i++)
{
Encoding.ASCII.GetBytes(name).AsSpan().CopyTo(nacpData.ByteSpan[(i * SizeOfApplicationTitle)..]);
"Ryujinx"u8.CopyTo(nacpData.ByteSpan[(i * SizeOfApplicationTitle + OffsetOfApplicationPublisherStrings)..]);
}
// version buffer
Encoding.ASCII.GetBytes(version).AsSpan().CopyTo(nacpData.ByteSpan[OffsetOfDisplayVersion..]);
return nacpData;
}
}
}

View File

@@ -1,4 +1,5 @@
using CommandLine;
using Gommon;
using LibHac.Tools.FsSystem;
using Ryujinx.Audio.Backends.SDL2;
using Ryujinx.Common;
@@ -96,8 +97,13 @@ namespace Ryujinx.Headless.SDL2
}
Parser.Default.ParseArguments<Options>(args)
.WithParsed(Load)
.WithNotParsed(errors => errors.Output());
.WithParsed(Load)
.WithNotParsed(errors =>
{
Logger.Error?.PrintMsg(LogClass.Application, "Error parsing command-line arguments:");
errors.ForEach(err => Logger.Error?.PrintMsg(LogClass.Application, $" - {err.Tag}"));
});
}
private static InputConfig HandlePlayerConfiguration(string inputProfileName, string inputId, PlayerIndex index)
@@ -579,8 +585,8 @@ namespace Ryujinx.Headless.SDL2
options.MultiplayerLanInterfaceId,
Common.Configuration.Multiplayer.MultiplayerMode.Disabled,
false,
"",
"",
string.Empty,
string.Empty,
options.CustomVSyncInterval);
return new Switch(configuration);

View File

@@ -1,5 +1,4 @@
using DynamicData;
using DynamicData.Kernel;
using Gommon;
using LibHac;
using LibHac.Common;
@@ -37,14 +36,13 @@ using System.Threading.Tasks;
using ContentType = LibHac.Ncm.ContentType;
using MissingKeyException = LibHac.Common.Keys.MissingKeyException;
using Path = System.IO.Path;
using SpanHelpers = LibHac.Common.SpanHelpers;
using TimeSpan = System.TimeSpan;
namespace Ryujinx.UI.App.Common
{
public class ApplicationLibrary
{
public static string DefaultLanPlayWebHost = "ryuldnweb.vudjun.com";
public const string DefaultLanPlayWebHost = "ryuldnweb.vudjun.com";
public Language DesiredLanguage { get; set; }
public event EventHandler<ApplicationCountUpdatedEventArgs> ApplicationCountUpdated;
public event EventHandler<LdnGameDataReceivedEventArgs> LdnGameDataReceived;
@@ -191,12 +189,9 @@ namespace Ryujinx.UI.App.Common
}
}
if (isExeFs)
{
return GetApplicationFromExeFs(pfs, filePath);
}
return null;
return isExeFs
? GetApplicationFromExeFs(pfs, filePath)
: null;
}
/// <exception cref="LibHac.Common.Keys.MissingKeyException">The configured key set is missing a key.</exception>
@@ -512,10 +507,6 @@ namespace Ryujinx.UI.App.Common
case ".xci":
case ".nsp":
{
IntegrityCheckLevel checkLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks
? IntegrityCheckLevel.ErrorOnInvalid
: IntegrityCheckLevel.None;
using IFileSystem pfs = PartitionFileSystemUtils.OpenApplicationFileSystem(filePath, _virtualFileSystem);
foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))
@@ -604,7 +595,7 @@ namespace Ryujinx.UI.App.Common
controlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None)
.OpenFile(ref nacpFile.Ref, "/control.nacp".ToU8Span(), OpenMode.Read)
.ThrowIfFailure();
nacpFile.Get.Read(out _, 0, SpanHelpers.AsByteSpan(ref controlData),
nacpFile.Get.Read(out _, 0, LibHac.Common.SpanHelpers.AsByteSpan(ref controlData),
ReadOption.None).ThrowIfFailure();
var displayVersion = controlData.DisplayVersionString.ToString();
@@ -827,7 +818,7 @@ namespace Ryujinx.UI.App.Common
{
_downloadableContents.Edit(it =>
{
DownloadableContentsHelper.SaveDownloadableContentsJson(_virtualFileSystem, application.IdBase, dlcs);
DownloadableContentsHelper.SaveDownloadableContentsJson(application.IdBase, dlcs);
it.Remove(it.Items.Where(item => item.Dlc.TitleIdBase == application.IdBase));
it.AddOrUpdate(dlcs);
@@ -839,7 +830,7 @@ namespace Ryujinx.UI.App.Common
{
_titleUpdates.Edit(it =>
{
TitleUpdatesHelper.SaveTitleUpdatesJson(_virtualFileSystem, application.IdBase, updates);
TitleUpdatesHelper.SaveTitleUpdatesJson(application.IdBase, updates);
it.Remove(it.Items.Where(item => item.TitleUpdate.TitleIdBase == application.IdBase));
it.AddOrUpdate(updates);
@@ -1088,14 +1079,15 @@ namespace Ryujinx.UI.App.Common
private bool AddAndAutoSelectUpdate(TitleUpdateModel update)
{
var currentlySelected = TitleUpdates.Items.FirstOrOptional(it =>
if (update == null) return false;
var currentlySelected = TitleUpdates.Items.FindFirst(it =>
it.TitleUpdate.TitleIdBase == update.TitleIdBase && it.IsSelected);
var shouldSelect = !currentlySelected.HasValue ||
currentlySelected.Value.TitleUpdate.Version < update.Version;
var shouldSelect = currentlySelected.Check(curr => curr.TitleUpdate?.Version < update.Version);
_titleUpdates.AddOrUpdate((update, shouldSelect));
if (currentlySelected.HasValue && shouldSelect)
{
_titleUpdates.AddOrUpdate((currentlySelected.Value.TitleUpdate, false));
@@ -1464,7 +1456,7 @@ namespace Ryujinx.UI.App.Common
if (addedNewDlc)
{
var gameDlcs = it.Items.Where(dlc => dlc.Dlc.TitleIdBase == application.IdBase).ToList();
DownloadableContentsHelper.SaveDownloadableContentsJson(_virtualFileSystem, application.IdBase,
DownloadableContentsHelper.SaveDownloadableContentsJson(application.IdBase,
gameDlcs);
}
}
@@ -1483,11 +1475,11 @@ namespace Ryujinx.UI.App.Common
TitleUpdatesHelper.LoadTitleUpdatesJson(_virtualFileSystem, application.IdBase);
it.AddOrUpdate(savedUpdates);
var selectedUpdate = savedUpdates.FirstOrOptional(update => update.IsSelected);
var selectedUpdate = savedUpdates.FindFirst(update => update.IsSelected);
if (TryGetTitleUpdatesFromFile(application.Path, out var bundledUpdates))
{
var savedUpdateLookup = savedUpdates.Select(update => update.Item1).ToHashSet();
var savedUpdateLookup = savedUpdates.Select(update => update.Update).ToHashSet();
bool updatesChanged = false;
foreach (var update in bundledUpdates.OrderByDescending(bundled => bundled.Version))
@@ -1495,12 +1487,11 @@ namespace Ryujinx.UI.App.Common
if (!savedUpdateLookup.Contains(update))
{
bool shouldSelect = false;
if (!selectedUpdate.HasValue || selectedUpdate.Value.Item1.Version < update.Version)
if (selectedUpdate.Check(su => su.Update?.Version < update.Version))
{
shouldSelect = true;
if (selectedUpdate.HasValue)
_titleUpdates.AddOrUpdate((selectedUpdate.Value.Item1, false));
selectedUpdate = DynamicData.Kernel.Optional<(TitleUpdateModel, bool IsSelected)>.Create((update, true));
_titleUpdates.AddOrUpdate((selectedUpdate.Value.Update, false));
selectedUpdate = (update, true);
}
modifiedVersion = modifiedVersion || shouldSelect;
@@ -1513,7 +1504,7 @@ namespace Ryujinx.UI.App.Common
if (updatesChanged)
{
var gameUpdates = it.Items.Where(update => update.TitleUpdate.TitleIdBase == application.IdBase).ToList();
TitleUpdatesHelper.SaveTitleUpdatesJson(_virtualFileSystem, application.IdBase, gameUpdates);
TitleUpdatesHelper.SaveTitleUpdatesJson(application.IdBase, gameUpdates);
}
}
});
@@ -1525,14 +1516,14 @@ namespace Ryujinx.UI.App.Common
private void SaveDownloadableContentsForGame(ulong titleIdBase)
{
var dlcs = DownloadableContents.Items.Where(dlc => dlc.Dlc.TitleIdBase == titleIdBase).ToList();
DownloadableContentsHelper.SaveDownloadableContentsJson(_virtualFileSystem, titleIdBase, dlcs);
DownloadableContentsHelper.SaveDownloadableContentsJson(titleIdBase, dlcs);
}
// Save the _currently tracked_ update state for the game
private void SaveTitleUpdatesForGame(ulong titleIdBase)
{
var updates = TitleUpdates.Items.Where(update => update.TitleUpdate.TitleIdBase == titleIdBase).ToList();
TitleUpdatesHelper.SaveTitleUpdatesJson(_virtualFileSystem, titleIdBase, updates);
TitleUpdatesHelper.SaveTitleUpdatesJson(titleIdBase, updates);
}
// ApplicationData isnt live-updating (e.g. when an update gets applied) and so this is meant to trigger a refresh

View File

@@ -1,16 +1,12 @@
using ARMeilleure;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Common.Configuration.Hid.Controller;
using Ryujinx.Common.Configuration.Hid.Keyboard;
using Ryujinx.Common.Configuration.Multiplayer;
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.Vulkan;
using Ryujinx.HLE;
using Ryujinx.UI.Common.Configuration.System;
using Ryujinx.UI.Common.Configuration.UI;
using System;
using System.Collections.Generic;
namespace Ryujinx.UI.Common.Configuration
{
@@ -21,10 +17,10 @@ namespace Ryujinx.UI.Common.Configuration
if (Instance != null)
{
throw new InvalidOperationException("Configuration is already initialized");
}
}
Instance = new ConfigurationState();
}
}
public ConfigurationFileFormat ToFileFormat()
{

View File

@@ -42,7 +42,7 @@ namespace Ryujinx.UI.Common.Helper
}
}
public static void SaveDownloadableContentsJson(VirtualFileSystem vfs, ulong applicationIdBase, List<(DownloadableContentModel, bool IsEnabled)> dlcs)
public static void SaveDownloadableContentsJson(ulong applicationIdBase, List<(DownloadableContentModel, bool IsEnabled)> dlcs)
{
DownloadableContentContainer container = default;
List<DownloadableContentContainer> downloadableContentContainerList = new();

View File

@@ -28,7 +28,7 @@ namespace Ryujinx.UI.Common.Helper
{
private static readonly TitleUpdateMetadataJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions());
public static List<(TitleUpdateModel, bool IsSelected)> LoadTitleUpdatesJson(VirtualFileSystem vfs, ulong applicationIdBase)
public static List<(TitleUpdateModel Update, bool IsSelected)> LoadTitleUpdatesJson(VirtualFileSystem vfs, ulong applicationIdBase)
{
var titleUpdatesJsonPath = PathToGameUpdatesJson(applicationIdBase);
@@ -49,7 +49,7 @@ namespace Ryujinx.UI.Common.Helper
}
}
public static void SaveTitleUpdatesJson(VirtualFileSystem vfs, ulong applicationIdBase, List<(TitleUpdateModel, bool IsSelected)> updates)
public static void SaveTitleUpdatesJson(ulong applicationIdBase, List<(TitleUpdateModel, bool IsSelected)> updates)
{
var titleUpdateWindowData = new TitleUpdateMetadata
{
@@ -77,7 +77,7 @@ namespace Ryujinx.UI.Common.Helper
JsonHelper.SerializeToFile(titleUpdatesJsonPath, titleUpdateWindowData, _serializerContext.TitleUpdateMetadata);
}
private static List<(TitleUpdateModel, bool IsSelected)> LoadTitleUpdates(VirtualFileSystem vfs, TitleUpdateMetadata titleUpdateMetadata, ulong applicationIdBase)
private static List<(TitleUpdateModel Update, bool IsSelected)> LoadTitleUpdates(VirtualFileSystem vfs, TitleUpdateMetadata titleUpdateMetadata, ulong applicationIdBase)
{
var result = new List<(TitleUpdateModel, bool IsSelected)>();

View File

@@ -11,7 +11,7 @@
</ResourceDictionary>
</Application.Resources>
<Application.Styles>
<sty:FluentAvaloniaTheme PreferSystemTheme="False" />
<sty:FluentAvaloniaTheme PreferUserAccentColor="True" PreferSystemTheme="False" />
<StyleInclude Source="/Assets/Styles/Styles.xaml" />
</Application.Styles>
</Application>

View File

@@ -788,7 +788,7 @@
"CheatWindowHeading": "الغش متوفر لـ {0} [{1}]",
"BuildId": "معرف البناء:",
"DlcWindowBundledContentNotice": "Bundled DLC cannot be removed, only disabled.",
"DlcWindowHeading": "المحتويات القابلة للتنزيل {0}",
"DlcWindowHeading": "{0} DLC(s) available",
"DlcWindowDlcAddedMessage": "{0} new downloadable content(s) added",
"AutoloadDlcAddedMessage": "{0} new downloadable content(s) added",
"AutoloadDlcRemovedMessage": "{0} missing downloadable content(s) removed",

View File

@@ -788,7 +788,7 @@
"CheatWindowHeading": "Cheats verfügbar für {0} [{1}]",
"DlcWindowBundledContentNotice": "Bundled DLC cannot be removed, only disabled.",
"BuildId": "BuildId:",
"DlcWindowHeading": "DLC verfügbar für {0} [{1}]",
"DlcWindowHeading": "{0} DLC(s) available",
"DlcWindowDlcAddedMessage": "{0} new downloadable content(s) added",
"AutoloadDlcAddedMessage": "{0} new downloadable content(s) added",
"AutoloadDlcRemovedMessage": "{0} missing downloadable content(s) removed",

View File

@@ -788,7 +788,7 @@
"CheatWindowHeading": "Διαθέσιμα Cheats για {0} [{1}]",
"BuildId": "BuildId:",
"DlcWindowBundledContentNotice": "Bundled DLC cannot be removed, only disabled.",
"DlcWindowHeading": "{0} Downloadable Content(s) available for {1} ({2})",
"DlcWindowHeading": "{0} DLC(s) available",
"DlcWindowDlcAddedMessage": "{0} new downloadable content(s) added",
"AutoloadDlcAddedMessage": "{0} new downloadable content(s) added",
"AutoloadDlcRemovedMessage": "{0} missing downloadable content(s) removed",

View File

@@ -802,7 +802,7 @@
"CheatWindowHeading": "Cheats Available for {0} [{1}]",
"BuildId": "BuildId:",
"DlcWindowBundledContentNotice": "Bundled DLC cannot be removed, only disabled.",
"DlcWindowHeading": "{0} Downloadable Content(s) available for {1} ({2})",
"DlcWindowHeading": "{0} DLC(s) available",
"DlcWindowDlcAddedMessage": "{0} new downloadable content(s) added",
"AutoloadDlcAddedMessage": "{0} new downloadable content(s) added",
"AutoloadDlcRemovedMessage": "{0} missing downloadable content(s) removed",

View File

@@ -787,7 +787,7 @@
"UpdateWindowBundledContentNotice": "Las actualizaciones agrupadas no pueden ser eliminadas, solamente deshabilitadas.",
"CheatWindowHeading": "Cheats disponibles para {0} [{1}]",
"BuildId": "Id de compilación:",
"DlcWindowHeading": "Contenido descargable disponible para {0} [{1}]",
"DlcWindowHeading": "{0} DLC(s) available",
"DlcWindowDlcAddedMessage": "Se agregaron {0} nuevo(s) contenido(s) descargable(s)",
"AutoloadDlcAddedMessage": "Se agregaron {0} nuevo(s) contenido(s) descargable(s)",
"AutoloadDlcRemovedMessage": "Se eliminaron {0} contenido(s) descargable(s) faltantes",

View File

@@ -788,7 +788,7 @@
"CheatWindowHeading": "Cheats disponibles pour {0} [{1}]",
"BuildId": "BuildId :",
"DlcWindowBundledContentNotice": "Les DLC inclus avec le jeu ne peuvent pas être supprimés mais peuvent être désactivés.",
"DlcWindowHeading": "{0} Contenu(s) téléchargeable(s)",
"DlcWindowHeading": "{0} DLC(s) available",
"DlcWindowDlcAddedMessage": "{0} nouveau(x) contenu(s) téléchargeable(s) ajouté(s)",
"AutoloadDlcAddedMessage": "{0} nouveau(x) contenu(s) téléchargeable(s) ajouté(s)",
"AutoloadDlcRemovedMessage": "{0} contenu(s) téléchargeable(s) manquant(s) supprimé(s)",

View File

@@ -788,7 +788,7 @@
"CheatWindowHeading": "Trucchi disponibili per {0} [{1}]",
"BuildId": "ID Build",
"DlcWindowBundledContentNotice": "i DLC \"impacchettati\" non possono essere rimossi, ma solo disabilitati.",
"DlcWindowHeading": "DLC disponibili per {0} [{1}]",
"DlcWindowHeading": "{0} DLC(s) available",
"DlcWindowDlcAddedMessage": "{0} nuovo/i contenuto/i scaricabile/i aggiunto/i",
"AutoloadDlcAddedMessage": "{0} contenuto/i scaricabile/i aggiunto/i",
"AutoloadDlcRemovedMessage": "{0} contenuto/i scaricabile/i mancante/i rimosso/i",

View File

@@ -787,7 +787,7 @@
"UpdateWindowBundledContentNotice": "Bundled updates cannot be removed, only disabled.",
"CheatWindowHeading": "利用可能なチート {0} [{1}]",
"BuildId": "ビルドID:",
"DlcWindowHeading": "利用可能な DLC {0} [{1}]",
"DlcWindowHeading": "{0} DLC(s) available",
"DlcWindowDlcAddedMessage": "{0} new downloadable content(s) added",
"AutoloadDlcAddedMessage": "{0} new downloadable content(s) added",
"AutoloadDlcRemovedMessage": "{0} missing downloadable content(s) removed",

View File

@@ -788,7 +788,7 @@
"CheatWindowHeading": "{0} [{1}]에 사용 가능한 치트",
"BuildId": "빌드ID:",
"DlcWindowBundledContentNotice": "번들 DLC는 제거할 수 없으며 비활성화만 가능합니다.",
"DlcWindowHeading": "{1} ({2})에 내려받기 가능한 콘텐츠 {0}개 사용 가능",
"DlcWindowHeading": "{0} DLC(s) available",
"DlcWindowDlcAddedMessage": "{0}개의 새로운 내려받기 가능한 콘텐츠가 추가됨",
"AutoloadDlcAddedMessage": "{0}개의 새로운 내려받기 가능한 콘텐츠가 추가됨",
"AutoloadDlcRemovedMessage": "{0}개의 내려받기 가능한 콘텐츠가 제거됨",

View File

@@ -788,7 +788,7 @@
"CheatWindowHeading": "Kody Dostępne dla {0} [{1}]",
"BuildId": "Identyfikator wersji:",
"DlcWindowBundledContentNotice": "Bundled DLC cannot be removed, only disabled.",
"DlcWindowHeading": "{0} Zawartości do Pobrania dostępna dla {1} ({2})",
"DlcWindowHeading": "{0} DLC(s) available",
"DlcWindowDlcAddedMessage": "{0} new downloadable content(s) added",
"AutoloadDlcAddedMessage": "{0} new downloadable content(s) added",
"AutoloadDlcRemovedMessage": "{0} missing downloadable content(s) removed",

View File

@@ -787,7 +787,7 @@
"CheatWindowHeading": "Cheats disponíveis para {0} [{1}]",
"BuildId": "ID da Build:",
"DlcWindowBundledContentNotice": "DLCs incorporadas não podem ser removidas, apenas desativadas.",
"DlcWindowHeading": "{0} DLCs disponíveis para {1} ({2})",
"DlcWindowHeading": "{0} DLC(s) available",
"DlcWindowDlcAddedMessage": "{0} novo(s) conteúdo(s) para download adicionado(s)",
"AutoloadDlcAddedMessage": "{0} novo(s) conteúdo(s) para download adicionado(s)",
"AutoloadDlcRemovedMessage": "{0} conteúdo(s) para download ausente(s) removido(s)",

View File

@@ -788,7 +788,7 @@
"CheatWindowHeading": "Доступные читы для {0} [{1}]",
"BuildId": "ID версии:",
"DlcWindowBundledContentNotice": "Bundled DLC cannot be removed, only disabled.",
"DlcWindowHeading": "{0} DLC",
"DlcWindowHeading": "{0} DLC(s) available",
"DlcWindowDlcAddedMessage": "{0} new downloadable content(s) added",
"AutoloadDlcAddedMessage": "{0} new downloadable content(s) added",
"AutoloadDlcRemovedMessage": "{0} missing downloadable content(s) removed",

View File

@@ -788,7 +788,7 @@
"CheatWindowHeading": "สูตรโกงมีให้สำหรับ {0} [{1}]",
"BuildId": "รหัสการสร้าง:",
"DlcWindowBundledContentNotice": "แพ็ค DLC ไม่สามารถลบทิ้งได้ สามารถปิดใช้งานได้เท่านั้น",
"DlcWindowHeading": "{0} DLC ที่สามารถดาวน์โหลดได้",
"DlcWindowHeading": "{0} DLC(s) available",
"DlcWindowDlcAddedMessage": "{0} DLC ใหม่ที่เพิ่มเข้ามา",
"AutoloadDlcAddedMessage": "{0} ใหม่ที่เพิ่มเข้ามา",
"AutoloadDlcRemovedMessage": "{0} missing downloadable content(s) removed",

View File

@@ -788,7 +788,7 @@
"CheatWindowHeading": "{0} için Hile mevcut [{1}]",
"BuildId": "BuildId:",
"DlcWindowBundledContentNotice": "Bundled DLC cannot be removed, only disabled.",
"DlcWindowHeading": "{0} Downloadable Content(s) available for {1} ({2})",
"DlcWindowHeading": "{0} DLC(s) available",
"DlcWindowDlcAddedMessage": "{0} new downloadable content(s) added",
"AutoloadDlcAddedMessage": "{0} new downloadable content(s) added",
"AutoloadDlcRemovedMessage": "{0} missing downloadable content(s) removed",

View File

@@ -788,7 +788,7 @@
"CheatWindowHeading": "Коди доступні для {0} [{1}]",
"BuildId": "ID збірки:",
"DlcWindowBundledContentNotice": "Bundled DLC cannot be removed, only disabled.",
"DlcWindowHeading": "Вміст для завантаження, доступний для {1} ({2}): {0}",
"DlcWindowHeading": "{0} DLC(s) available",
"DlcWindowDlcAddedMessage": "{0} new downloadable content(s) added",
"AutoloadDlcAddedMessage": "{0} new downloadable content(s) added",
"AutoloadDlcRemovedMessage": "{0} missing downloadable content(s) removed",

View File

@@ -788,7 +788,7 @@
"CheatWindowHeading": "可用於 {0} [{1}] 的密技",
"BuildId": "組建識別碼:",
"DlcWindowBundledContentNotice": "附帶的 DLC 只能被停用而無法被刪除。",
"DlcWindowHeading": "{0} 個可下載內容",
"DlcWindowHeading": "{0} DLC(s) available",
"DlcWindowDlcAddedMessage": "已加入 {0} 個 DLC",
"AutoloadDlcAddedMessage": "已加入 {0} 個 DLC",
"AutoloadDlcRemovedMessage": "已刪除 {0} 個遺失的 DLC",

View File

@@ -4,18 +4,6 @@
<ResourceDictionary x:Key="Default">
<SolidColorBrush x:Key="DataGridSelectionBackgroundBrush"
Color="{DynamicResource DataGridSelectionColor}" />
<SolidColorBrush x:Key="ThemeAccentColorBrush"
Color="{DynamicResource SystemAccentColor}" />
<SolidColorBrush x:Key="ThemeAccentBrush4"
Color="{DynamicResource ThemeAccentColor4}" />
<Color x:Key="SystemAccentColor">#FF00C3E3</Color>
<Color x:Key="SystemAccentColorDark1">#FF00C3E3</Color>
<Color x:Key="SystemAccentColorDark2">#FF00C3E3</Color>
<Color x:Key="SystemAccentColorDark3">#FF00C3E3</Color>
<Color x:Key="SystemAccentColorLight1">#FF00C3E3</Color>
<Color x:Key="SystemAccentColorLight2">#FF00C3E3</Color>
<Color x:Key="SystemAccentColorLight3">#FF00C3E3</Color>
<Color x:Key="ThemeAccentColor4">#FFe8e8e8</Color>
<Color x:Key="DataGridSelectionColor">#FF00FABB</Color>
<Color x:Key="ThemeContentBackgroundColor">#FFF0F0F0</Color>
<Color x:Key="ThemeControlBorderColor">#FFd6d6d6</Color>
@@ -26,6 +14,7 @@
<Color x:Key="AppListBackgroundColor">#b3ffffff</Color>
<Color x:Key="AppListHoverBackgroundColor">#80cccccc</Color>
<Color x:Key="SecondaryTextColor">#A0000000</Color>
<Color x:Key="FavoriteApplicationIconColor">#fffcd12a</Color>
<Color x:Key="Switch">#FF2EEAC9</Color>
<Color x:Key="Unbounded">#FFFF4554</Color>
<Color x:Key="Custom">#6483F5</Color>
@@ -33,18 +22,6 @@
<ResourceDictionary x:Key="Light">
<SolidColorBrush x:Key="DataGridSelectionBackgroundBrush"
Color="{DynamicResource DataGridSelectionColor}" />
<SolidColorBrush x:Key="ThemeAccentColorBrush"
Color="{DynamicResource SystemAccentColor}" />
<SolidColorBrush x:Key="ThemeAccentBrush4"
Color="{DynamicResource ThemeAccentColor4}" />
<Color x:Key="SystemAccentColor">#FF00C3E3</Color>
<Color x:Key="SystemAccentColorDark1">#FF00C3E3</Color>
<Color x:Key="SystemAccentColorDark2">#FF00C3E3</Color>
<Color x:Key="SystemAccentColorDark3">#FF00C3E3</Color>
<Color x:Key="SystemAccentColorLight1">#FF00C3E3</Color>
<Color x:Key="SystemAccentColorLight2">#FF00C3E3</Color>
<Color x:Key="SystemAccentColorLight3">#FF00C3E3</Color>
<Color x:Key="ThemeAccentColor4">#FFe8e8e8</Color>
<Color x:Key="DataGridSelectionColor">#FF00FABB</Color>
<Color x:Key="ThemeContentBackgroundColor">#FFF0F0F0</Color>
<Color x:Key="ThemeControlBorderColor">#FFd6d6d6</Color>
@@ -59,18 +36,7 @@
<ResourceDictionary x:Key="Dark">
<SolidColorBrush x:Key="DataGridSelectionBackgroundBrush"
Color="{DynamicResource DataGridSelectionColor}" />
<SolidColorBrush x:Key="ThemeAccentColorBrush"
Color="{DynamicResource SystemAccentColor}" />
<SolidColorBrush x:Key="ThemeAccentBrush4"
Color="{DynamicResource ThemeAccentColor4}" />
<Color x:Key="ControlFillColorSecondary">#008AA8</Color>
<Color x:Key="SystemAccentColor">#FF00C3E3</Color>
<Color x:Key="SystemAccentColorDark1">#FF99b000</Color>
<Color x:Key="SystemAccentColorDark2">#FF006d7d</Color>
<Color x:Key="SystemAccentColorDark3">#FF00525E</Color>
<Color x:Key="SystemAccentColorLight1">#FF00dbff</Color>
<Color x:Key="SystemAccentColorLight2">#FF19dfff</Color>
<Color x:Key="SystemAccentColorLight3">#FF33e3ff</Color>
<Color x:Key="DataGridSelectionColor">#FF00FABB</Color>
<Color x:Key="ThemeContentBackgroundColor">#FF2D2D2D</Color>
<Color x:Key="ThemeControlBorderColor">#FF505050</Color>

View File

@@ -17,13 +17,13 @@ namespace Ryujinx.Ava.Common.Markup
public virtual string Name => "Item";
public virtual Action<object, T?>? Setter => null;
protected abstract T? GetValue();
protected abstract T? Value { get; }
protected virtual void ConfigureBindingExtension(CompiledBindingExtension _) { }
private ClrPropertyInfo PropertyInfo =>
new(Name,
_ => GetValue(),
_ => Value,
Setter as Action<object, object?>,
typeof(T));

View File

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

View File

@@ -91,7 +91,7 @@
HorizontalAlignment="Left"
VerticalAlignment="Top"
FontSize="16"
Foreground="{DynamicResource SystemAccentColor}"
Foreground="{DynamicResource FavoriteApplicationIconColor}"
IsVisible="{Binding Favorite}"
Symbol="StarFilled" />
</Grid>

View File

@@ -146,7 +146,7 @@
HorizontalAlignment="Left"
VerticalAlignment="Top"
FontSize="16"
Foreground="{DynamicResource SystemAccentColor}"
Foreground="{DynamicResource FavoriteApplicationIconColor}"
IsVisible="{Binding Favorite}"
Symbol="StarFilled" />
</Grid>

View File

@@ -13,6 +13,7 @@ using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ava.UI.Windows;
using Ryujinx.Common;
using Ryujinx.Common.Utilities;
using Ryujinx.HLE;
using Ryujinx.UI.App.Common;
using Ryujinx.UI.Common;
using Ryujinx.UI.Common.Configuration;
@@ -126,32 +127,22 @@ namespace Ryujinx.Ava.UI.Views.Main
public async void OpenMiiApplet(object sender, RoutedEventArgs e)
{
const string name = "miiEdit";
const ulong programId = 0x0100000000001009;
string contentPath = ViewModel.ContentManager.GetInstalledContentPath(programId, StorageId.BuiltInSystem, NcaContentType.Program);
const string AppletName = "miiEdit";
const ulong AppletProgramId = 0x0100000000001009;
const string AppletVersion = "1.0.0";
string contentPath = ViewModel.ContentManager.GetInstalledContentPath(AppletProgramId, StorageId.BuiltInSystem, NcaContentType.Program);
if (!string.IsNullOrEmpty(contentPath))
{
ApplicationData applicationData = new()
{
Name = name,
Id = programId,
Path = contentPath,
Name = AppletName,
Id = AppletProgramId,
Path = contentPath
};
string version = "1.0.0";
var nacpData = new BlitStruct<ApplicationControlProperty>(1);
//version buffer
Encoding.ASCII.GetBytes(version).AsSpan().CopyTo(nacpData.ByteSpan.Slice(0x3060));
//name and distributor buffer
//repeat once for each locale (the ApplicationControlProperty has 16 locales)
for (int i = 0; i < 0x10; i++)
{
Encoding.ASCII.GetBytes(name).AsSpan().CopyTo(nacpData.ByteSpan.Slice(i * 0x300));
"Ryujinx"u8.ToArray().AsSpan().CopyTo(nacpData.ByteSpan.Slice(i * 0x300 + 0x200));
}
var nacpData = StructHelpers.CreateCustomNacpData(AppletName, AppletVersion);
await ViewModel.LoadApplication(applicationData, ViewModel.IsFullScreen || ViewModel.StartGamesInFullscreen, nacpData);
}

View File

@@ -61,23 +61,17 @@ namespace Ryujinx.Ava.UI.Windows
private void RemoveDLC(object sender, RoutedEventArgs e)
{
if (sender is Button button)
if (sender is Button { DataContext: DownloadableContentModel dlc })
{
if (button.DataContext is DownloadableContentModel model)
{
ViewModel.Remove(model);
}
ViewModel.Remove(dlc);
}
}
private void OpenLocation(object sender, RoutedEventArgs e)
{
if (sender is Button button)
if (sender is Button { DataContext: DownloadableContentModel dlc })
{
if (button.DataContext is DownloadableContentModel model)
{
OpenHelper.LocateFile(model.ContainerPath);
}
OpenHelper.LocateFile(dlc.ContainerPath);
}
}