mirror of
https://git.ryujinx.app/ryubing/ryujinx.git
synced 2026-05-08 14:25:47 +00:00
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:
180
src/Ryujinx/UI/Models/LdnGameModel.cs
Normal file
180
src/Ryujinx/UI/Models/LdnGameModel.cs
Normal file
@@ -0,0 +1,180 @@
|
||||
using Gommon;
|
||||
using Humanizer;
|
||||
using LibHac.Ns;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.Systems.Configuration;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Models
|
||||
{
|
||||
public record LdnGameModel
|
||||
{
|
||||
public string Id { get; private init; }
|
||||
public bool IsPublic { get; private init; }
|
||||
public short PlayerCount { get; private init; }
|
||||
public short MaxPlayerCount { get; private init; }
|
||||
public TitleTuple Title { get; private init; }
|
||||
public ConnectionType ConnectionType { get; private init; }
|
||||
public bool IsJoinable { get; private init; }
|
||||
public ushort SceneId { get; private init; }
|
||||
public string[] Players { get; private init; }
|
||||
|
||||
public string PlayersLabel =>
|
||||
LocaleManager.GetFormatted(LocaleKeys.LdnGameListPlayersAndPlayerCount, PlayerCount, MaxPlayerCount);
|
||||
|
||||
public string FormattedPlayers =>
|
||||
Players.Chunk(4)
|
||||
.Select(x => x.FormatCollection(s => s, prefix: " ", separator: ", "))
|
||||
.JoinToString("\n ");
|
||||
|
||||
public DateTimeOffset CreatedAt { get; init; }
|
||||
|
||||
public string FormattedCreatedAt
|
||||
=> LocaleManager.GetFormatted(LocaleKeys.LdnGameListCreatedAt, CreatedAt.Humanize());
|
||||
|
||||
public string CreatedAtToolTip => CreatedAt.DateTime.ToString(CultureInfo.CurrentUICulture);
|
||||
|
||||
public LocaleKeys ConnectionTypeLocaleKey => ConnectionType switch
|
||||
{
|
||||
ConnectionType.MasterServerProxy => LocaleKeys.LdnGameListConnectionTypeMasterServerProxy,
|
||||
ConnectionType.PeerToPeer => LocaleKeys.LdnGameListConnectionTypeP2P,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(ConnectionType),
|
||||
$"Expected either 'P2P' or 'Master Server Proxy' ConnectionType; got '{ConnectionType}'")
|
||||
};
|
||||
|
||||
public LocaleKeys ConnectionTypeToolTipLocaleKey => ConnectionType switch
|
||||
{
|
||||
ConnectionType.MasterServerProxy => LocaleKeys.LdnGameListConnectionTypeMasterServerProxyToolTip,
|
||||
ConnectionType.PeerToPeer => LocaleKeys.LdnGameListConnectionTypeP2PToolTip,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(ConnectionType),
|
||||
$"Expected either 'P2P' or 'Master Server Proxy' ConnectionType; got '{ConnectionType}'")
|
||||
};
|
||||
|
||||
public record struct TitleTuple
|
||||
{
|
||||
public required string Name { get; init; }
|
||||
public required string Id { get; init; }
|
||||
public required string Version { get; init; }
|
||||
}
|
||||
|
||||
public static Array GetArrayForApp(
|
||||
LdnGameModel[] receivedData,
|
||||
ref ApplicationControlProperty acp,
|
||||
bool onlyJoinable = true,
|
||||
bool onlyPublic = true)
|
||||
{
|
||||
LibHac.Common.FixedArrays.Array8<ulong> communicationId = acp.LocalCommunicationId;
|
||||
|
||||
return new Array(receivedData.Where(game =>
|
||||
communicationId.AsReadOnlySpan().Contains(game.Title.Id.ToULong())
|
||||
), onlyJoinable, onlyPublic);
|
||||
}
|
||||
|
||||
public class Array : IEnumerable<LdnGameModel>
|
||||
{
|
||||
private readonly LdnGameModel[] _ldnDatas;
|
||||
|
||||
internal Array(IEnumerable<LdnGameModel> receivedData, bool onlyJoinable = false, bool onlyPublic = false)
|
||||
{
|
||||
if (onlyJoinable)
|
||||
receivedData = receivedData.Where(x => x.IsJoinable);
|
||||
|
||||
if (onlyPublic)
|
||||
receivedData = receivedData.Where(x => x.IsPublic);
|
||||
|
||||
_ldnDatas = receivedData.ToArray();
|
||||
}
|
||||
|
||||
public int PlayerCount => _ldnDatas.Sum(it => it.PlayerCount);
|
||||
public int GameCount => _ldnDatas.Length;
|
||||
|
||||
public IEnumerator<LdnGameModel> GetEnumerator() => (_ldnDatas as IEnumerable<LdnGameModel>).GetEnumerator();
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => _ldnDatas.GetEnumerator();
|
||||
}
|
||||
|
||||
public static async Task<IEnumerable<LdnGameModel>> GetAllAsync(HttpClient client = null)
|
||||
=> LdnGameJsonModel.ParseArray(await GetAllAsyncRequestImpl(client))
|
||||
.Select(FromJson);
|
||||
|
||||
private static async Task<string> GetAllAsyncRequestImpl(HttpClient client = null)
|
||||
{
|
||||
var ldnWebHost = ConfigurationState.Instance.Multiplayer.GetLdnWebServer();
|
||||
|
||||
LocaleManager.Associate(LocaleKeys.LdnGameListRefreshToolTip, ldnWebHost);
|
||||
|
||||
try
|
||||
{
|
||||
if (client != null)
|
||||
return await client.GetStringAsync($"https://{ldnWebHost}/api/public_games");
|
||||
|
||||
using HttpClient httpClient = new();
|
||||
return await httpClient.GetStringAsync($"https://{ldnWebHost}/api/public_games");
|
||||
}
|
||||
catch
|
||||
{
|
||||
return "[]";
|
||||
}
|
||||
}
|
||||
|
||||
private static LdnGameModel FromJson(LdnGameJsonModel json) =>
|
||||
new()
|
||||
{
|
||||
Id = json.Id,
|
||||
IsPublic = json.IsPublic,
|
||||
PlayerCount = json.PlayerCount,
|
||||
MaxPlayerCount = json.MaxPlayerCount,
|
||||
Title = new TitleTuple { Name = json.TitleName, Id = json.TitleId, Version = json.TitleVersion },
|
||||
ConnectionType = json.ConnectionType switch
|
||||
{
|
||||
"P2P" => ConnectionType.PeerToPeer,
|
||||
"Master Server Proxy" => ConnectionType.MasterServerProxy,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(json),
|
||||
$"Expected either 'P2P' or 'Master Server Proxy' ConnectionType; got '{json.ConnectionType}'")
|
||||
},
|
||||
IsJoinable = json.Joinability is "Joinable",
|
||||
SceneId = json.SceneId,
|
||||
Players = json.Players,
|
||||
CreatedAt = DateTimeOffset.FromUnixTimeMilliseconds(json.CreatedAtUnixTimestamp).ToLocalTime()
|
||||
};
|
||||
}
|
||||
|
||||
public class LdnGameJsonModel
|
||||
{
|
||||
[JsonPropertyName("id")] public string Id { get; set; }
|
||||
[JsonPropertyName("is_public")] public bool IsPublic { get; set; }
|
||||
[JsonPropertyName("player_count")] public short PlayerCount { get; set; }
|
||||
[JsonPropertyName("max_player_count")] public short MaxPlayerCount { get; set; }
|
||||
[JsonPropertyName("game_name")] public string TitleName { get; set; }
|
||||
[JsonPropertyName("title_id")] public string TitleId { get; set; }
|
||||
[JsonPropertyName("title_version")] public string TitleVersion { get; set; }
|
||||
[JsonPropertyName("mode")] public string ConnectionType { get; set; }
|
||||
[JsonPropertyName("status")] public string Joinability { get; set; }
|
||||
[JsonPropertyName("scene_id")] public ushort SceneId { get; set; }
|
||||
[JsonPropertyName("players")] public string[] Players { get; set; }
|
||||
[JsonPropertyName("created_at")] public long CreatedAtUnixTimestamp { get; set; }
|
||||
|
||||
public static LdnGameJsonModel Parse(string value)
|
||||
=> JsonHelper.Deserialize(value, LdnGameJsonModelSerializerContext.Default.LdnGameJsonModel);
|
||||
|
||||
public static LdnGameJsonModel[] ParseArray(string value)
|
||||
=> JsonHelper.Deserialize(value, LdnGameJsonModelSerializerContext.Default.LdnGameJsonModelArray);
|
||||
}
|
||||
|
||||
public enum ConnectionType
|
||||
{
|
||||
PeerToPeer,
|
||||
MasterServerProxy
|
||||
}
|
||||
|
||||
[JsonSerializable(typeof(LdnGameJsonModel[]))]
|
||||
partial class LdnGameJsonModelSerializerContext : JsonSerializerContext;
|
||||
}
|
||||
Reference in New Issue
Block a user