Compare commits

...

7 Commits

Author SHA1 Message Date
WilliamWsyHK
bd2681b2f9 Add missing and update translations for zh-tw (#158)
Simply add back some missing translations and update outdated
translations for zh-tw.
2024-11-07 10:46:40 -06:00
Evan Husted
640d7f9e77 Updater: kinda confused how this didn't work? 2024-11-06 19:55:58 -06:00
Evan Husted
02e8278438 Merge remote-tracking branch 'origin/master' 2024-11-06 19:46:30 -06:00
Evan Husted
6acd86c890 Fix canary updater & checking if current build is canary. 2024-11-06 19:46:20 -06:00
Luke Warner
708256ce96 Add just, a whole bunch of games to RPC assets. (#98) 2024-11-06 19:22:40 -06:00
Evan Husted
5bf50836e1 i18n: missing comma in en_US locale 2024-11-06 18:24:30 -06:00
Evan Husted
730ba44043 misc: Canary-specific naming & other small changes I had that I need to push. 2024-11-06 18:23:27 -06:00
17 changed files with 349 additions and 302 deletions

View File

@@ -28,9 +28,9 @@ namespace Ryujinx.Common
public static bool IsFlatHubBuild => IsValid && ReleaseChannelOwner.Equals(FlatHubChannel);
public static bool IsCanaryBuild => IsValid && ReleaseChannelOwner.Equals(CanaryChannel);
public static bool IsCanaryBuild => IsValid && ReleaseChannelName.Equals(CanaryChannel);
public static bool IsReleaseBuild => IsValid && ReleaseChannelOwner.Equals(ReleaseChannel);
public static bool IsReleaseBuild => IsValid && ReleaseChannelName.Equals(ReleaseChannel);
public static string Version => IsValid ? BuildVersion : Assembly.GetEntryAssembly()!.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion;
}

View File

@@ -122,70 +122,144 @@ namespace Ryujinx.UI.Common
private static readonly string[] _discordGameAssetKeys =
[
"01002da013484000", // The Legend of Zelda: Skyward Sword HD
"010055d009f78000", // Fire Emblem: Three Houses
"0100a12011cc8000", // Fire Emblem: Shadow Dragon
"0100a6301214e000", // Fire Emblem Engage
"0100f15003e64000", // Fire Emblem Warriors
"010071f0143ea000", // Fire Emblem Warriors: Three Hopes
"01007e3006dda000", // Kirby Star Allies
"01004d300c5ae000", // Kirby and the Forgotten Land
"01006b601380e000", // Kirby's Return to Dream Land Deluxe
"01003fb00c5a8000", // Super Kirby Clash
"0100227010460000", // Kirby Fighters 2
"0100a8e016236000", // Kirby's Dream Buffet
"01007ef00011e000", // The Legend of Zelda: Breath of the Wild
"01006bb00c6f0000", // The Legend of Zelda: Link's Awakening
"01002da013484000", // The Legend of Zelda: Skyward Sword HD
"0100f2c0115b6000", // The Legend of Zelda: Tears of the Kingdom
"01008cf01baac000", // The Legend of Zelda: Echoes of Wisdom
"01006bb00c6f0000", // The Legend of Zelda: Link's Awakening
"0100000000010000", // SUPER MARIO ODYSSEY
"010015100b514000", // Super Mario Bros. Wonder
"0100152000022000", // Mario Kart 8 Deluxe
"01006fe013472000", // Mario Party Superstars
"0100965017338000", // Super Mario Party Jamboree
"010049900f546000", // Super Mario 3D All-Stars
"010028600ebda000", // Super Mario 3D World + Bowser's Fury
"0100ecd018ebe000", // Paper Mario: The Thousand-Year Door
"010019401051c000", // Mario Strikers League
"0100ea80032ea000", // Super Mario Bros. U Deluxe
"0100bc0018138000", // Super Mario RPG
"0100bde00862a000", // Mario Tennis Aces
"01000b900d8b0000", // Cadence of Hyrule
"0100ae00096ea000", // Hyrule Warriors: Definitive Edition
"01002b00111a2000", // Hyrule Warriors: Age of Calamity
"010048701995e000", // Luigi's Mansion 2 HD
"0100dca0064a6000", // Luigi's Mansion 3
"01008f6008c5e000", // Pokémon Violet
"0100abf008968000", // Pokémon Sword
"01008db008c2c000", // Pokémon Shield
"0100000011d90000", // Pokémon Brilliant Diamond
"01001f5010dfa000", // Pokémon Legends: Arceus
"010093801237c000", // Metroid Dread
"010012101468c000", // Metroid Prime Remastered
"0100000000010000", // SUPER MARIO ODYSSEY
"0100ea80032ea000", // Super Mario Bros. U Deluxe
"01009b90006dc000", // Super Mario Maker 2
"010049900f546000", // Super Mario 3D All-Stars
"010049900F546001", // ^ 64
"010049900F546002", // ^ Sunshine
"010049900F546003", // ^ Galaxy
"010028600ebda000", // Super Mario 3D World + Bowser's Fury
"010015100b514000", // Super Mario Bros. Wonder
"0100152000022000", // Mario Kart 8 Deluxe
"010036b0034e4000", // Super Mario Party
"01006fe013472000", // Mario Party Superstars
"0100965017338000", // Super Mario Party Jamboree
"010067300059a000", // Mario + Rabbids: Kingdom Battle
"0100317013770000", // Mario + Rabbids: Sparks of Hope
"0100a3900c3e2000", // Paper Mario: The Origami King
"0100ecd018ebe000", // Paper Mario: The Thousand-Year Door
"0100bc0018138000", // Super Mario RPG
"0100bde00862a000", // Mario Tennis Aces
"0100c9c00e25c000", // Mario Golf: Super Rush
"010019401051c000", // Mario Strikers: Battle League
"010003000e146000", // Mario & Sonic at the Olympic Games Tokyo 2020
"0100b99019412000", // Mario vs. Donkey Kong
"0100aa80194b0000", // Pikmin 1
"0100d680194b2000", // Pikmin 2
"0100f4c009322000", // Pikmin 3 Deluxe
"0100b7c00933a000", // Pikmin 4
"010003f003a34000", // Pokémon: Let's Go Pikachu!
"0100187003a36000", // Pokémon: Let's Go Eevee!
"0100abf008968000", // Pokémon Sword
"01008db008c2c000", // Pokémon Shield
"0100000011d90000", // Pokémon Brilliant Diamond
"010018e011d92000", // Pokémon Shining Pearl
"01001f5010dfa000", // Pokémon Legends: Arceus
"0100a3d008c5c000", // Pokémon Scarlet
"01008f6008c5e000", // Pokémon Violet
"0100b3f000be2000", // Pokkén Tournament DX
"0100f4300bf2c000", // New Pokémon Snap
"01003bc0000a0000", // Splatoon 2 (US)
"0100f8f0000a2000", // Splatoon 2 (EU)
"01003c700009c000", // Splatoon 2 (JP)
"0100c2500fc20000", // Splatoon 3
"0100ba0018500000", // Splatoon 3: Splatfest World Premiere
"010040600c5ce000", // Tetris 99
"0100277011f1a000", // Super Mario Bros. 35
"0100ad9012510000", // PAC-MAN 99
"0100ccf019c8c000", // F-ZERO 99
"0100d870045b6000", // NES - Nintendo Switch Online
"01008d300c50c000", // SNES - Nintendo Switch Online
"0100c9a00ece6000", // N64 - Nintendo Switch Online
"0100e0601c632000", // N64 - Nintendo Switch Online 18+
"0100c62011050000", // GB - Nintendo Switch Online
"010012f017576000", // GBA - Nintendo Switch Online
"01000320000cc000", // 1-2 Switch
"0100300012f2a000", // Advance Wars 1+2: Re-Boot Camp
"01006f8002326000", // Animal Crossing: New Horizons
"0100620012d6e000", // Big Brain Academy: Brain vs. Brain
"010018300d006000", // BOXBOY! + BOXGIRL!
"0100c1f0051b6000", // Donkey Kong Country: Tropical Freeze
"0100ed000d390000", // Dr. Kawashima's Brain Training
"010067b017588000", // Endless Ocean Luminous
"0100d2f00d5c0000", // Nintendo Switch Sports
"01006b5012b32000", // Part Time UFO
"0100704000B3A000", // Snipperclips
"01006a800016e000", // Super Smash Bros. Ultimate
"0100a9400c9c2000", // Tokyo Mirage Sessions #FE Encore
"010076f0049a2000", // Bayonetta
"01007960049a0000", // Bayonetta 2
"01004a4010fea000", // Bayonetta 3
"0100cf5010fec000", // Bayonetta Origins: Cereza and the Lost Demon
"0100dcd01525a000", // Persona 3 Portable
"010062b01525c000", // Persona 4 Golden
"010075a016a3a000", // Persona 4 Arena Ultimax
"01005ca01580e000", // Persona 5 Royal
"0100801011c3e000", // Persona 5 Strikers
"010087701b092000", // Persona 5 Tactica
"01009aa000faa000", // Sonic Mania
"01004ad014bf0000", // Sonic Frontiers
"01005ea01c0fc000", // SONIC X SHADOW GENERATIONS
"01005ea01c0fc001", // ^
"01004d300c5ae000", // Kirby and the Forgotten Land
"01006b601380e000", // Kirby's Return to Dreamland Deluxe
"01007e3006dda000", // Kirby Star Allies
"0100c2500fc20000", // Splatoon 3
"0100ba0018500000", // Splatoon 3: Splatfest World Premiere
"01000a10041ea000", // The Elder Scrolls V: Skyrim
"01007820196a6000", // Red Dead Redemption
"01008c8012920000", // Dying Light Platinum Edition
"0100744001588000", // Cars 3: Driven to Win
"0100c1f0051b6000", // Donkey Kong Country: Tropical Freeze
"01002b00111a2000", // Hyrule Warriors: Age of Calamity
"01006f8002326000", // Animal Crossing: New Horizons
"0100853015e86000", // No Man's Sky
"01008d100d43e000", // Saints Row IV
"0100de600beee000", // Saints Row: The Third - The Full Package
"0100d7a01b7a2000", // Star Wars: Bounty Hunter
"0100dbf01000a000", // Burnout Paradise Remastered
"0100e46006708000", // Terraria
"010056e00853a000", // A Hat in Time
"01006a800016e000", // Super Smash Bros. Ultimate
"0100dbf01000a000", // Burnout Paradise Remastered
"0100744001588000", // Cars 3: Driven to Win
"0100b41013c82000", // Cruis'n Blast
"01008c8012920000", // Dying Light Platinum Edition
"01000a10041ea000", // The Elder Scrolls V: Skyrim
"0100770008dd8000", // Monster Hunter Generations Ultimate
"0100b04011742000", // Monster Hunter Rise
"0100853015e86000", // No Man's Sky
"01007bb017812000", // Portal
"0100abd01785c000", // Portal 2
"01008e200c5c2000", // Muse Dash
"01007820196a6000", // Red Dead Redemption
"01002f7013224000", // Rune Factory 5
"01008d100d43e000", // Saints Row IV
"0100de600beee000", // Saints Row: The Third - The Full Package
"01001180021fa000", // Shovel Knight: Specter of Torment
"010012101468c000", // Metroid Prime Remastered
"0100c9a00ece6000", // Nintendo 64 - Nintendo Switch Online
"0100d7a01b7a2000", // Star Wars: Bounty Hunter
"0100800015926000", // Suika Game
"0100e46006708000", // Terraria
"010080b00ad66000", // Undertale
];
}
}

View File

@@ -5,6 +5,8 @@ namespace Ryujinx.UI.Common.Models.Github
public class GithubReleasesJsonResponse
{
public string Name { get; set; }
public string TagName { get; set; }
public List<GithubReleaseAssetJsonResponse> Assets { get; set; }
}
}

View File

@@ -23,8 +23,10 @@ namespace Ryujinx.Ava
{
internal static string FormatTitle(LocaleKeys? windowTitleKey = null)
=> windowTitleKey is null
? $"Ryujinx {Program.Version}"
: $"Ryujinx {Program.Version} - {LocaleManager.Instance[windowTitleKey.Value]}";
? $"{FullAppName} {Program.Version}"
: $"{FullAppName} {Program.Version} - {LocaleManager.Instance[windowTitleKey.Value]}";
public static readonly string FullAppName = ReleaseInformation.IsCanaryBuild ? "Ryujinx Canary" : "Ryujinx";
public static MainWindow MainWindow => Current!
.ApplicationLifetime.Cast<IClassicDesktopStyleApplicationLifetime>()

View File

@@ -451,7 +451,7 @@
"DialogUpdaterCancelUpdateMessage": "Update canceled!",
"DialogUpdaterAlreadyOnLatestVersionMessage": "You are already using the latest version of Ryujinx!",
"DialogUpdaterFailedToGetVersionMessage": "An error occurred while trying to retrieve release information from GitHub. This may happen if a new release is currently being compiled by GitHub Actions. Please try again in a few minutes.",
"DialogUpdaterConvertFailedGithubMessage": "Failed to convert the Ryujinx version received from GitHub."
"DialogUpdaterConvertFailedGithubMessage": "Failed to convert the Ryujinx version received from GitHub.",
"DialogUpdaterDownloadingMessage": "Downloading Update...",
"DialogUpdaterExtractionMessage": "Extracting Update...",
"DialogUpdaterRenamingMessage": "Renaming Update...",

View File

@@ -10,10 +10,10 @@
"SettingsTabSystemUseHypervisor": "使用 Hypervisor",
"MenuBarFile": "檔案(_F)",
"MenuBarFileOpenFromFile": "從檔案載入應用程式(_L)",
"MenuBarFileOpenFromFileError": "No applications found in selected file.",
"MenuBarFileOpenFromFileError": "未能從已選擇的檔案中找到應用程式。",
"MenuBarFileOpenUnpacked": "載入未封裝的遊戲(_U)",
"MenuBarFileLoadDlcFromFolder": "Load DLC From Folder",
"MenuBarFileLoadTitleUpdatesFromFolder": "Load Title Updates From Folder",
"MenuBarFileLoadDlcFromFolder": "從資料夾中載入 DLC",
"MenuBarFileLoadTitleUpdatesFromFolder": "從資料夾中載入遊戲更新",
"MenuBarFileOpenEmuFolder": "開啟 Ryujinx 資料夾",
"MenuBarFileOpenLogsFolder": "開啟日誌資料夾",
"MenuBarFileExit": "結束(_E)",
@@ -100,14 +100,14 @@
"SettingsTabGeneralCheckUpdatesOnLaunch": "啟動時檢查更新",
"SettingsTabGeneralShowConfirmExitDialog": "顯示「確認結束」對話方塊",
"SettingsTabGeneralRememberWindowState": "記住視窗大小/位置",
"SettingsTabGeneralShowTitleBar": "Show Title Bar (Requires restart)",
"SettingsTabGeneralShowTitleBar": "顯示「標題列」 (需要重新開啟Ryujinx)",
"SettingsTabGeneralHideCursor": "隱藏滑鼠游標:",
"SettingsTabGeneralHideCursorNever": "從不",
"SettingsTabGeneralHideCursorOnIdle": "閒置時",
"SettingsTabGeneralHideCursorAlways": "總是",
"SettingsTabGeneralGameDirectories": "遊戲資料夾",
"SettingsTabGeneralAutoloadDirectories": "Autoload DLC/Updates Directories",
"SettingsTabGeneralAutoloadNote": "DLC and Updates which refer to missing files will be unloaded automatically",
"SettingsTabGeneralAutoloadDirectories": "自動載入 DLC/遊戲更新資料夾",
"SettingsTabGeneralAutoloadNote": "遺失的 DLC 及遊戲更新檔案將會在自動載入中移除",
"SettingsTabGeneralAdd": "新增",
"SettingsTabGeneralRemove": "刪除",
"SettingsTabSystem": "系統",
@@ -142,7 +142,7 @@
"SettingsTabSystemSystemTime": "系統時鐘:",
"SettingsTabSystemEnableVsync": "垂直同步",
"SettingsTabSystemEnablePptc": "PPTC (剖析式持久轉譯快取, Profiled Persistent Translation Cache)",
"SettingsTabSystemEnableLowPowerPptc": "Low-power PPTC",
"SettingsTabSystemEnableLowPowerPptc": "低功耗 PPTC",
"SettingsTabSystemEnableFsIntegrityChecks": "檔案系統完整性檢查",
"SettingsTabSystemAudioBackend": "音效後端:",
"SettingsTabSystemAudioBackendDummy": "虛設 (Dummy)",
@@ -416,7 +416,7 @@
"GameListContextMenuToggleFavorite": "加入/移除為我的最愛",
"GameListContextMenuToggleFavoriteToolTip": "切換遊戲的我的最愛狀態",
"SettingsTabGeneralTheme": "佈景主題:",
"SettingsTabGeneralThemeAuto": "Auto",
"SettingsTabGeneralThemeAuto": "自動",
"SettingsTabGeneralThemeDark": "深色",
"SettingsTabGeneralThemeLight": "淺色",
"ControllerSettingsConfigureGeneral": "配置",
@@ -567,9 +567,9 @@
"AddGameDirBoxTooltip": "輸入要新增到清單中的遊戲資料夾",
"AddGameDirTooltip": "新增遊戲資料夾到清單中",
"RemoveGameDirTooltip": "移除選取的遊戲資料夾",
"AddAutoloadDirBoxTooltip": "Enter an autoload directory to add to the list",
"AddAutoloadDirTooltip": "Add an autoload directory to the list",
"RemoveAutoloadDirTooltip": "Remove selected autoload directory",
"AddAutoloadDirBoxTooltip": "輸入要新增到清單中的「自動載入 DLC/遊戲更新資料夾」",
"AddAutoloadDirTooltip": "新增「自動載入 DLC/遊戲更新資料夾」到清單中",
"RemoveAutoloadDirTooltip": "移除選取的「自動載入 DLC/遊戲更新資料夾」",
"CustomThemeCheckTooltip": "為圖形使用者介面使用自訂 Avalonia 佈景主題,變更模擬器功能表的外觀",
"CustomThemePathTooltip": "自訂 GUI 佈景主題的路徑",
"CustomThemeBrowseTooltip": "瀏覽自訂 GUI 佈景主題",
@@ -582,7 +582,7 @@
"TimeTooltip": "變更系統時鐘",
"VSyncToggleTooltip": "模擬遊戲機的垂直同步。對大多數遊戲來說,它本質上是一個幀率限制器;停用它可能會導致遊戲以更高的速度執行,或使載入畫面耗時更長或卡住。\n\n可以在遊戲中使用快速鍵進行切換 (預設為 F1)。如果您打算停用,我們建議您這樣做。\n\n如果不確定請保持開啟狀態。",
"PptcToggleTooltip": "儲存已轉譯的 JIT 函數,這樣每次載入遊戲時就無需再轉譯這些函數。\n\n減少遊戲首次啟動後的卡頓現象並大大加快啟動時間。\n\n如果不確定請保持開啟狀態。",
"LowPowerPptcToggleTooltip": "Load the PPTC using a third of the amount of cores.",
"LowPowerPptcToggleTooltip": "使用 CPU 核心數量的三分之一載入 PPTC。",
"FsIntegrityToggleTooltip": "在啟動遊戲時檢查損壞的檔案,如果檢測到損壞的檔案,則在日誌中顯示雜湊值錯誤。\n\n對效能沒有影響旨在幫助排除故障。\n\n如果不確定請保持開啟狀態。",
"AudioBackendTooltip": "變更用於繪製音訊的後端。\n\nSDL2 是首選,而 OpenAL 和 SoundIO 則作為備用。虛設 (Dummy) 將沒有聲音。\n\n如果不確定請設定為 SDL2。",
"MemoryManagerTooltip": "變更客體記憶體的映射和存取方式。這會極大地影響模擬 CPU 效能。\n\n如果不確定請設定為主體略過檢查模式。",
@@ -590,7 +590,7 @@
"MemoryManagerHostTooltip": "直接映射主體位址空間中的記憶體。更快的 JIT 編譯和執行速度。",
"MemoryManagerUnsafeTooltip": "直接映射記憶體,但在存取前不封鎖客體位址空間內的位址。速度更快,但相對不安全。訪客應用程式可以從 Ryujinx 中的任何地方存取記憶體,因此只能使用該模式執行您信任的程式。",
"UseHypervisorTooltip": "使用 Hypervisor 取代 JIT。使用時可大幅提高效能但在目前狀態下可能不穩定。",
"DRamTooltip": "利用另一種 MemoryMode 配置來模仿 Switch 開發模式。\n\n這僅對高解析度紋理套件或 4K 解析度模組有用。不會提高效能。\n\n如果不確定保持關閉狀態。",
"DRamTooltip": "利用另一種 MemoryMode 配置來模仿 Switch 開發模式。\n\n這僅對高解析度紋理套件或 4K 解析度模組有用。不會提高效能。\n\n如果不確定設定為 4GiB。",
"IgnoreMissingServicesTooltip": "忽略未實現的 Horizon OS 服務。這可能有助於在啟動某些遊戲時避免崩潰。\n\n如果不確定請保持關閉狀態。",
"IgnoreAppletTooltip": "如果遊戲手把在遊戲過程中斷開連接,則外部對話方塊「控制器小程式」將不會出現。不會提示關閉對話方塊或設定新控制器。一旦先前斷開的控制器重新連接,遊戲將自動恢復。",
"GraphicsBackendThreadingTooltip": "在第二個執行緒上執行圖形後端指令。\n\n在本身不支援多執行緒的 GPU 驅動程式上,可加快著色器編譯、減少卡頓並提高效能。在支援多執行緒的驅動程式上效能略有提升。\n\n如果不確定請設定為自動。",
@@ -615,8 +615,8 @@
"DebugLogTooltip": "在控制台中輸出偵錯日誌訊息。\n\n只有在人員特別指示的情況下才能使用因為這會導致日誌難以閱讀並降低模擬器效能。",
"LoadApplicationFileTooltip": "開啟檔案總管,選擇與 Switch 相容的檔案來載入",
"LoadApplicationFolderTooltip": "開啟檔案總管,選擇與 Switch 相容且未封裝的應用程式來載入",
"LoadDlcFromFolderTooltip": "Open a file explorer to choose one or more folders to bulk load DLC from",
"LoadTitleUpdatesFromFolderTooltip": "Open a file explorer to choose one or more folders to bulk load title updates from",
"LoadDlcFromFolderTooltip": "開啟檔案總管,選擇一個或多個資料夾來大量載入 DLC",
"LoadTitleUpdatesFromFolderTooltip": "開啟檔案總管,選擇一個或多個資料夾來大量載入遊戲更新",
"OpenRyujinxFolderTooltip": "開啟 Ryujinx 檔案系統資料夾",
"OpenRyujinxLogsTooltip": "開啟日誌被寫入的資料夾",
"ExitTooltip": "結束 Ryujinx",
@@ -668,8 +668,8 @@
"OpenSetupGuideMessage": "開啟設定指南",
"NoUpdate": "沒有更新",
"TitleUpdateVersionLabel": "版本 {0}",
"TitleBundledUpdateVersionLabel": "Bundled: Version {0}",
"TitleBundledDlcLabel": "Bundled:",
"TitleBundledUpdateVersionLabel": "附帶: 版本 {0}",
"TitleBundledDlcLabel": "附帶:",
"RyujinxInfo": "Ryujinx - 資訊",
"RyujinxConfirm": "Ryujinx - 確認",
"FileDialogAllTypes": "全部類型",
@@ -727,17 +727,17 @@
"DlcWindowTitle": "管理 {0} 的可下載內容 ({1})",
"ModWindowTitle": "管理 {0} 的模組 ({1})",
"UpdateWindowTitle": "遊戲更新管理員",
"UpdateWindowUpdateAddedMessage": "{0} new update(s) added",
"UpdateWindowBundledContentNotice": "Bundled updates cannot be removed, only disabled.",
"UpdateWindowUpdateAddedMessage": "已加入 {0} 個遊戲更新",
"UpdateWindowBundledContentNotice": "附帶的遊戲更新只能被停用而無法被刪除。",
"CheatWindowHeading": "可用於 {0} [{1}] 的密技",
"BuildId": "組建識別碼:",
"DlcWindowBundledContentNotice": "Bundled DLC cannot be removed, only disabled.",
"DlcWindowBundledContentNotice": "附帶的 DLC 只能被停用而無法被刪除。",
"DlcWindowHeading": "{0} 個可下載內容",
"DlcWindowDlcAddedMessage": "{0} new downloadable content(s) added",
"AutoloadDlcAddedMessage": "{0} new downloadable content(s) added",
"AutoloadDlcRemovedMessage": "{0} missing downloadable content(s) removed",
"AutoloadUpdateAddedMessage": "{0} new update(s) added",
"AutoloadUpdateRemovedMessage": "{0} missing update(s) removed",
"DlcWindowDlcAddedMessage": "已加入 {0} 個 DLC",
"AutoloadDlcAddedMessage": "已加入 {0} 個 DLC",
"AutoloadDlcRemovedMessage": "已刪除 {0} 個遺失的 DLC",
"AutoloadUpdateAddedMessage": "已加入 {0} 個遊戲更新",
"AutoloadUpdateRemovedMessage": "已刪除 {0} 個遺失的遊戲更新",
"ModWindowHeading": "{0} 模組",
"UserProfilesEditProfile": "編輯所選",
"Cancel": "取消",

View File

@@ -2,19 +2,38 @@
using Avalonia.Markup.Xaml;
using Avalonia.Markup.Xaml.MarkupExtensions;
using Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings;
using Gommon;
using System;
// ReSharper disable VirtualMemberNeverOverridden.Global
// ReSharper disable MemberCanBeProtected.Global
// ReSharper disable MemberCanBePrivate.Global
#nullable enable
namespace Ryujinx.Ava.Common.Markup
{
internal abstract class BasicMarkupExtension : MarkupExtension
internal abstract class BasicMarkupExtension<T> : MarkupExtension
{
protected abstract ClrPropertyInfo PropertyInfo { get; }
public override object ProvideValue(IServiceProvider serviceProvider) =>
new CompiledBindingExtension(
new CompiledBindingPathBuilder()
.Property(PropertyInfo, PropertyInfoAccessorFactory.CreateInpcPropertyAccessor)
.Build()
).ProvideValue(serviceProvider);
public virtual string Name => "Item";
public virtual Action<object, T?>? Setter => null;
protected abstract T? GetValue();
protected virtual void ConfigureBindingExtension(CompiledBindingExtension _) { }
private ClrPropertyInfo PropertyInfo =>
new(Name,
_ => GetValue(),
Setter as Action<object, object?>,
typeof(T));
public override object ProvideValue(IServiceProvider serviceProvider)
=> new CompiledBindingExtension(
new CompiledBindingPathBuilder()
.Property(PropertyInfo, PropertyInfoAccessorFactory.CreateInpcPropertyAccessor)
.Build()
)
.Apply(ConfigureBindingExtension)
.ProvideValue(serviceProvider);
}
}

View File

@@ -1,51 +1,24 @@
using Avalonia.Data.Core;
using Avalonia.Markup.Xaml;
using Avalonia.Markup.Xaml.MarkupExtensions;
using Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings;
using Projektanker.Icons.Avalonia;
using Ryujinx.Ava.Common.Locale;
using System;
namespace Ryujinx.Ava.Common.Markup
{
internal class IconExtension(string iconString) : BasicMarkupExtension
internal class IconExtension(string iconString) : BasicMarkupExtension<Icon>
{
protected override ClrPropertyInfo PropertyInfo
=> new(
"Item",
_ => new Icon { Value = iconString },
null,
typeof(Icon)
);
protected override Icon GetValue() => new() { Value = iconString };
}
internal class SpinningIconExtension(string iconString) : BasicMarkupExtension
internal class SpinningIconExtension(string iconString) : BasicMarkupExtension<Icon>
{
protected override ClrPropertyInfo PropertyInfo
=> new(
"Item",
_ => new Icon { Value = iconString, Animation = IconAnimation.Spin },
null,
typeof(Icon)
);
protected override Icon GetValue() => new() { Value = iconString, Animation = IconAnimation.Spin };
}
internal class LocaleExtension(LocaleKeys key) : MarkupExtension
internal class LocaleExtension(LocaleKeys key) : BasicMarkupExtension<string>
{
private ClrPropertyInfo PropertyInfo
=> new(
"Item",
_ => LocaleManager.Instance[key],
null,
typeof(string)
);
public override object ProvideValue(IServiceProvider serviceProvider) =>
new CompiledBindingExtension(
new CompiledBindingPathBuilder()
.Property(PropertyInfo, PropertyInfoAccessorFactory.CreateInpcPropertyAccessor)
.Build()
) { Source = LocaleManager.Instance }
.ProvideValue(serviceProvider);
protected override string GetValue() => LocaleManager.Instance[key];
protected override void ConfigureBindingExtension(CompiledBindingExtension bindingExtension)
=> bindingExtension.Source = LocaleManager.Instance;
}
}

View File

@@ -23,21 +23,15 @@ namespace Ryujinx.Ava.Input
public bool IsConnected => true;
public GamepadFeaturesFlag Features => GamepadFeaturesFlag.None;
private class ButtonMappingEntry
private class ButtonMappingEntry(GamepadButtonInputId to, Key from)
{
public readonly Key From;
public readonly GamepadButtonInputId To;
public ButtonMappingEntry(GamepadButtonInputId to, Key from)
{
To = to;
From = from;
}
public readonly GamepadButtonInputId To = to;
public readonly Key From = from;
}
public AvaloniaKeyboard(AvaloniaKeyboardDriver driver, string id, string name)
{
_buttonsUserMapping = new List<ButtonMappingEntry>();
_buttonsUserMapping = [];
_driver = driver;
Id = id;

View File

@@ -127,11 +127,11 @@ namespace Ryujinx.Ava.UI.Applet
try
{
_parent.ViewModel.AppHost.NpadManager.BlockInputUpdates();
var response = await SwkbdAppletDialog.ShowInputDialog(LocaleManager.Instance[LocaleKeys.SoftwareKeyboard], args);
(UserResult result, string userInput) = await SwkbdAppletDialog.ShowInputDialog(LocaleManager.Instance[LocaleKeys.SoftwareKeyboard], args);
if (response.Result == UserResult.Ok)
if (result == UserResult.Ok)
{
inputText = response.Input;
inputText = userInput;
okPressed = true;
}
}

View File

@@ -33,7 +33,7 @@ namespace Ryujinx.Ava.UI.Views.Input
{
base.OnPointerReleased(e);
if (_currentAssigner != null && _currentAssigner.ToggledButton != null && !_currentAssigner.ToggledButton.IsPointerOver)
if (_currentAssigner is { ToggledButton.IsPointerOver: false })
{
_currentAssigner.Cancel();
}
@@ -41,143 +41,146 @@ namespace Ryujinx.Ava.UI.Views.Input
private void Button_IsCheckedChanged(object sender, RoutedEventArgs e)
{
if (sender is ToggleButton button)
if (sender is not ToggleButton button)
return;
if (button.IsChecked is true)
{
if ((bool)button.IsChecked)
if (_currentAssigner != null && button == _currentAssigner.ToggledButton)
{
if (_currentAssigner != null && button == _currentAssigner.ToggledButton)
{
return;
}
if (_currentAssigner == null)
{
_currentAssigner = new ButtonKeyAssigner(button);
Focus(NavigationMethod.Pointer);
PointerPressed += MouseClick;
if (DataContext is not KeyboardInputViewModel viewModel)
return;
}
if (_currentAssigner == null)
IKeyboard keyboard =
(IKeyboard)viewModel.ParentModel.AvaloniaKeyboardDriver.GetGamepad("0"); // Open Avalonia keyboard for cancel operations.
IButtonAssigner assigner =
new KeyboardKeyAssigner((IKeyboard)viewModel.ParentModel.SelectedGamepad);
_currentAssigner.ButtonAssigned += (_, e) =>
{
_currentAssigner = new ButtonKeyAssigner(button);
Focus(NavigationMethod.Pointer);
PointerPressed += MouseClick;
var viewModel = (DataContext as KeyboardInputViewModel);
IKeyboard keyboard = (IKeyboard)viewModel.ParentModel.AvaloniaKeyboardDriver.GetGamepad("0"); // Open Avalonia keyboard for cancel operations.
IButtonAssigner assigner = CreateButtonAssigner();
_currentAssigner.ButtonAssigned += (sender, e) =>
if (e.ButtonValue.HasValue)
{
if (e.ButtonValue.HasValue)
var buttonValue = e.ButtonValue.Value;
viewModel.ParentModel.IsModified = true;
switch (button.Name)
{
var buttonValue = e.ButtonValue.Value;
viewModel.ParentModel.IsModified = true;
switch (button.Name)
{
case "ButtonZl":
viewModel.Config.ButtonZl = buttonValue.AsHidType<Key>();
break;
case "ButtonL":
viewModel.Config.ButtonL = buttonValue.AsHidType<Key>();
break;
case "ButtonMinus":
viewModel.Config.ButtonMinus = buttonValue.AsHidType<Key>();
break;
case "LeftStickButton":
viewModel.Config.LeftStickButton = buttonValue.AsHidType<Key>();
break;
case "LeftStickUp":
viewModel.Config.LeftStickUp = buttonValue.AsHidType<Key>();
break;
case "LeftStickDown":
viewModel.Config.LeftStickDown = buttonValue.AsHidType<Key>();
break;
case "LeftStickRight":
viewModel.Config.LeftStickRight = buttonValue.AsHidType<Key>();
break;
case "LeftStickLeft":
viewModel.Config.LeftStickLeft = buttonValue.AsHidType<Key>();
break;
case "DpadUp":
viewModel.Config.DpadUp = buttonValue.AsHidType<Key>();
break;
case "DpadDown":
viewModel.Config.DpadDown = buttonValue.AsHidType<Key>();
break;
case "DpadLeft":
viewModel.Config.DpadLeft = buttonValue.AsHidType<Key>();
break;
case "DpadRight":
viewModel.Config.DpadRight = buttonValue.AsHidType<Key>();
break;
case "LeftButtonSr":
viewModel.Config.LeftButtonSr = buttonValue.AsHidType<Key>();
break;
case "LeftButtonSl":
viewModel.Config.LeftButtonSl = buttonValue.AsHidType<Key>();
break;
case "RightButtonSr":
viewModel.Config.RightButtonSr = buttonValue.AsHidType<Key>();
break;
case "RightButtonSl":
viewModel.Config.RightButtonSl = buttonValue.AsHidType<Key>();
break;
case "ButtonZr":
viewModel.Config.ButtonZr = buttonValue.AsHidType<Key>();
break;
case "ButtonR":
viewModel.Config.ButtonR = buttonValue.AsHidType<Key>();
break;
case "ButtonPlus":
viewModel.Config.ButtonPlus = buttonValue.AsHidType<Key>();
break;
case "ButtonA":
viewModel.Config.ButtonA = buttonValue.AsHidType<Key>();
break;
case "ButtonB":
viewModel.Config.ButtonB = buttonValue.AsHidType<Key>();
break;
case "ButtonX":
viewModel.Config.ButtonX = buttonValue.AsHidType<Key>();
break;
case "ButtonY":
viewModel.Config.ButtonY = buttonValue.AsHidType<Key>();
break;
case "RightStickButton":
viewModel.Config.RightStickButton = buttonValue.AsHidType<Key>();
break;
case "RightStickUp":
viewModel.Config.RightStickUp = buttonValue.AsHidType<Key>();
break;
case "RightStickDown":
viewModel.Config.RightStickDown = buttonValue.AsHidType<Key>();
break;
case "RightStickRight":
viewModel.Config.RightStickRight = buttonValue.AsHidType<Key>();
break;
case "RightStickLeft":
viewModel.Config.RightStickLeft = buttonValue.AsHidType<Key>();
break;
}
case "ButtonZl":
viewModel.Config.ButtonZl = buttonValue.AsHidType<Key>();
break;
case "ButtonL":
viewModel.Config.ButtonL = buttonValue.AsHidType<Key>();
break;
case "ButtonMinus":
viewModel.Config.ButtonMinus = buttonValue.AsHidType<Key>();
break;
case "LeftStickButton":
viewModel.Config.LeftStickButton = buttonValue.AsHidType<Key>();
break;
case "LeftStickUp":
viewModel.Config.LeftStickUp = buttonValue.AsHidType<Key>();
break;
case "LeftStickDown":
viewModel.Config.LeftStickDown = buttonValue.AsHidType<Key>();
break;
case "LeftStickRight":
viewModel.Config.LeftStickRight = buttonValue.AsHidType<Key>();
break;
case "LeftStickLeft":
viewModel.Config.LeftStickLeft = buttonValue.AsHidType<Key>();
break;
case "DpadUp":
viewModel.Config.DpadUp = buttonValue.AsHidType<Key>();
break;
case "DpadDown":
viewModel.Config.DpadDown = buttonValue.AsHidType<Key>();
break;
case "DpadLeft":
viewModel.Config.DpadLeft = buttonValue.AsHidType<Key>();
break;
case "DpadRight":
viewModel.Config.DpadRight = buttonValue.AsHidType<Key>();
break;
case "LeftButtonSr":
viewModel.Config.LeftButtonSr = buttonValue.AsHidType<Key>();
break;
case "LeftButtonSl":
viewModel.Config.LeftButtonSl = buttonValue.AsHidType<Key>();
break;
case "RightButtonSr":
viewModel.Config.RightButtonSr = buttonValue.AsHidType<Key>();
break;
case "RightButtonSl":
viewModel.Config.RightButtonSl = buttonValue.AsHidType<Key>();
break;
case "ButtonZr":
viewModel.Config.ButtonZr = buttonValue.AsHidType<Key>();
break;
case "ButtonR":
viewModel.Config.ButtonR = buttonValue.AsHidType<Key>();
break;
case "ButtonPlus":
viewModel.Config.ButtonPlus = buttonValue.AsHidType<Key>();
break;
case "ButtonA":
viewModel.Config.ButtonA = buttonValue.AsHidType<Key>();
break;
case "ButtonB":
viewModel.Config.ButtonB = buttonValue.AsHidType<Key>();
break;
case "ButtonX":
viewModel.Config.ButtonX = buttonValue.AsHidType<Key>();
break;
case "ButtonY":
viewModel.Config.ButtonY = buttonValue.AsHidType<Key>();
break;
case "RightStickButton":
viewModel.Config.RightStickButton = buttonValue.AsHidType<Key>();
break;
case "RightStickUp":
viewModel.Config.RightStickUp = buttonValue.AsHidType<Key>();
break;
case "RightStickDown":
viewModel.Config.RightStickDown = buttonValue.AsHidType<Key>();
break;
case "RightStickRight":
viewModel.Config.RightStickRight = buttonValue.AsHidType<Key>();
break;
case "RightStickLeft":
viewModel.Config.RightStickLeft = buttonValue.AsHidType<Key>();
break;
}
};
_currentAssigner.GetInputAndAssign(assigner, keyboard);
}
else
{
if (_currentAssigner != null)
{
_currentAssigner.Cancel();
_currentAssigner = null;
button.IsChecked = false;
}
}
};
_currentAssigner.GetInputAndAssign(assigner, keyboard);
}
else
{
_currentAssigner?.Cancel();
_currentAssigner = null;
if (_currentAssigner != null)
{
_currentAssigner.Cancel();
_currentAssigner = null;
button.IsChecked = false;
}
}
}
else
{
_currentAssigner?.Cancel();
_currentAssigner = null;
}
}
private void MouseClick(object sender, PointerPressedEventArgs e)
@@ -189,15 +192,6 @@ namespace Ryujinx.Ava.UI.Views.Input
PointerPressed -= MouseClick;
}
private IButtonAssigner CreateButtonAssigner()
{
IButtonAssigner assigner;
assigner = new KeyboardKeyAssigner((IKeyboard)(DataContext as KeyboardInputViewModel).ParentModel.SelectedGamepad);
return assigner;
}
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnDetachedFromVisualTree(e);

View File

@@ -273,8 +273,8 @@
</MenuItem>
<MenuItem VerticalAlignment="Center" Header="{ext:Locale MenuBarView}">
<MenuItem VerticalAlignment="Center" Header="{ext:Locale MenuBarViewWindow}">
<MenuItem Header="{ext:Locale MenuBarViewWindow720}" Tag="720 1280" Click="ChangeWindowSize_Click" />
<MenuItem Header="{ext:Locale MenuBarViewWindow1080}" Tag="1080 1920" Click="ChangeWindowSize_Click" />
<MenuItem Header="{ext:Locale MenuBarViewWindow720}" Tag="1280 720" Click="ChangeWindowSize_Click" />
<MenuItem Header="{ext:Locale MenuBarViewWindow1080}" Tag="1920 1080" Click="ChangeWindowSize_Click" />
</MenuItem>
</MenuItem>
<MenuItem VerticalAlignment="Center" Header="{ext:Locale MenuBarHelp}">

View File

@@ -184,8 +184,10 @@ namespace Ryujinx.Ava.UI.Views.Main
if (sender is not MenuItem { Tag: string resolution })
return;
(int height, int width) = resolution.Split(' ')
.Into(parts => (int.Parse(parts[0]), int.Parse(parts[1])));
(int width, int height) = resolution.Split(' ', 2)
.Into(parts =>
(int.Parse(parts[0]), int.Parse(parts[1]))
);
await Dispatcher.UIThread.InvokeAsync(() =>
{
@@ -200,7 +202,7 @@ namespace Ryujinx.Ava.UI.Views.Main
public async void CheckForUpdates(object sender, RoutedEventArgs e)
{
if (Updater.CanUpdate(true))
await Updater.BeginParse(Window, true);
await Window.BeginUpdateAsync(true);
}
public async void OpenXCITrimmerWindow(object sender, RoutedEventArgs e) => await XCITrimmerWindow.Show(ViewModel);

View File

@@ -33,7 +33,7 @@ namespace Ryujinx.Ava.UI.Views.Main
LocaleManager.Instance.LocaleChanged += () => Dispatcher.UIThread.Post(() =>
{
if (Window.ViewModel.EnableNonGameRunningControls)
Refresh_OnClick(null, null);
Window.LoadApplications();
});
}
}

View File

@@ -63,8 +63,7 @@ namespace Ryujinx.Ava.UI.Views.User
private async void Import_OnClick(object sender, RoutedEventArgs e)
{
var window = this.GetVisualRoot() as Window;
var result = await window.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
var result = await ((Window)this.GetVisualRoot()!).StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
{
AllowMultiple = false,
FileTypeFilter = new List<FilePickerFileType>

View File

@@ -28,6 +28,7 @@ using Ryujinx.UI.Common.Configuration;
using Ryujinx.UI.Common.Helper;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using System.Runtime.Versioning;
using System.Threading;
@@ -349,12 +350,12 @@ namespace Ryujinx.Ava.UI.Windows
await Dispatcher.UIThread.InvokeAsync(async () => await UserErrorDialog.ShowUserErrorDialog(UserError.NoKeys));
}
if (ConfigurationState.Instance.CheckUpdatesOnStart && Updater.CanUpdate(false))
if (ConfigurationState.Instance.CheckUpdatesOnStart && Updater.CanUpdate())
{
await Updater.BeginParse(this, false).ContinueWith(task =>
{
Logger.Error?.Print(LogClass.Application, $"Updater Error: {task.Exception}");
}, TaskContinuationOptions.OnlyOnFaulted);
await this.BeginUpdateAsync()
.ContinueWith(
task => Logger.Error?.Print(LogClass.Application, $"Updater Error: {task.Exception}"),
TaskContinuationOptions.OnlyOnFaulted);
}
}
@@ -392,30 +393,17 @@ namespace Ryujinx.Ava.UI.Windows
ViewModel.WindowState = ConfigurationState.Instance.UI.WindowStartup.WindowMaximized.Value ? WindowState.Maximized : WindowState.Normal;
if (CheckScreenBounds(savedPoint))
if (Screens.All.Any(screen => screen.Bounds.Contains(savedPoint)))
{
Position = savedPoint;
}
else
{
Logger.Warning?.Print(LogClass.Application, "Failed to find valid start-up coordinates. Defaulting to primary monitor center.");
WindowStartupLocation = WindowStartupLocation.CenterScreen;
}
}
private bool CheckScreenBounds(PixelPoint configPoint)
{
for (int i = 0; i < Screens.ScreenCount; i++)
{
if (Screens.All[i].Bounds.Contains(configPoint))
{
return true;
}
}
Logger.Warning?.Print(LogClass.Application, "Failed to find valid start-up coordinates. Defaulting to primary monitor center.");
return false;
}
private void SaveWindowSizePosition()
{
ConfigurationState.Instance.UI.WindowStartup.WindowMaximized.Value = WindowState == WindowState.Maximized;
@@ -507,8 +495,7 @@ namespace Ryujinx.Ava.UI.Windows
private void VolumeStatus_CheckedChanged(object sender, RoutedEventArgs e)
{
var volumeSplitButton = sender as ToggleSplitButton;
if (ViewModel.IsGameRunning)
if (ViewModel.IsGameRunning && sender is ToggleSplitButton volumeSplitButton)
{
if (!volumeSplitButton.IsChecked)
{

View File

@@ -32,6 +32,8 @@ namespace Ryujinx.Ava
internal static class Updater
{
private const string GitHubApiUrl = "https://api.github.com";
private const string LatestReleaseUrl = $"{GitHubApiUrl}/repos/{ReleaseInformation.ReleaseChannelOwner}/{ReleaseInformation.ReleaseChannelRepo}/releases/latest";
private static readonly GithubReleasesJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions());
private static readonly string _homeDir = AppDomain.CurrentDomain.BaseDirectory;
@@ -46,9 +48,9 @@ namespace Ryujinx.Ava
private static bool _updateSuccessful;
private static bool _running;
private static readonly string[] _windowsDependencyDirs = Array.Empty<string>();
private static readonly string[] _windowsDependencyDirs = [];
public static async Task BeginParse(Window mainWindow, bool showVersionUpToDate)
public static async Task BeginUpdateAsync(this Window mainWindow, bool showVersionUpToDate = false)
{
if (_running)
{
@@ -96,11 +98,10 @@ namespace Ryujinx.Ava
try
{
using HttpClient jsonClient = ConstructHttpClient();
string buildInfoUrl = $"{GitHubApiUrl}/repos/{ReleaseInformation.ReleaseChannelOwner}/{ReleaseInformation.ReleaseChannelRepo}/releases/latest";
string fetchedJson = await jsonClient.GetStringAsync(buildInfoUrl);
string fetchedJson = await jsonClient.GetStringAsync(LatestReleaseUrl);
var fetched = JsonHelper.Deserialize(fetchedJson, _serializerContext.GithubReleasesJsonResponse);
_buildVer = fetched.Name;
_buildVer = fetched.TagName;
foreach (var asset in fetched.Assets)
{
@@ -159,7 +160,7 @@ namespace Ryujinx.Ava
}
catch
{
Logger.Error?.Print(LogClass.Application, "Failed to convert the received Ryujinx version from Github!");
Logger.Error?.Print(LogClass.Application, $"Failed to convert the received {App.FullAppName} version from GitHub!");
await ContentDialogHelper.CreateWarningDialog(
LocaleManager.Instance[LocaleKeys.DialogUpdaterConvertFailedGithubMessage],
@@ -636,7 +637,7 @@ namespace Ryujinx.Ava
taskDialog.Hide();
}
public static bool CanUpdate(bool showWarnings)
public static bool CanUpdate(bool showWarnings = false)
{
#if !DISABLE_UPDATER
if (!NetworkInterface.GetIsNetworkAvailable())