Compare commits

...

10 Commits

Author SHA1 Message Date
GreemDev
2aa072fbfa fix: Super Mario Party Jamboree audio renderer crashing
See merge request ryubing/ryujinx!34
2025-05-24 17:00:30 -05:00
KeatonTheBot
1c411082db Optimize XMAD instruction sequence into a single 32-bit multiply when possible
See merge request [ryubing/ryujinx!24](https://git.ryujinx.app/ryubing/ryujinx/-/merge_requests/24)
2025-05-23 17:12:37 -05:00
shinyoyo
be7285f7fc Update Simplified Chinese translation.
See merge request [ryubing/ryujinx!37](https://git.ryujinx.app/ryubing/ryujinx/-/merge_requests/37)
2025-05-23 17:09:38 -05:00
Goodfeat
6f59a14ec6 Small Fix: now it is enough to activate dirty hack in global settings for the option...
See merge request [ryubing/ryujinx!20](https://git.ryujinx.app/ryubing/ryujinx/-/merge_requests/20)
2025-05-23 06:27:33 -05:00
GreemDev
d85ec0eff5 infra: use src.ryujinx.app redirect domain for git repo button 2025-05-20 04:40:46 -05:00
GreemDev
02ad827a94 chore: Remove mention of GitHub in changelog button tooltip 2025-05-20 04:39:36 -05:00
GreemDev
6e7220eb28 chore: specify Nintendo Switch 1 in about window and remove leftover mention of GitHub 2025-05-20 04:37:03 -05:00
GreemDev
7a3a21b0c0 ui: Proper light/dark GitLab logos.
Images from 6c7b7d6fc4
2025-05-20 04:36:31 -05:00
GreemDev
92440afcd7 UI: Show Total Time Played at the bottom of the UI in the status bar next to game total.
Does not show up in-game, and is recalculated every time the game list is reloaded.
2025-05-20 04:19:54 -05:00
GreemDev
df3b5b4bd8 gpu: tweak: Do not log missing Votevtg implementation. 2025-05-20 03:28:03 -05:00
17 changed files with 487 additions and 49 deletions

View File

@@ -1947,6 +1947,31 @@
"zh_TW": "LDN 上在線的玩家數量: {0}"
}
},
{
"ID": "GameListLabelTotalTimePlayed",
"Translations": {
"ar_SA": "",
"de_DE": "Gesamte Spielzeit: {0}",
"el_GR": "Συνολικός χρόνος παιχνιδιού: {0}",
"en_US": "Total Play Time: {0}",
"es_ES": "Tiempo total de juego: {0}",
"fr_FR": "Temps de jeu total: {0}",
"he_IL": "",
"it_IT": "Tempo totale di gioco: {0}",
"ja_JP": "",
"ko_KR": "",
"no_NO": "",
"pl_PL": "Całkowity czas gry: {0}",
"pt_BR": "Tempo total de jogo: {0}",
"ru_RU": "",
"sv_SE": "",
"th_TH": "",
"tr_TR": "Toplam Oyun Süresi: {0}",
"uk_UA": "",
"zh_CN": "总游戏时间: {0}",
"zh_TW": ""
}
},
{
"ID": "GameListContextMenuOpenUserSaveDirectory",
"Translations": {
@@ -15503,23 +15528,23 @@
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Ryujinx is an emulator for the Nintendo Switch™.\nGet all the latest news in our Discord.\nDevelopers interested in contributing can find out more on our GitHub or Discord.",
"en_US": "Ryujinx is an emulator for the Nintendo Switch™ 1.\nGet all the latest news in our Discord.\nDevelopers interested in contributing can find out more on our GitLab or Discord.",
"es_ES": "",
"fr_FR": "Ryujinx est un émulateur pour la Nintendo Switch™.\nObtenez le dernières nouvelles sur le Discord.\nLes développeurs qui veulent contribuer peuvent en savoir plus sur notre GitHub ou Discord.",
"fr_FR": "Ryujinx est un émulateur pour la Nintendo Switch™ 1.\nObtenez le dernières nouvelles sur le Discord.\nLes développeurs qui veulent contribuer peuvent en savoir plus sur notre GitLab ou Discord.",
"he_IL": "",
"it_IT": "Ryujinx è un emulatore della console Nintendo Switch™.\nRimani aggiornato sulle ultime novità nel nostro server Discord.\nGli sviluppatori interessati a contribuire possono trovare maggiori informazioni su Discord o sulla nostra pagina GitHub.",
"it_IT": "Ryujinx è un emulatore della console Nintendo Switch™ 1.\nRimani aggiornato sulle ultime novità nel nostro server Discord.\nGli sviluppatori interessati a contribuire possono trovare maggiori informazioni su Discord o sulla nostra pagina GitLab.",
"ja_JP": "",
"ko_KR": "Ryujinx는 Nintendo Switch™용 에뮬레이터입니다.\n모든 최신 소식을 Discord에서 확인하세요.\n기여에 관심이 있는 개발자는 GitHub 또는 Discord에서 자세한 내용을 확인할 수 있습니다.",
"no_NO": "Ryujinx er en emulator for Nintendo SwitchTM.\nVennligst støtt oss på Patreon.\nFå alle de siste nyhetene på vår Twitter eller Discord.\nUtviklere som er interessert i å bidra kan finne ut mer på GitHub eller Discord.",
"ko_KR": "Ryujinx는 Nintendo Switch™ 1용 에뮬레이터입니다.\n모든 최신 소식을 Discord에서 확인하세요.\n기여에 관심이 있는 개발자는 GitLab 또는 Discord에서 자세한 내용을 확인할 수 있습니다.",
"no_NO": "Ryujinx er en emulator for Nintendo Switch™ 1\nVennligst støtt oss på Patreon.\nFå alle de siste nyhetene på vår Twitter eller Discord.\nUtviklere som er interessert i å bidra kan finne ut mer på GitLab eller Discord.",
"pl_PL": "",
"pt_BR": "Ryujinx é um emulador de Nintendo Switch™.\nReceba todas as últimas notícias em nosso Discord.\nDesenvolvedores interessados em contribuir podem descobrir mais em nosso GitHub ou Discord.",
"ru_RU": "Ryujinx - это эмулятор для Nintendo Switch™.\nПолучайте все последние новости разработки в нашем Discord.\nРазработчики, заинтересованные в участии, могут узнать больше на нашем GitHub или Discord.",
"sv_SE": "Ryujinx är en emulator för Nintendo Switch™.\nFå de senaste nyheterna via vår Discord.\nUtvecklare som är intresserade att bidra kan hitta mer info på vår GitHub eller Discord.",
"pt_BR": "Ryujinx é um emulador de Nintendo Switch™ 1.\nReceba todas as últimas notícias em nosso Discord.\nDesenvolvedores interessados em contribuir podem descobrir mais em nosso GitLab ou Discord.",
"ru_RU": "Ryujinx - это эмулятор для Nintendo Switch™ 1.\nПолучайте все последние новости разработки в нашем Discord.\nРазработчики, заинтересованные в участии, могут узнать больше на нашем GitLab или Discord.",
"sv_SE": "Ryujinx är en emulator för Nintendo Switch™ 1.\nFå de senaste nyheterna via vår Discord.\nUtvecklare som är intresserade att bidra kan hitta mer info på vår GitLab eller Discord.",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Ryujinx — це емулятор для Nintendo Switch™.\nОстанні новини можна отримати в нашому Discord.\nРозробники, що бажають долучитись до розробки та зробити свій внесок, можуть отримати більше інформації на нашому GitHub або в Discord.",
"zh_CN": "Ryujinx 是一个 Nintendo Switch™ 模拟器。\n有兴趣做出贡献的开发者可以在我们的 GitHub 或 Discord 上了解更多信息。\n",
"zh_TW": "Ryujinx 是一款 Nintendo Switch™ 模擬器。\n關注我們的 Discord 取得所有最新消息。\n對於有興趣貢獻的開發者可以在我們的 GitHub 或 Discord 上了解更多資訊。"
"uk_UA": "Ryujinx — це емулятор для Nintendo Switch™ 1.\nОстанні новини можна отримати в нашому Discord.\nРозробники, що бажають долучитись до розробки та зробити свій внесок, можуть отримати більше інформації на нашому GitLab або в Discord.",
"zh_CN": "Ryujinx 是一个 Nintendo Switch™ 1 模拟器。\n有兴趣做出贡献的开发者可以在我们的 GitLab 或 Discord 上了解更多信息。\n",
"zh_TW": "Ryujinx 是一款 Nintendo Switch™ 1 模擬器。\n關注我們的 Discord 取得所有最新消息。\n對於有興趣貢獻的開發者可以在我們的 GitLab 或 Discord 上了解更多資訊。"
}
},
{
@@ -23250,26 +23275,26 @@
{
"ID": "AboutChangelogButton",
"Translations": {
"ar_SA": "عرض سجل التغييرات على غيت هاب",
"de_DE": "Changelog in GitHub öffnen",
"el_GR": "Προβολή αρχείου αλλαγών στο GitHub",
"en_US": "View Changelog on GitHub",
"es_ES": "Ver registro de cambios en GitHub",
"fr_FR": "Voir le Changelog sur GitHub",
"he_IL": "צפה במידע אודות שינויים בגיטהב",
"it_IT": "Visualizza changelog su GitHub",
"ja_JP": "GitHub で更新履歴を表示",
"ko_KR": "GitHub에서 변경 내역 보기",
"no_NO": "Vis endringslogg på GitHub",
"pl_PL": "Zobacz listę zmian na GitHubie",
"pt_BR": "Ver Mudanças no GitHub",
"ru_RU": "Показать список изменений на GitHub",
"sv_SE": "Visa ändringslogg på GitHub",
"th_TH": "ดูประวัติการเปลี่ยนแปลงบน GitHub",
"tr_TR": "GitHub'da Değişiklikleri Görüntüle",
"uk_UA": "Переглянути журнал змін на GitHub",
"zh_CN": "在 Github 上查看更新日志",
"zh_TW": "在 GitHub 上檢視更新日誌"
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "View Changelog",
"es_ES": "",
"fr_FR": "",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "",
"sv_SE": "",
"th_TH": "ด",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "查看更新日志",
"zh_TW": ""
}
},
{
@@ -24748,4 +24773,4 @@
}
}
]
}
}

View File

@@ -81,14 +81,14 @@ namespace Ryujinx.Audio.Renderer.Dsp
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static short GetCoefficientAtIndex(ReadOnlySpan<short> coefficients, int index)
{
if ((uint)index >= (uint)coefficients.Length)
if ((uint)index < (uint)coefficients.Length)
{
Logger.Error?.Print(LogClass.AudioRenderer, $"Out of bound read for coefficient at index {index}");
return 0;
return coefficients[index];
}
return coefficients[index];
Logger.Error?.Print(LogClass.AudioRenderer, $"Out of bound read for coefficient at index {index}");
return 0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]

View File

@@ -331,7 +331,8 @@ namespace Ryujinx.Graphics.Shader.Instructions
{
context.GetOp<InstVotevtg>();
context.TranslatorContext.GpuAccessor.Log("Shader instruction Votevtg is not implemented.");
// This instruction is proprietary and will not be implemented. Commenting it out to avoid false reports.
//context.TranslatorContext.GpuAccessor.Log("Shader instruction Votevtg is not implemented.");
}
public static void Vset(EmitterContext context)

View File

@@ -9,6 +9,11 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
{
public static void RunPass(TransformContext context)
{
for (int blkIndex = 0; blkIndex < context.Blocks.Length; blkIndex++)
{
XmadOptimizer.RunPass(context.Blocks[blkIndex]);
}
RunOptimizationPasses(context.Blocks, context.ResourceManager);
// TODO: Some of those are not optimizations and shouldn't be here.
@@ -355,7 +360,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
operation.TurnIntoCopy(attrMulLhs);
}
private static void RemoveNode(BasicBlock block, LinkedListNode<INode> llNode)
public static void RemoveNode(BasicBlock block, LinkedListNode<INode> llNode)
{
// Remove a node from the nodes list, and also remove itself
// from all the use lists on the operands that this node uses.

View File

@@ -0,0 +1,342 @@
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using System.Collections.Generic;
namespace Ryujinx.Graphics.Shader.Translation.Optimizations
{
static class XmadOptimizer
{
public static void RunPass(BasicBlock block)
{
for (LinkedListNode<INode> node = block.Operations.First; node != null; node = node.Next)
{
if (!(node.Value is Operation operation))
{
continue;
}
if (TryMatchXmadPattern(operation, out Operand x, out Operand y, out Operand addend))
{
LinkedListNode<INode> nextNode;
if (addend != null)
{
Operand temp = OperandHelper.Local();
nextNode = block.Operations.AddAfter(node, new Operation(Instruction.Multiply, temp, x, y));
nextNode = block.Operations.AddAfter(nextNode, new Operation(Instruction.Add, operation.Dest, temp, addend));
}
else
{
nextNode = block.Operations.AddAfter(node, new Operation(Instruction.Multiply, operation.Dest, x, y));
}
Optimizer.RemoveNode(block, node);
node = nextNode;
}
}
}
private static bool TryMatchXmadPattern(Operation operation, out Operand x, out Operand y, out Operand addend)
{
return TryMatchXmad32x32Pattern(operation, out x, out y, out addend) ||
TryMatchXmad32x16Pattern(operation, out x, out y, out addend);
}
private static bool TryMatchXmad32x32Pattern(Operation operation, out Operand x, out Operand y, out Operand addend)
{
x = null;
y = null;
addend = null;
if (operation.Inst != Instruction.Add)
{
return false;
}
Operand src1 = operation.GetSource(0);
Operand src2 = operation.GetSource(1);
if (!(src2.AsgOp is Operation addOp) || addOp.Inst != Instruction.Add)
{
return false;
}
Operand lowTimesLowResult = GetCopySource(addOp.GetSource(0));
if (!(lowTimesLowResult.AsgOp is Operation lowTimesLowOp))
{
return false;
}
if (!TryMatchLowTimesLow(lowTimesLowOp, out x, out y, out addend))
{
return false;
}
Operand lowTimesHighResult = GetCopySource(GetShifted16Source(addOp.GetSource(1), Instruction.ShiftLeft));
if (!(lowTimesHighResult.AsgOp is Operation lowTimesHighOp))
{
return false;
}
if (!TryMatchLowTimesHigh(lowTimesHighOp, x, y))
{
return false;
}
if (!(src1.AsgOp is Operation highTimesHighOp))
{
return false;
}
if (!TryMatchHighTimesHigh(highTimesHighOp, x, lowTimesHighResult))
{
return false;
}
return true;
}
private static bool TryMatchXmad32x16Pattern(Operation operation, out Operand x, out Operand y, out Operand addend)
{
x = null;
y = null;
addend = null;
if (operation.Inst != Instruction.Add)
{
return false;
}
Operand src1 = operation.GetSource(0);
Operand src2 = operation.GetSource(1);
Operand lowTimesLowResult = GetCopySource(src2);
if (!(lowTimesLowResult.AsgOp is Operation lowTimesLowOp))
{
return false;
}
if (!TryMatchLowTimesLow(lowTimesLowOp, out x, out y, out addend))
{
return false;
}
Operand highTimesLowResult = src1;
if (!(highTimesLowResult.AsgOp is Operation highTimesLowOp))
{
return false;
}
if (!TryMatchHighTimesLow(highTimesLowOp, x, y))
{
return false;
}
return y.Type == OperandType.Constant && (ushort)y.Value == y.Value;
}
private static bool TryMatchLowTimesLow(Operation operation, out Operand x, out Operand y, out Operand addend)
{
// x = x & 0xFFFF
// y = y & 0xFFFF
// lowTimesLow = x * y
x = null;
y = null;
addend = null;
if (operation.Inst == Instruction.Add)
{
if (!(operation.GetSource(0).AsgOp is Operation mulOp))
{
return false;
}
addend = operation.GetSource(1);
operation = mulOp;
}
if (operation.Inst != Instruction.Multiply)
{
return false;
}
Operand src1 = GetMasked16Source(operation.GetSource(0));
Operand src2 = GetMasked16Source(operation.GetSource(1));
if (src1 == null || src2 == null)
{
return false;
}
x = src1;
y = src2;
return true;
}
private static bool TryMatchLowTimesHigh(Operation operation, Operand x, Operand y)
{
// xLow = x & 0xFFFF
// yHigh = y >> 16
// lowTimesHigh = xLow * yHigh
// result = (lowTimesHigh & 0xFFFF) | (y << 16)
if (operation.Inst != Instruction.BitwiseOr)
{
return false;
}
Operand mulResult = GetMasked16Source(operation.GetSource(0));
if (mulResult == null)
{
return false;
}
mulResult = GetCopySource(mulResult);
if (!(mulResult.AsgOp is Operation mulOp) || mulOp.Inst != Instruction.Multiply)
{
return false;
}
if (GetMasked16Source(mulOp.GetSource(0)) != x)
{
return false;
}
if (GetShifted16Source(mulOp.GetSource(1), Instruction.ShiftRightU32) != y)
{
return false;
}
if (GetShifted16Source(operation.GetSource(1), Instruction.ShiftLeft) != y)
{
return false;
}
return true;
}
private static bool TryMatchHighTimesLow(Operation operation, Operand x, Operand y)
{
// xHigh = x >> 16
// yLow = y & 0xFFFF
// highTimesLow = xHigh * yLow
// result = highTimesLow << 16
if (operation.Inst != Instruction.ShiftLeft || !IsConst(operation.GetSource(1), 16))
{
return false;
}
Operand mulResult = operation.GetSource(0);
if (!(mulResult.AsgOp is Operation mulOp) || mulOp.Inst != Instruction.Multiply)
{
return false;
}
if (GetShifted16Source(mulOp.GetSource(0), Instruction.ShiftRightU32) != x)
{
return false;
}
Operand src2 = GetMasked16Source(mulOp.GetSource(1));
if (src2.Type != y.Type || src2.Value != y.Value)
{
return false;
}
return true;
}
private static bool TryMatchHighTimesHigh(Operation operation, Operand x, Operand lowTimesHighResult)
{
// xHigh = x >> 16
// lowTimesHighResultHigh = lowTimesHighResult >> 16
// highTimesHigh = xHigh * lowTimesHighResultHigh
// result = highTimesHigh << 16
if (operation.Inst != Instruction.ShiftLeft || !IsConst(operation.GetSource(1), 16))
{
return false;
}
Operand mulResult = operation.GetSource(0);
if (!(mulResult.AsgOp is Operation mulOp) || mulOp.Inst != Instruction.Multiply)
{
return false;
}
if (GetShifted16Source(mulOp.GetSource(0), Instruction.ShiftRightU32) != x)
{
return false;
}
if (GetCopySource(GetShifted16Source(mulOp.GetSource(1), Instruction.ShiftRightU32)) != lowTimesHighResult)
{
return false;
}
return true;
}
private static Operand GetMasked16Source(Operand value)
{
if (!(value.AsgOp is Operation maskOp))
{
return null;
}
if (maskOp.Inst != Instruction.BitwiseAnd || !IsConst(maskOp.GetSource(1), ushort.MaxValue))
{
return null;
}
return maskOp.GetSource(0);
}
private static Operand GetShifted16Source(Operand value, Instruction shiftInst)
{
if (!(value.AsgOp is Operation shiftOp))
{
return null;
}
if (shiftOp.Inst != shiftInst || !IsConst(shiftOp.GetSource(1), 16))
{
return null;
}
return shiftOp.GetSource(0);
}
private static Operand GetCopySource(Operand value)
{
while (value.AsgOp is Operation operation && IsCopy(operation))
{
value = operation.GetSource(0);
}
return value;
}
private static bool IsCopy(Operation operation)
{
return operation.Inst == Instruction.Copy || (operation.Inst == Instruction.Add && IsConst(operation.GetSource(1), 0));
}
private static bool IsConst(Operand operand, int value)
{
return operand.Type == OperandType.Constant && operand.Value == value;
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

View File

@@ -170,7 +170,8 @@
<EmbeddedResource Include="Assets\UIImages\Logo_Amiibo.png" />
<EmbeddedResource Include="Assets\UIImages\Logo_Discord_Dark.png" />
<EmbeddedResource Include="Assets\UIImages\Logo_Discord_Light.png" />
<EmbeddedResource Include="Assets\UIImages\Logo_GitLab.png" />
<EmbeddedResource Include="Assets\UIImages\Logo_GitLab_Dark.png" />
<EmbeddedResource Include="Assets\UIImages\Logo_GitLab_Light.png" />
<EmbeddedResource Include="Assets\UIImages\Logo_Ryujinx.png" />
<EmbeddedResource Include="Assets\UIImages\Logo_Ryujinx_AntiAlias.png" />
</ItemGroup>

View File

@@ -51,6 +51,26 @@ namespace Ryujinx.Ava.Systems.AppLibrary
public readonly IObservableCache<(TitleUpdateModel TitleUpdate, bool IsSelected), TitleUpdateModel> TitleUpdates;
public readonly IObservableCache<(DownloadableContentModel Dlc, bool IsEnabled), DownloadableContentModel> DownloadableContents;
private Gommon.Optional<TimeSpan> _totalTimePlayed;
public Gommon.Optional<TimeSpan> TotalTimePlayed
{
get => _totalTimePlayed;
private set
{
_totalTimePlayed = value;
_totalTimePlayedChanged.Call(value);
}
}
public event Action<Gommon.Optional<TimeSpan>> TotalTimePlayedRecalculated
{
add => _totalTimePlayedChanged.Add(value);
remove => _totalTimePlayedChanged.Remove(value);
}
private readonly Event<Gommon.Optional<TimeSpan>> _totalTimePlayedChanged = new();
private readonly byte[] _nspIcon;
private readonly byte[] _xciIcon;
private readonly byte[] _ncaIcon;
@@ -825,6 +845,22 @@ namespace Ryujinx.Ava.Systems.AppLibrary
}
}
public Task RefreshTotalTimePlayedAsync()
{
TotalTimePlayed = Gommon.Optional<TimeSpan>.None;
TimeSpan temporary = TimeSpan.Zero;
foreach (var installedApplication in Applications.Items)
{
temporary += LoadAndSaveMetaData(installedApplication.IdString).TimePlayed;
}
TotalTimePlayed = temporary;
return Task.CompletedTask;
}
public async Task RefreshLdn()
{
if (ConfigurationState.Instance.Multiplayer.Mode == MultiplayerMode.LdnRyu)

View File

@@ -157,8 +157,8 @@ namespace Ryujinx.Ava.Systems.Configuration
Multiplayer.LdnServer.Value = cff.LdnServer;
{
Hacks.ShowDirtyHacks.Value = cff.ShowDirtyHacks;
Hacks.ShowDirtyHacks.Value = shouldLoadFromFile ? cff.ShowDirtyHacks: Hacks.ShowDirtyHacks.Value; // Get from global config only
DirtyHacks hacks = new (cff.DirtyHacks ?? []);
Hacks.Xc2MenuSoftlockFix.Value = hacks.IsEnabled(DirtyHack.Xc2MenuSoftlockFix);

View File

@@ -24,8 +24,6 @@ namespace Ryujinx.Ava.UI.ViewModels
Version = RyujinxApp.FullAppName + "\n" + Program.Version;
UpdateLogoTheme(ConfigurationState.Instance.UI.BaseStyle.Value);
GitLabLogo = LoadBitmap("resm:Ryujinx.Assets.UIImages.Logo_GitLab.png?assembly=Ryujinx");
RyujinxApp.ThemeChanged += Ryujinx_ThemeChanged;
}
@@ -43,6 +41,7 @@ namespace Ryujinx.Ava.UI.ViewModels
string themeName = isDarkTheme ? "Dark" : "Light";
DiscordLogo = LoadBitmap(LogoPathFormat.Format("Discord", themeName));
GitLabLogo = LoadBitmap(LogoPathFormat.Format("GitLab", themeName));
}
private static Bitmap LoadBitmap(string uri) => new(Avalonia.Platform.AssetLoader.Open(new Uri(uri)));

View File

@@ -25,6 +25,7 @@ using Ryujinx.Ava.UI.Renderer;
using Ryujinx.Ava.UI.Windows;
using Ryujinx.Ava.Systems.AppLibrary;
using Ryujinx.Ava.Systems.Configuration;
using Ryujinx.Ava.Utilities;
using Ryujinx.Common;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Helper;
@@ -112,6 +113,7 @@ namespace Ryujinx.Ava.UI.ViewModels
await Updater.BeginUpdateAsync(true);
});
private bool _showTotalTimePlayed;
private bool _showLoadProgress;
private bool _isGameRunning;
private bool _isAmiiboRequested;
@@ -197,6 +199,8 @@ namespace Ryujinx.Ava.UI.ViewModels
#if DEBUG
topLevel.AttachDevTools(new KeyGesture(Avalonia.Input.Key.F12, KeyModifiers.Control));
#endif
Window.ApplicationLibrary.TotalTimePlayedRecalculated += TotalTimePlayed_Recalculated;
}
#region Properties
@@ -299,6 +303,24 @@ namespace Ryujinx.Ava.UI.ViewModels
OnPropertyChanged(nameof(ShowFirmwareStatus));
}
}
private void TotalTimePlayed_Recalculated(Optional<TimeSpan> ts)
{
ShowTotalTimePlayed = ts.HasValue;
if (ts.HasValue)
LocaleManager.Instance.SetDynamicValues(LocaleKeys.GameListLabelTotalTimePlayed, ValueFormatUtils.FormatTimeSpan(ts.Value));
}
public bool ShowTotalTimePlayed
{
get => _showTotalTimePlayed && EnableNonGameRunningControls;
set
{
_showTotalTimePlayed = value;
OnPropertyChanged();
}
}
public ApplicationData ListSelectedApplication
{

View File

@@ -113,6 +113,7 @@
Background="Transparent"
Click="Button_OnClick"
CornerRadius="15"
Tag="https://src.ryujinx.app"
ToolTip.Tip="{ext:Locale AboutGitLabUrlTooltipMessage}">
<Image Source="{Binding GitLabLogo}" />
</Button>

View File

@@ -6,7 +6,6 @@ using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Common;
using Ryujinx.Common.Helper;
using System.Threading.Tasks;
using Button = Avalonia.Controls.Button;
@@ -18,9 +17,6 @@ namespace Ryujinx.Ava.UI.Views.Dialog
public AboutView()
{
InitializeComponent();
GitRepoButton.Tag =
$"https://git.ryujinx.app/{ReleaseInformation.ReleaseChannelOwner}/{ReleaseInformation.ReleaseChannelSourceRepo}";
}
public static async Task Show()

View File

@@ -29,7 +29,7 @@
Margin="5"
VerticalAlignment="Center"
IsVisible="{Binding EnableNonGameRunningControls}">
<Grid Margin="0" ColumnDefinitions="Auto,Auto,Auto,*">
<Grid Margin="0" ColumnDefinitions="Auto,Auto,Auto,*,Auto,Auto,*">
<Button
Width="25"
Height="25"
@@ -68,6 +68,14 @@
IsVisible="{Binding StatusBarVisible}"
Maximum="{Binding StatusBarProgressMaximum}"
Value="{Binding StatusBarProgressValue}" />
<controls:MiniVerticalSeparator Grid.Column="4" IsVisible="{Binding ShowTotalTimePlayed}" />
<TextBlock
Grid.Column="5"
Margin="5,0,5,0"
VerticalAlignment="Center"
IsVisible="{Binding ShowTotalTimePlayed}"
Text="{ext:Locale GameListLabelTotalTimePlayed}">
</TextBlock>
</Grid>
</StackPanel>
<StackPanel

View File

@@ -717,6 +717,8 @@ namespace Ryujinx.Ava.UI.Windows
ShowNewContentAddedDialog(dlcLoaded, dlcRemoved, updatesLoaded, updatesRemoved);
}
Executor.ExecuteBackgroundAsync(ApplicationLibrary.RefreshTotalTimePlayedAsync);
_isLoading = false;
})