diff --git a/docs/compatibility.csv b/docs/compatibility.csv index 5f6ead1eb..a3c9aa619 100644 --- a/docs/compatibility.csv +++ b/docs/compatibility.csv @@ -1436,7 +1436,7 @@ 010083A018262000,"Hitman: Blood Money — Reprisal",deadlock,ingame,2024-09-28 16:28:50 01004B100A5CC000,"Hob: The Definitive Edition",,playable,2021-01-13 09:39:19 0100F7300ED2C000,"Hoggy2",,playable,2022-10-10 13:53:35 -0100F7E00C70E000,"Hogwarts Legacy",slow,ingame,2024-09-03 19:53:58 +0100F7E00C70E000,"Hogwarts Legacy",UE4;slow,ingame,2024-09-03 19:53:58 0100633007D48000,"Hollow Knight",nvdec,playable,2023-01-16 15:44:56 0100F2100061E800,"Hollow0",UE4;gpu,ingame,2021-03-03 23:42:56 0100342009E16000,"Holy Potatoes! What The Hell?!",,playable,2020-07-03 10:48:56 diff --git a/src/Ryujinx.Common/SharedConstants.cs b/src/Ryujinx.Common/SharedConstants.cs new file mode 100644 index 000000000..f40afeb2b --- /dev/null +++ b/src/Ryujinx.Common/SharedConstants.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Common +{ + public static class SharedConstants + { + public const string DefaultLanPlayHost = "ryuldn.vudjun.com"; + public const short LanPlayPort = 30456; + public const string DefaultLanPlayWebHost = "ryuldnweb.vudjun.com"; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/IUserLocalCommunicationService.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/IUserLocalCommunicationService.cs index 5c6ee7732..9f7e6206b 100644 --- a/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/IUserLocalCommunicationService.cs +++ b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/IUserLocalCommunicationService.cs @@ -23,9 +23,6 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator { class IUserLocalCommunicationService : IpcService, IDisposable { - public static string DefaultLanPlayHost = "ryuldn.vudjun.com"; - public static short LanPlayPort = 30456; - public INetworkClient NetworkClient { get; private set; } private const int NifmRequestID = 90; @@ -1092,20 +1089,18 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator case MultiplayerMode.LdnRyu: try { - string ldnServer = context.Device.Configuration.MultiplayerLdnServer; - if (string.IsNullOrEmpty(ldnServer)) - { - ldnServer = DefaultLanPlayHost; - } + string ldnServer = context.Device.Configuration.MultiplayerLdnServer + ?? throw new InvalidOperationException("Cannot initialize RyuLDN with a null Multiplayer server."); + if (!IPAddress.TryParse(ldnServer, out IPAddress ipAddress)) { ipAddress = Dns.GetHostEntry(ldnServer).AddressList[0]; } - NetworkClient = new LdnMasterProxyClient(ipAddress.ToString(), LanPlayPort, context.Device.Configuration); + NetworkClient = new LdnMasterProxyClient(ipAddress.ToString(), SharedConstants.LanPlayPort, context.Device.Configuration); } catch (Exception ex) { - Logger.Error?.Print(LogClass.ServiceLdn, "Could not locate LdnRyu server. Defaulting to stubbed wireless."); + Logger.Error?.Print(LogClass.ServiceLdn, "Could not locate RyuLDN server. Defaulting to stubbed wireless."); Logger.Error?.Print(LogClass.ServiceLdn, ex.Message); NetworkClient = new LdnDisabledClient(); } diff --git a/src/Ryujinx.Input.SDL2/SDL2Gamepad.cs b/src/Ryujinx.Input.SDL2/SDL2Gamepad.cs index 3b243eed0..6f1b79d38 100644 --- a/src/Ryujinx.Input.SDL2/SDL2Gamepad.cs +++ b/src/Ryujinx.Input.SDL2/SDL2Gamepad.cs @@ -111,7 +111,7 @@ namespace Ryujinx.Input.SDL2 byte blue = packedRgb > 0 ? (byte)(packedRgb % 256) : (byte)0; if (SDL_GameControllerSetLED(_gamepadHandle, red, green, blue) != 0) - Logger.Error?.Print(LogClass.Hid, "LED setting failed; probably in the middle of disconnecting."); + Logger.Debug?.Print(LogClass.Hid, "LED setting failed; probably in the middle of disconnecting."); } private GamepadFeaturesFlag GetFeaturesFlag() diff --git a/src/Ryujinx/AppHost.cs b/src/Ryujinx/AppHost.cs index b26921e6a..4147b8a5e 100644 --- a/src/Ryujinx/AppHost.cs +++ b/src/Ryujinx/AppHost.cs @@ -951,7 +951,7 @@ namespace Ryujinx.Ava ConfigurationState.Instance.Multiplayer.Mode, ConfigurationState.Instance.Multiplayer.DisableP2p, ConfigurationState.Instance.Multiplayer.LdnPassphrase, - ConfigurationState.Instance.Multiplayer.LdnServer, + ConfigurationState.Instance.Multiplayer.GetLdnServer(), ConfigurationState.Instance.Graphics.CustomVSyncInterval.Value, ConfigurationState.Instance.Hacks.ShowDirtyHacks ? ConfigurationState.Instance.Hacks.EnabledHacks : null)); } diff --git a/src/Ryujinx/Assets/locales.json b/src/Ryujinx/Assets/locales.json index 40319866a..1a050e647 100644 --- a/src/Ryujinx/Assets/locales.json +++ b/src/Ryujinx/Assets/locales.json @@ -468,7 +468,7 @@ "th_TH": "", "tr_TR": "", "uk_UA": "Відкрити теку скріншотів", - "zh_CN": "", + "zh_CN": "打开截图文件夹", "zh_TW": "" } }, @@ -5143,7 +5143,7 @@ "th_TH": "", "tr_TR": "", "uk_UA": "Ігнорувати Аплет Контролера", - "zh_CN": "", + "zh_CN": "忽略控制器小程序", "zh_TW": "" } }, @@ -16618,7 +16618,7 @@ "th_TH": "", "tr_TR": "", "uk_UA": "Діалогове вікно Аплету Контролера не з'явиться, якщо геймпад було відключено під час роботи програми.\n\nЗалиште вимкненим якщо не впевнені.", - "zh_CN": "", + "zh_CN": "在应用程序运行时如果游戏手柄断开连接则不会显示控制器小程序对话框。\n\n如果不确定,请保持关闭状态。", "zh_TW": "" } }, @@ -17293,7 +17293,7 @@ "th_TH": "", "tr_TR": "", "uk_UA": "Відкрити теку куди зберігаються скріншоти Ryujinx", - "zh_CN": "", + "zh_CN": "打开 Ryujinx 截图文件夹", "zh_TW": "" } }, diff --git a/src/Ryujinx/UI/Windows/MainWindow.axaml.cs b/src/Ryujinx/UI/Windows/MainWindow.axaml.cs index c5f886ff3..08b2a0585 100644 --- a/src/Ryujinx/UI/Windows/MainWindow.axaml.cs +++ b/src/Ryujinx/UI/Windows/MainWindow.axaml.cs @@ -148,8 +148,11 @@ namespace Ryujinx.Ava.UI.Windows { if ((firmwarePath.ExistsAsFile && firmwarePath.Extension is "xci" or "zip") || firmwarePath.ExistsAsDirectory) + { await Dispatcher.UIThread.InvokeAsync(() => ViewModel.HandleFirmwareInstallation(firmwarePath)); + CommandLineState.FirmwareToInstallPathArg = null; + } else Logger.Notice.Print(LogClass.UI, "Invalid firmware type provided. Path must be a directory, or a .zip or .xci file."); } @@ -186,17 +189,12 @@ namespace Ryujinx.Ava.UI.Windows { Dispatcher.UIThread.Post(() => { - List ldnGameDataArray = e.LdnData.ToList(); ViewModel.LdnData.Clear(); foreach (ApplicationData application in ViewModel.Applications.Where(it => it.HasControlHolder)) { ref ApplicationControlProperty controlHolder = ref application.ControlHolder.Value; - - ViewModel.LdnData[application.IdString] = - LdnGameData.GetArrayForApp( - ldnGameDataArray, - ref controlHolder - ); + + ViewModel.LdnData[application.IdString] = e.LdnData.Where(ref controlHolder); UpdateApplicationWithLdnData(application); } diff --git a/src/Ryujinx/Utilities/AppLibrary/ApplicationLibrary.cs b/src/Ryujinx/Utilities/AppLibrary/ApplicationLibrary.cs index 79cac1a0e..260fabee5 100644 --- a/src/Ryujinx/Utilities/AppLibrary/ApplicationLibrary.cs +++ b/src/Ryujinx/Utilities/AppLibrary/ApplicationLibrary.cs @@ -42,7 +42,6 @@ namespace Ryujinx.Ava.Utilities.AppLibrary { public class ApplicationLibrary { - public const string DefaultLanPlayWebHost = "ryuldnweb.vudjun.com"; public Language DesiredLanguage { get; set; } public event EventHandler ApplicationCountUpdated; public event Action LdnGameDataReceived; @@ -826,7 +825,6 @@ namespace Ryujinx.Ava.Utilities.AppLibrary public async Task RefreshLdn() { - if (ConfigurationState.Instance.Multiplayer.Mode == MultiplayerMode.LdnRyu) { try @@ -834,33 +832,22 @@ namespace Ryujinx.Ava.Utilities.AppLibrary string ldnWebHost = ConfigurationState.Instance.Multiplayer.LdnServer; if (string.IsNullOrEmpty(ldnWebHost)) { - ldnWebHost = DefaultLanPlayWebHost; + ldnWebHost = SharedConstants.DefaultLanPlayWebHost; } - IEnumerable ldnGameDataArray = Array.Empty(); + using HttpClient httpClient = new(); string ldnGameDataArrayString = await httpClient.GetStringAsync($"https://{ldnWebHost}/api/public_games"); - ldnGameDataArray = JsonHelper.Deserialize(ldnGameDataArrayString, _ldnDataSerializerContext.IEnumerableLdnGameData); - LdnGameDataReceived?.Invoke(new LdnGameDataReceivedEventArgs - { - LdnData = ldnGameDataArray - }); + LdnGameData[] ldnGameDataArray = JsonHelper.Deserialize(ldnGameDataArrayString, _ldnDataSerializerContext.IEnumerableLdnGameData).ToArray(); + LdnGameDataReceived?.Invoke(new LdnGameDataReceivedEventArgs(ldnGameDataArray)); + return; } catch (Exception ex) { Logger.Warning?.Print(LogClass.Application, $"Failed to fetch the public games JSON from the API. Player and game count in the game list will be unavailable.\n{ex.Message}"); - LdnGameDataReceived?.Invoke(new LdnGameDataReceivedEventArgs - { - LdnData = Array.Empty() - }); } } - else - { - LdnGameDataReceived?.Invoke(new LdnGameDataReceivedEventArgs - { - LdnData = Array.Empty() - }); - } + + LdnGameDataReceived?.Invoke(LdnGameDataReceivedEventArgs.Empty); } // Replace the currently stored DLC state for the game with the provided DLC state. diff --git a/src/Ryujinx/Utilities/AppLibrary/LdnGameData.cs b/src/Ryujinx/Utilities/AppLibrary/LdnGameData.cs index 4b9b8fe6c..252c0ecdc 100644 --- a/src/Ryujinx/Utilities/AppLibrary/LdnGameData.cs +++ b/src/Ryujinx/Utilities/AppLibrary/LdnGameData.cs @@ -18,7 +18,7 @@ namespace Ryujinx.Ava.Utilities.AppLibrary public IEnumerable Players { get; set; } public static Array GetArrayForApp( - IEnumerable receivedData, ref ApplicationControlProperty acp) + LdnGameData[] receivedData, ref ApplicationControlProperty acp) { LibHac.Common.FixedArrays.Array8 communicationId = acp.LocalCommunicationId; @@ -40,4 +40,10 @@ namespace Ryujinx.Ava.Utilities.AppLibrary 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); + } } diff --git a/src/Ryujinx/Utilities/AppLibrary/LdnGameDataReceivedEventArgs.cs b/src/Ryujinx/Utilities/AppLibrary/LdnGameDataReceivedEventArgs.cs index 97299c9e8..0eaa6ecb3 100644 --- a/src/Ryujinx/Utilities/AppLibrary/LdnGameDataReceivedEventArgs.cs +++ b/src/Ryujinx/Utilities/AppLibrary/LdnGameDataReceivedEventArgs.cs @@ -5,6 +5,14 @@ namespace Ryujinx.Ava.Utilities.AppLibrary { public class LdnGameDataReceivedEventArgs : EventArgs { - public IEnumerable LdnData { get; set; } + public static new readonly LdnGameDataReceivedEventArgs Empty = new(null); + + public LdnGameDataReceivedEventArgs(LdnGameData[] ldnData) + { + LdnData = ldnData ?? []; + } + + + public LdnGameData[] LdnData { get; set; } } } diff --git a/src/Ryujinx/Utilities/CommandLineState.cs b/src/Ryujinx/Utilities/CommandLineState.cs index d6e8f669c..71c7b5e73 100644 --- a/src/Ryujinx/Utilities/CommandLineState.cs +++ b/src/Ryujinx/Utilities/CommandLineState.cs @@ -14,7 +14,7 @@ namespace Ryujinx.Ava.Utilities public static string OverrideBackendThreading { get; private set; } public static string OverrideHideCursor { get; private set; } public static string BaseDirPathArg { get; private set; } - public static FilePath FirmwareToInstallPathArg { get; private set; } + public static FilePath FirmwareToInstallPathArg { get; set; } public static string Profile { get; private set; } public static string LaunchPathArg { get; private set; } public static string LaunchApplicationId { get; private set; } diff --git a/src/Ryujinx/Utilities/Configuration/ConfigurationState.Model.cs b/src/Ryujinx/Utilities/Configuration/ConfigurationState.Model.cs index 3f22c6c0f..ead99fbac 100644 --- a/src/Ryujinx/Utilities/Configuration/ConfigurationState.Model.cs +++ b/src/Ryujinx/Utilities/Configuration/ConfigurationState.Model.cs @@ -1,5 +1,6 @@ using ARMeilleure; using Gommon; +using Ryujinx.Ava.Utilities.AppLibrary; using Ryujinx.Ava.Utilities.Configuration.System; using Ryujinx.Ava.Utilities.Configuration.UI; using Ryujinx.Common; @@ -647,6 +648,14 @@ namespace Ryujinx.Ava.Utilities.Configuration /// public ReactiveObject LdnServer { get; private set; } + public string GetLdnServer() + { + string ldnServer = LdnServer; + return string.IsNullOrEmpty(ldnServer) + ? SharedConstants.DefaultLanPlayHost + : ldnServer; + } + public MultiplayerSection() { LanInterfaceId = new ReactiveObject();