feature: UI: LDN Games Viewer

This window can be accessed via "Help" menu in the title bar.
This menu's data is synced with the in-app-list LDN game data, and that has been modified to hide unjoinable games (in-progress and/or private (needing a passphrase)). You can still see these games in the list.
This commit is contained in:
GreemDev
2025-08-30 19:54:00 -05:00
parent da340f5615
commit 6e47d8548c
28 changed files with 1574 additions and 132 deletions

View File

@@ -1169,10 +1169,8 @@ namespace Ryujinx.Ava.Systems
string frameTime = Device.Statistics.GetGameFrameTime().ToString("00.00");
return Device.TurboMode
? LocaleManager.GetUnformatted(LocaleKeys.FpsTurboStatusBarText)
.Format(frameRate, frameTime, Device.TickScalar)
: LocaleManager.GetUnformatted(LocaleKeys.FpsStatusBarText)
.Format(frameRate, frameTime);
? LocaleManager.GetFormatted(LocaleKeys.FpsTurboStatusBarText, frameRate, frameTime, Device.TickScalar)
: LocaleManager.GetFormatted(LocaleKeys.FpsStatusBarText, frameRate, frameTime);
}
public async Task ShowExitPrompt()

View File

@@ -14,6 +14,7 @@ using LibHac.Tools.FsSystem.NcaUtils;
using Ryujinx.Ava.Common.Models;
using Ryujinx.Ava.Systems.Configuration;
using Ryujinx.Ava.Systems.Configuration.System;
using Ryujinx.Ava.UI.Models;
using Ryujinx.Ava.Utilities;
using Ryujinx.Common;
using Ryujinx.Common.Configuration;
@@ -29,7 +30,6 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading;
@@ -85,7 +85,6 @@ namespace Ryujinx.Ava.Systems.AppLibrary
private readonly SourceCache<(DownloadableContentModel Dlc, bool IsEnabled), DownloadableContentModel> _downloadableContents = new(it => it.Dlc);
private static readonly ApplicationJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions());
private static readonly LdnGameDataSerializerContext _ldnDataSerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
public ApplicationLibrary(VirtualFileSystem virtualFileSystem, IntegrityCheckLevel checkLevel)
{
@@ -865,16 +864,7 @@ namespace Ryujinx.Ava.Systems.AppLibrary
{
try
{
string ldnWebHost = ConfigurationState.Instance.Multiplayer.LdnServer;
if (string.IsNullOrEmpty(ldnWebHost))
{
ldnWebHost = SharedConstants.DefaultLanPlayWebHost;
}
using HttpClient httpClient = new();
string ldnGameDataArrayString = await httpClient.GetStringAsync($"https://{ldnWebHost}/api/public_games");
LdnGameData[] ldnGameDataArray = JsonHelper.Deserialize(ldnGameDataArrayString, _ldnDataSerializerContext.IEnumerableLdnGameData).ToArray();
LdnGameDataReceived?.Invoke(new LdnGameDataReceivedEventArgs(ldnGameDataArray));
LdnGameDataReceived?.Invoke(new LdnGameDataReceivedEventArgs(await LdnGameModel.GetAllAsync()));
return;
}
catch (Exception ex)

View File

@@ -1,49 +0,0 @@
using Gommon;
using LibHac.Ns;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Ryujinx.Ava.Systems.AppLibrary
{
public struct LdnGameData
{
public string Id { get; set; }
public int PlayerCount { get; set; }
public int MaxPlayerCount { get; set; }
public string GameName { get; set; }
public string TitleId { get; set; }
public string Mode { get; set; }
public string Status { get; set; }
public IEnumerable<string> Players { get; set; }
public static Array GetArrayForApp(
LdnGameData[] receivedData, ref ApplicationControlProperty acp)
{
LibHac.Common.FixedArrays.Array8<ulong> communicationId = acp.LocalCommunicationId;
return new Array(receivedData.Where(game =>
communicationId.AsReadOnlySpan().Contains(game.TitleId.ToULong())
));
}
public class Array
{
private readonly LdnGameData[] _ldnDatas;
internal Array(IEnumerable<LdnGameData> receivedData)
{
_ldnDatas = receivedData.ToArray();
}
public int PlayerCount => _ldnDatas.Sum(it => it.PlayerCount);
public int GameCount => _ldnDatas.Length;
}
}
public static class LdnGameDataHelper
{
public static LdnGameData.Array Where(this LdnGameData[] unfilteredDatas, ref ApplicationControlProperty acp)
=> LdnGameData.GetArrayForApp(unfilteredDatas, ref acp);
}
}

View File

@@ -1,4 +1,7 @@
using Ryujinx.Ava.UI.Models;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Ryujinx.Ava.Systems.AppLibrary
{
@@ -6,12 +9,17 @@ namespace Ryujinx.Ava.Systems.AppLibrary
{
public static new readonly LdnGameDataReceivedEventArgs Empty = new(null);
public LdnGameDataReceivedEventArgs(LdnGameData[] ldnData)
public LdnGameDataReceivedEventArgs(LdnGameModel[] ldnData)
{
LdnData = ldnData ?? [];
}
public LdnGameDataReceivedEventArgs(IEnumerable<LdnGameModel> ldnData)
{
LdnData = ldnData?.ToArray() ?? [];
}
public LdnGameData[] LdnData { get; set; }
public LdnGameModel[] LdnData { get; }
}
}

View File

@@ -1,8 +0,0 @@
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace Ryujinx.Ava.Systems.AppLibrary
{
[JsonSerializable(typeof(IEnumerable<LdnGameData>))]
internal partial class LdnGameDataSerializerContext : JsonSerializerContext;
}

View File

@@ -11,6 +11,7 @@ using Ryujinx.Common.Helper;
using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities;
using Ryujinx.HLE;
using System;
using System.Collections.Generic;
using System.Linq;
using RyuLogger = Ryujinx.Common.Logging.Logger;
@@ -689,6 +690,16 @@ namespace Ryujinx.Ava.Systems.Configuration
: ldnServer;
}
public string GetLdnWebServer()
{
if (Environment.GetEnvironmentVariable("RYULDN_WEB_HOST") is not { } ldnWebServer)
ldnWebServer = LdnServer;
return string.IsNullOrEmpty(ldnWebServer)
? SharedConstants.DefaultLanPlayWebHost
: ldnWebServer;
}
public MultiplayerSection()
{
LanInterfaceId = new ReactiveObject<string>();

View File

@@ -1,11 +1,9 @@
using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Utilities;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
namespace Ryujinx.Ava.Systems