Compare commits

..

8 Commits

Author SHA1 Message Date
Daniel Nylander
10d20c1ae3 Update Swedish locale (#720) 2025-02-26 02:02:07 -06:00
Evan Husted
e294a79975 UI: dev: [ci skip] Add Avalonia DevTools support to all Windows defined by Ryujinx, accessible via Ctrl + F12 when running in Debug. 2025-02-25 23:12:57 -06:00
Evan Husted
ec06a86899 UI: Increase default size for setting windows to include autoload setting & the bottom of the input settings 2025-02-25 22:51:26 -06:00
Vladimir Sokolov
2e4de17472 Custom configuration for each game (#632)
![image](https://github.com/user-attachments/assets/5dd139b4-2004-4c13-85d1-fc3378382adf)

![image](https://github.com/user-attachments/assets/9bcb8524-a403-428f-9f98-e8c03c75f079)

Now you can make a separate configuration (independent file) for each
game. All emulator settings are available except for some UI functionality ones. 
The configuration file can be changed and deleted from a separate menu. The
user configuration menu is available through the context menu on a given application.

---------

Co-authored-by: Evan Husted <greem@greemdev.net>
2025-02-25 21:11:20 -06:00
Piplup
9227cbe5a7 Dynamic RPC: Improve Pokemon Scarlet/Violet (#723)
Updated Pokemon Scarlet and Violet to use multi parser it now displays
if your in a team circle and area of the game

![image](https://github.com/user-attachments/assets/6d8d52c5-65a9-4ac3-af91-cb0c03576ad6)
2025-02-25 17:48:47 -06:00
Evan Husted
332bcdfaf1 UI: Updater: Add support for eventual Windows on ARM updates 2025-02-25 17:34:56 -06:00
FluffyOMC
1c8276197f SSBU DRPC - Stage Editing (#707)
Adds it so the Rich Presence now notices when the player edits a custom
stage!
2025-02-25 15:48:35 -06:00
LotP1
a3596ba858 Reset in-memory JIT cache on game quit + fix Purge PPTC (#709)
Jit cache now fully resets when booting a game multiple times.
This should fix random jit cache crashes.
Also removed some redundant code related to region allocation and fixed
PPTC Purge not fully purging all PPTC files in the backup folder.
2025-02-25 15:34:21 -06:00
23 changed files with 353 additions and 208 deletions

View File

@@ -24,7 +24,7 @@ namespace ARMeilleure.Translation.Cache
private static JitCacheInvalidation _jitCacheInvalidator;
private static CacheMemoryAllocator _cacheAllocator;
private static List<CacheMemoryAllocator> _cacheAllocators = [];
private static readonly List<CacheEntry> _cacheEntries = [];
@@ -40,37 +40,48 @@ namespace ARMeilleure.Translation.Cache
public static void Initialize(IJitMemoryAllocator allocator)
{
if (_initialized)
{
return;
}
lock (_lock)
{
if (_initialized)
{
return;
if (OperatingSystem.IsWindows())
{
JitUnwindWindows.RemoveFunctionTableHandler(
_jitRegions[0].Pointer);
}
for (int i = 0; i < _jitRegions.Count; i++)
{
_jitRegions[i].Dispose();
}
_jitRegions.Clear();
_cacheAllocators.Clear();
}
else
{
_initialized = true;
}
_activeRegionIndex = 0;
ReservedRegion firstRegion = new(allocator, CacheSize);
_jitRegions.Add(firstRegion);
_activeRegionIndex = 0;
CacheMemoryAllocator firstCacheAllocator = new(CacheSize);
_cacheAllocators.Add(firstCacheAllocator);
if (!OperatingSystem.IsWindows() && !OperatingSystem.IsMacOS())
{
_jitCacheInvalidator = new JitCacheInvalidation(allocator);
}
_cacheAllocator = new CacheMemoryAllocator(CacheSize);
if (OperatingSystem.IsWindows())
{
JitUnwindWindows.InstallFunctionTableHandler(
firstRegion.Pointer, CacheSize, firstRegion.Pointer + Allocate(_pageSize)
);
}
_initialized = true;
}
}
@@ -136,7 +147,7 @@ namespace ARMeilleure.Translation.Cache
if (TryFind(funcOffset, out CacheEntry entry, out int entryIndex) && entry.Offset == funcOffset)
{
_cacheAllocator.Free(funcOffset, AlignCodeSize(entry.Size));
_cacheAllocators[_activeRegionIndex].Free(funcOffset, AlignCodeSize(entry.Size));
_cacheEntries.RemoveAt(entryIndex);
}
@@ -167,30 +178,24 @@ namespace ARMeilleure.Translation.Cache
{
codeSize = AlignCodeSize(codeSize);
for (int i = _activeRegionIndex; i < _jitRegions.Count; i++)
int allocOffset = _cacheAllocators[_activeRegionIndex].Allocate(codeSize);
if (allocOffset >= 0)
{
int allocOffset = _cacheAllocator.Allocate(codeSize);
if (allocOffset >= 0)
{
_jitRegions[i].ExpandIfNeeded((ulong)allocOffset + (ulong)codeSize);
_activeRegionIndex = i;
return allocOffset;
}
_jitRegions[_activeRegionIndex].ExpandIfNeeded((ulong)allocOffset + (ulong)codeSize);
return allocOffset;
}
int exhaustedRegion = _activeRegionIndex;
ReservedRegion newRegion = new(_jitRegions[0].Allocator, CacheSize);
_jitRegions.Add(newRegion);
_activeRegionIndex = _jitRegions.Count - 1;
int newRegionNumber = _activeRegionIndex;
Logger.Warning?.Print(LogClass.Cpu, $"JIT Cache Region {exhaustedRegion} exhausted, creating new Cache Region {newRegionNumber} ({((long)(newRegionNumber + 1) * CacheSize).Bytes()} Total Allocation).");
_cacheAllocator = new CacheMemoryAllocator(CacheSize);
Logger.Warning?.Print(LogClass.Cpu, $"JIT Cache Region {exhaustedRegion} exhausted, creating new Cache Region {_activeRegionIndex} ({((long)(_activeRegionIndex + 1) * CacheSize).Bytes()} Total Allocation).");
int allocOffsetNew = _cacheAllocator.Allocate(codeSize);
_cacheAllocators.Add(new CacheMemoryAllocator(CacheSize));
int allocOffsetNew = _cacheAllocators[_activeRegionIndex].Allocate(codeSize);
if (allocOffsetNew < 0)
{
throw new OutOfMemoryException("Failed to allocate in new Cache Region!");

View File

@@ -52,6 +52,11 @@ namespace ARMeilleure.Translation.Cache
nint context,
[MarshalAs(UnmanagedType.LPWStr)] string outOfProcessCallbackDll);
[LibraryImport("kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static unsafe partial bool RtlDeleteFunctionTable(
ulong tableIdentifier);
private static GetRuntimeFunctionCallback _getRuntimeFunctionCallback;
private static int _sizeOfRuntimeFunction;
@@ -91,6 +96,23 @@ namespace ARMeilleure.Translation.Cache
}
}
public static void RemoveFunctionTableHandler(nint codeCachePointer)
{
ulong codeCachePtr = (ulong)codeCachePointer.ToInt64();
bool result;
unsafe
{
result = RtlDeleteFunctionTable(codeCachePtr | 3);
}
if (!result)
{
throw new InvalidOperationException("Failure removing function table callback.");
}
}
private static unsafe RuntimeFunction* FunctionTableHandler(ulong controlPc, nint context)
{
int offset = (int)((long)controlPc - context.ToInt64());

View File

@@ -5,15 +5,34 @@ using System.Runtime.InteropServices;
namespace Ryujinx.Common.Helper
{
public enum OperatingSystemType
{
MacOS,
Linux,
Windows
}
public static class RunningPlatform
{
public static readonly OperatingSystemType CurrentOS
= IsMacOS
? OperatingSystemType.MacOS
: IsWindows
? OperatingSystemType.Windows
: IsLinux
? OperatingSystemType.Linux
: throw new PlatformNotSupportedException();
public static Architecture Architecture => RuntimeInformation.OSArchitecture;
public static Architecture CurrentProcessArchitecture => RuntimeInformation.ProcessArchitecture;
public static bool IsMacOS => OperatingSystem.IsMacOS();
public static bool IsWindows => OperatingSystem.IsWindows();
public static bool IsLinux => OperatingSystem.IsLinux();
public static bool IsArm => RuntimeInformation.OSArchitecture is Architecture.Arm64;
public static bool IsArm => Architecture is Architecture.Arm64;
public static bool IsX64 => RuntimeInformation.OSArchitecture is Architecture.X64;
public static bool IsX64 => Architecture is Architecture.X64;
public static bool IsIntelMac => IsMacOS && IsX64;
public static bool IsArmMac => IsMacOS && IsArm;

View File

@@ -343,7 +343,7 @@
<Setter Property="Foreground" Value="SeaGreen"/>
<Setter Property="Margin" Value="5,0,0,0"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Text" Value="(Global)"/>
<Setter Property="Text" Value="{ext:Locale GameSpecificConfigurationGlobal}"/>
</Style>
<Style Selector="StackPanel.globalConfigMarker">
</Style>

View File

@@ -464,7 +464,7 @@
"pl_PL": "",
"pt_BR": "Abrir Pasta de Capturas de Tela",
"ru_RU": "Открыть папку со скриншотами",
"sv_SE": "",
"sv_SE": "Öppna skärmbildsmappen",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Відкрити теку скріншотів",
@@ -1564,7 +1564,7 @@
"pl_PL": "",
"pt_BR": "Desenvolvido por {0}",
"ru_RU": "Разработана {0}",
"sv_SE": "",
"sv_SE": "Utvecklat av {0}",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Розроблено: {0}",
@@ -1864,7 +1864,7 @@
"pl_PL": "",
"pt_BR": "Compatibilidade:",
"ru_RU": "Совместимость:",
"sv_SE": "",
"sv_SE": "Kompatibilitet:",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Сумісність:",
@@ -1889,7 +1889,7 @@
"pl_PL": "",
"pt_BR": "ID do Título:",
"ru_RU": "ID приложения",
"sv_SE": "",
"sv_SE": "Titel-id:",
"th_TH": "",
"tr_TR": "",
"uk_UA": "ID гри:",
@@ -1914,7 +1914,7 @@
"pl_PL": "",
"pt_BR": "Jogos Hospedados: {0}",
"ru_RU": "Запущенно игр: {0}",
"sv_SE": "",
"sv_SE": "Värdskap för spel: {0}",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Розміщені ігри: {0}",
@@ -1939,7 +1939,7 @@
"pl_PL": "",
"pt_BR": "Jogadores Online: {0}",
"ru_RU": "Игроков онлайн: {0}",
"sv_SE": "",
"sv_SE": "Online-spelare: {0}",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Гравців онлайн: {0}",
@@ -2289,7 +2289,7 @@
"pl_PL": "",
"pt_BR": "Limpar Cache PPTC",
"ru_RU": "Очистить кэш PPTC",
"sv_SE": "",
"sv_SE": "Rensa PPTC-cache",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Очистити кеш PPTC",
@@ -2314,7 +2314,7 @@
"pl_PL": "",
"pt_BR": "Apaga os arquivos de cache PPTC do aplicativo",
"ru_RU": "Удаляет все файлы кэша PPTC для приложения",
"sv_SE": "",
"sv_SE": "Tar bort alla PPTC-cachefiler för applikationen",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Видаляє всі файли кешу PPTC для застосунку",
@@ -2748,28 +2748,53 @@
}
},
{
"ID": "GameListContextMenuEditGameConfiguration",
"ID": "GameListContextMenuCreateCustomConfiguration",
"Translations": {
"ar_SA": "تعديل تكوين اللعبة",
"de_DE": "Spielkonfiguration bearbeiten",
"el_GR": "Επεξεργασία ρυθμίσεων παιχνιδιού",
"en_US": "Edit Game Configuration",
"es_ES": "Editar configuración del juego",
"fr_FR": "Modifier la configuration du jeu",
"he_IL": "ערוך את הגדרת המשחק",
"it_IT": "Modifica configurazione gioco",
"ja_JP": "ゲーム設定を編集",
"ko_KR": "게임 설정 편집",
"no_NO": "Rediger spillkonfig.",
"pl_PL": "Edytuj konfigurację gry",
"pt_BR": "Editar configuração do jogo",
"ru_RU": "Редактировать конфигурацию игры",
"sv_SE": "Redigera spelkonfiguration",
"th_TH": "แก้ไขการตั้งค่าเกม",
"tr_TR": "Oyunun yapılandırmasını düzenle",
"uk_UA": "Редагувати конфігурацію гри",
"zh_CN": "编辑游戏配置",
"zh_TW": "編輯遊戲設定"
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Create Custom Configuration",
"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": ""
}
},
{
"ID": "GameListContextMenuEditCustomConfiguration",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Edit Custom Configuration",
"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": ""
}
},
{
@@ -2798,12 +2823,12 @@
}
},
{
"ID": "EditGameConfigurationToolTip",
"ID": "CreateCustomConfigurationToolTip",
"Translations": {
"ar_SA": "ينشئ تكوينًا مستقلًا للعبة الحالية",
"de_DE": "Erstellt eine unabhängige Konfiguration für das aktuelle Spiel",
"el_GR": "Δημιουργεί μια ανεξάρτητη διαμόρφωση για το τρέχον παιχνίδι",
"en_US": "Creates an independent configuration for the current game",
"en_US": "Creates an independent configuration for the selected game",
"es_ES": "Crea una configuración independiente para el juego actual",
"fr_FR": "Crée une configuration indépendante pour le jeu en cours",
"he_IL": "יוצר תצורה עצמאית למשחק הנוכחי",
@@ -2822,6 +2847,31 @@
"zh_TW": "為當前遊戲創建獨立的配置"
}
},
{
"ID": "EditCustomConfigurationToolTip",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Edit your existing independent configuration for the selected game",
"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": ""
}
},
{
"ID": "GameListContextMenuShowCompatEntry",
"Translations": {
@@ -2839,7 +2889,7 @@
"pl_PL": "",
"pt_BR": "Mostrar Dados de Compatibilidade",
"ru_RU": "Показать записи о совместимости",
"sv_SE": "",
"sv_SE": "Visa kompatibilitetspost",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Iнформація про сумісність",
@@ -2864,7 +2914,7 @@
"pl_PL": "",
"pt_BR": "Exibe o jogo selecionado na Lista de Compatibilidade, que normalmente pode ser acessada pelo menu Ajuda.",
"ru_RU": "Отобразить выбранную игру в списке совместимости, доступ к которому вы обычно можете получить через меню Справки.",
"sv_SE": "",
"sv_SE": "Visa valt spel i kompatibilitetslistan som du normalt sett kan komma åt via hjälpmenyn.",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Показати цю гру в Списку Сумісності. Список сумісності також можна зайти в меню Довідки.",
@@ -2889,7 +2939,7 @@
"pl_PL": "",
"pt_BR": "Mostrar Informações do Jogo",
"ru_RU": "Показать информацию об игре",
"sv_SE": "",
"sv_SE": "Visa spelinformation",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Інформація про гру",
@@ -2914,7 +2964,7 @@
"pl_PL": "",
"pt_BR": "Exibe estatísticas e detalhes sobre o jogo selecionado.",
"ru_RU": "Показывать статистику и подробную информацию о выбранной игре.",
"sv_SE": "",
"sv_SE": "Visa statistik och detaljer om det aktuella spelet.",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Показати статистику та деталі обраної гри.",
@@ -3464,7 +3514,7 @@
"pl_PL": "",
"pt_BR": "Verificar Atualizações:",
"ru_RU": "Проверка наличия обновлений",
"sv_SE": "",
"sv_SE": "Leta efter uppdateringar:",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Перевірка оновлень:",
@@ -3489,7 +3539,7 @@
"pl_PL": "",
"pt_BR": "Desligado",
"ru_RU": "Отключить",
"sv_SE": "",
"sv_SE": "Av",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Вимкнути",
@@ -3514,7 +3564,7 @@
"pl_PL": "",
"pt_BR": "Ao Abrir",
"ru_RU": "При запуске",
"sv_SE": "",
"sv_SE": "Fråga",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Запитувати щоразу",
@@ -3539,7 +3589,7 @@
"pl_PL": "",
"pt_BR": "2° Plano",
"ru_RU": "В фоне",
"sv_SE": "",
"sv_SE": "Bakgrund",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Оновлювати в фоні",
@@ -3564,7 +3614,7 @@
"pl_PL": "",
"pt_BR": "Ao Perder o Foco:",
"ru_RU": "При выходе эмулятора из фокуса",
"sv_SE": "",
"sv_SE": "När emulatorn tappar fokus:",
"th_TH": "",
"tr_TR": "",
"uk_UA": "При втраті фокуса емулятором:",
@@ -3589,7 +3639,7 @@
"pl_PL": "",
"pt_BR": "Não Fazer Nada",
"ru_RU": "Ничего не делать",
"sv_SE": "",
"sv_SE": "Gör ingenting",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Нічого не робити",
@@ -3614,7 +3664,7 @@
"pl_PL": "",
"pt_BR": "Bloquear Controles",
"ru_RU": "Блокировать управление",
"sv_SE": "",
"sv_SE": "Blockera inmatning",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Блокувати введення",
@@ -3639,7 +3689,7 @@
"pl_PL": "",
"pt_BR": "Ficar Mudo",
"ru_RU": "Отключить звук",
"sv_SE": "",
"sv_SE": "Stäng av ljudet",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Вимкнути звук",
@@ -3664,7 +3714,7 @@
"pl_PL": "",
"pt_BR": "Bloquear Controles & Ficar Mudo",
"ru_RU": "Блокировать управление и отключить звук",
"sv_SE": "",
"sv_SE": "Blockera inmatningar och stäng av ljudet",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Блокувати введення та Вимкнути звук",
@@ -3689,7 +3739,7 @@
"pl_PL": "",
"pt_BR": "Pausar a Emulação",
"ru_RU": "Поставить паузу",
"sv_SE": "",
"sv_SE": "Pausa emuleringen",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Поставити на паузу",
@@ -3764,7 +3814,7 @@
"pl_PL": "",
"pt_BR": "Desativar Controles Quando Estiver Fora de Foco",
"ru_RU": "Отключает управление при выходе из фокуса",
"sv_SE": "",
"sv_SE": "Inaktivera inmatning när fokus tappas",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
@@ -4789,7 +4839,7 @@
"pl_PL": "",
"pt_BR": "Sincronizar com o Sistema PC",
"ru_RU": "Соответствовать времени в системе",
"sv_SE": "",
"sv_SE": "Matcha systemtid",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
@@ -5214,7 +5264,7 @@
"pl_PL": "",
"pt_BR": "Ignorar Applet do Controlador",
"ru_RU": "Игнорировать апплет контроллера",
"sv_SE": "",
"sv_SE": "Ignorera kontroller-applet",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Ігнорувати Аплет Контролера",
@@ -6064,7 +6114,7 @@
"pl_PL": "",
"pt_BR": "Habilitar Logs da IU",
"ru_RU": "Включить журнал интерфейса",
"sv_SE": "",
"sv_SE": "Aktivera gränssnittsloggar",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Увімкнути журнали інтерфейсу",
@@ -6464,7 +6514,7 @@
"pl_PL": "",
"pt_BR": "Redefinir Configurações",
"ru_RU": "Сбросить настройки",
"sv_SE": "",
"sv_SE": "Nollställ inställningar",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Скинути налаштування",
@@ -6489,7 +6539,7 @@
"pl_PL": "",
"pt_BR": "Quero redefinir minhas configurações.",
"ru_RU": "Я хочу сбросить свои настройки.",
"sv_SE": "",
"sv_SE": "Jag vill nollställa mina inställningar.",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Я хочу скинути налаштування.",
@@ -8464,7 +8514,7 @@
"pl_PL": "",
"pt_BR": "Velocidade do Arco-íris",
"ru_RU": "Скорость переливания",
"sv_SE": "",
"sv_SE": "Regnbågshastighet",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
@@ -13764,7 +13814,7 @@
"pl_PL": "",
"pt_BR": "Você está prestes a limpar todos os dados PPTC de:\n\n{0}\n\nTem certeza de que deseja continuar?",
"ru_RU": "Вы собираетесь удалить все данные PPTC из:\n\n{0}\n\nВы уверены, что хотите продолжить?",
"sv_SE": "",
"sv_SE": "Du är på väg att ta bort allt PPTC-data från:\n\n{0}\n\nÄr du säker på att du vill fortsätta?",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Ви збираєтесь видалити всі дані PPTC з:\n\n{0}\n\nБажаєте продовжити цю операцію?",
@@ -16614,7 +16664,7 @@
"pl_PL": "",
"pt_BR": "A caixa de diálogo do Applet do controlador não aparecerá se o controle for desconectado enquanto um aplicativo estiver em execução.\n\nDeixe a opção DESLIGADO se não tiver certeza.",
"ru_RU": "Диалоговое окно апплета контроллера не будет отображаться, если геймпад отключен во время работы приложения.\n\nОставьте выключенным, если не уверены.",
"sv_SE": "",
"sv_SE": "Handkontroller-appleten kommer inte att visas om gamepaden är frånkopplad under tiden en applikation körs.\n\nLämna AV om du är osäker.",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Діалогове вікно Аплету Контролера не з'явиться, якщо геймпад було відключено під час роботи програми.\n\nЗалиште вимкненим якщо не впевнені.",
@@ -17089,7 +17139,7 @@
"pl_PL": "",
"pt_BR": "Imprime mensagens de log do Avalonia (UI) no console.",
"ru_RU": "Выводит сообщения журнала Avalonia (интерфейс) в консоли.",
"sv_SE": "",
"sv_SE": "Skriver ut loggmeddelanden från Avalonia (användargränssnittet) i konsollen.",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Виводити повідомлення журналу Avalonia (UI) в консоль",
@@ -17289,7 +17339,7 @@
"pl_PL": "",
"pt_BR": "Abre a pasta de capturas de tela do Ryujinx",
"ru_RU": "Открывает папку скриншотов Ryujinx",
"sv_SE": "",
"sv_SE": "Öppna Ryujinx skärmbildsmapp",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Відкрити теку куди зберігаються скріншоти Ryujinx",
@@ -18039,7 +18089,7 @@
"pl_PL": "",
"pt_BR": "Atualização Disponível!",
"ru_RU": "Доступно обновление!",
"sv_SE": "",
"sv_SE": "Uppdatering finns tillgänglig!",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Доступне оновлення!",
@@ -24014,7 +24064,7 @@
"pl_PL": "",
"pt_BR": "Inicializa e roda sem travamentos ou bugs de GPU de qualquer tipo, e em uma velocidade rápida o suficiente para ser aproveitado em um PC comum.",
"ru_RU": "Запускается и работает без любого рода сбоев или графисечких ошибок и на скорости, достаточной для работы на обычном ПК.",
"sv_SE": "",
"sv_SE": "Startar upp och spelas utan några krascher eller GPU-fel av några slag och med en hastighet som är snabb nog för bra upplevelse på en genomsnittlig PC.",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Запускається та оптимально працює (без збоїв або графічних багів) на середньостатистичному комп'ютері.",
@@ -24039,7 +24089,7 @@
"pl_PL": "",
"pt_BR": "Inicializa e entra no jogo, mas sofre de um ou mais dos seguintes: travamentos, deadlocks, bugs de GPU, áudio ruim que distrai ou é simplesmente muito lento. O jogo ainda pode ser jogado até o fim, mas não da forma como foi criado para ser jogado.",
"ru_RU": "Запускается и работает, но возникает одна или несколько из следующих проблем: сбои, взаимоблокировки, ошибки GPU, отвлекающие звуки или просто слишком медленная работа. Возможно, игру всё же удастся пройти до конца, но не так, как она задумана.",
"sv_SE": "",
"sv_SE": "Startar och går in i spelet men lider av ett eller flera av följande: kraschar, deadlocks, GPU-buggar, distraherande dåligt ljud eller är helt enkelt för långsamt. Spelet kan fortfarande spelas hela vägen igenom, men inte så som spelet är avsett att spelas.",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Запускається, але в грі на вас чекатимуть одна або декілька наступних проблем: збої, зависання, графічні баги, спотворений звук або ж гра загалом працюватиме надто повільно. Можливо, її все ще можна пройти, але досвід буде не найкращим.",
@@ -24064,7 +24114,7 @@
"pl_PL": "",
"pt_BR": "Inicializa e passa da tela de título, mas não entra no jogo principal.",
"ru_RU": "Загружается титульный экран и можно перейти дальше, но сама игра не работает.",
"sv_SE": "",
"sv_SE": "Startar upp och går förbi titelskärmen men tar sig inte in i huvudspelet.",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Запускається та проходить початковий екран, але пограти не вийде.",
@@ -24089,7 +24139,7 @@
"pl_PL": "",
"pt_BR": "Inizializa, mas não passa da tela de título.",
"ru_RU": "Загружается, но не проходит дальше титульного экрана.",
"sv_SE": "",
"sv_SE": "Startar upp men tar sig inte förbi titelskärmen.",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Запускається, але не відображає навіть початкового екрану.",
@@ -24114,7 +24164,7 @@
"pl_PL": "",
"pt_BR": "Não inicializa ou não mostra sinais de atividade.",
"ru_RU": "Не запускается или не подаёт признаков жизни.",
"sv_SE": "",
"sv_SE": "Startar inte upp eller visar någon form av aktivitet.",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Взагалі не запускається.",
@@ -24123,12 +24173,37 @@
}
},
{
"ID": "UserConfigurationHeader",
"ID": "GameSpecificConfigurationHeader",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "User Config",
"en_US": "Custom Config",
"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": ""
}
},
{
"ID": "GameSpecificConfigurationGlobal",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "(Global)",
"es_ES": "",
"fr_FR": "",
"he_IL": "",
@@ -24189,7 +24264,7 @@
"pl_PL": "",
"pt_BR": "Imagem da Presença do Discord",
"ru_RU": "Изображение для статуса активности",
"sv_SE": "",
"sv_SE": "Bild för Rich Presence",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Зображення картки активності Discord",
@@ -24214,7 +24289,7 @@
"pl_PL": "",
"pt_BR": "Presença Dinâmica do Discord",
"ru_RU": "Динамический статус активности",
"sv_SE": "",
"sv_SE": "Dynamisk Rich Presence",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Динамічна картка активності Discord",

View File

@@ -158,16 +158,6 @@ namespace Ryujinx.Ava
}
}
public static bool FindGameConfig(string gameDir)
{
if (File.Exists(gameDir))
{
return true;
}
return false;
}
public static string GetDirGameUserConfig(string gameId, bool rememberGlobalDir = false, bool changeFolderForGame = false)
{
if (string.IsNullOrEmpty(gameId))

View File

@@ -42,52 +42,33 @@ namespace Ryujinx.Ava
List<string> arguments = CommandLineState.Arguments.ToList();
string executableDirectory = AppDomain.CurrentDomain.BaseDirectory;
// On macOS we perform the update at relaunch.
if (OperatingSystem.IsMacOS())
var dialogTask = taskDialog.ShowAsync(true);
await Task.Delay(500);
// Find the process name.
string ryuName = Path.GetFileName(Environment.ProcessPath) ?? string.Empty;
// Fallback if the executable could not be found.
if (ryuName.Length == 0 || !Path.Exists(Path.Combine(executableDirectory, ryuName)))
{
string baseBundlePath = Path.GetFullPath(Path.Combine(executableDirectory, "..", ".."));
string newBundlePath = Path.Combine(_updateDir, "Ryujinx.app");
string updaterScriptPath = Path.Combine(newBundlePath, "Contents", "Resources", "updater.sh");
string currentPid = Environment.ProcessId.ToString();
arguments.InsertRange(0, new List<string> { updaterScriptPath, baseBundlePath, newBundlePath, currentPid });
Process.Start("/bin/bash", arguments);
ryuName = OperatingSystem.IsWindows() ? "Ryujinx.exe" : "Ryujinx";
}
else
ProcessStartInfo processStart = new(ryuName)
{
var dialogTask = taskDialog.ShowAsync(true);
await Task.Delay(500);
UseShellExecute = true,
WorkingDirectory = executableDirectory,
};
// Find the process name.
string ryuName = Path.GetFileName(Environment.ProcessPath) ?? string.Empty;
// Some operating systems can see the renamed executable, so strip off the .ryuold if found.
if (ryuName.EndsWith(".ryuold"))
{
ryuName = ryuName[..^7];
}
// Fallback if the executable could not be found.
if (ryuName.Length == 0 || !Path.Exists(Path.Combine(executableDirectory, ryuName)))
{
ryuName = OperatingSystem.IsWindows() ? "Ryujinx.exe" : "Ryujinx";
}
ProcessStartInfo processStart = new(ryuName)
{
UseShellExecute = true,
WorkingDirectory = executableDirectory,
};
foreach (var arg in args)
{
processStart.ArgumentList.Add(arg);
}
processStart.ArgumentList.Add(gamePath);
Process.Start(processStart);
foreach (var arg in args)
{
processStart.ArgumentList.Add(arg);
}
processStart.ArgumentList.Add(gamePath);
Process.Start(processStart);
Environment.Exit(0);
}
}

View File

@@ -95,7 +95,7 @@ namespace Ryujinx.Ava.UI.Applet
_parent.SettingsWindow =
new SettingsWindow(_parent.VirtualFileSystem, _parent.ContentManager);
await _parent.SettingsWindow.ShowDialog(window);
await StyleableAppWindow.ShowAsync(_parent.SettingsWindow, window);
_parent.SettingsWindow = null;

View File

@@ -19,11 +19,18 @@
Header="{ext:Locale GameListContextMenuCreateShortcut}"
Icon="{ext:Icon fa-solid fa-bookmark}"
ToolTip.Tip="{OnPlatform Default={ext:Locale GameListContextMenuCreateShortcutToolTip}, macOS={ext:Locale GameListContextMenuCreateShortcutToolTipMacOS}}" />
<MenuItem
Click="EditGameConfiguration_Click"
IsVisible="{Binding SelectedApplication.HasIndependentConfiguration}"
Header="{ext:Locale GameListContextMenuEditCustomConfiguration}"
Icon="{ext:Icon fa-solid fa-gear}"
ToolTip.Tip="{ext:Locale EditCustomConfigurationToolTip}" />
<MenuItem
Click="EditGameConfiguration_Click"
Header="{ext:Locale GameListContextMenuEditGameConfiguration}"
IsVisible="{Binding !SelectedApplication.HasIndependentConfiguration}"
Header="{ext:Locale GameListContextMenuCreateCustomConfiguration}"
Icon="{ext:Icon fa-solid fa-gear}"
ToolTip.Tip="{ext:Locale EditGameConfigurationToolTip}" />
ToolTip.Tip="{ext:Locale CreateCustomConfigurationToolTip}" />
<MenuItem
IsVisible="{Binding HasCompatibilityEntry}"
Click="OpenApplicationCompatibility_Click"

View File

@@ -91,11 +91,14 @@ namespace Ryujinx.Ava.UI.Controls
public async void OpenCheatManager_Click(object sender, RoutedEventArgs args)
{
if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
await new CheatWindow(
viewModel.VirtualFileSystem,
viewModel.SelectedApplication.IdString,
viewModel.SelectedApplication.Name,
viewModel.SelectedApplication.Path).ShowDialog((Window)viewModel.TopLevel);
await StyleableAppWindow.ShowAsync(
new CheatWindow(
viewModel.VirtualFileSystem,
viewModel.SelectedApplication.IdString,
viewModel.SelectedApplication.Name,
viewModel.SelectedApplication.Path
)
);
}
public void OpenModsDirectory_Click(object sender, RoutedEventArgs args)
@@ -200,7 +203,7 @@ namespace Ryujinx.Ava.UI.Controls
if (backupDir.Exists)
{
cacheFiles.AddRange(backupDir.EnumerateFiles("*.cache"));
cacheFiles.AddRange(mainDir.EnumerateFiles("*.info"));
cacheFiles.AddRange(backupDir.EnumerateFiles("*.info"));
}
if (cacheFiles.Count > 0)
@@ -391,9 +394,9 @@ namespace Ryujinx.Ava.UI.Controls
{
if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
{
await new GameSpecificSettingsWindow(viewModel).ShowDialog((Window)viewModel.TopLevel);
await StyleableAppWindow.ShowAsync(new GameSpecificSettingsWindow(viewModel));
//just checking for file presence
// just checking for file presence
viewModel.SelectedApplication.HasIndependentConfiguration = File.Exists(Program.GetDirGameUserConfig(viewModel.SelectedApplication.IdString,false,false));
viewModel.RefreshView();

View File

@@ -81,7 +81,7 @@
TextWrapping="Wrap" />
<TextBlock
IsVisible="{Binding HasIndependentConfiguration}"
Text="{ext:Locale UserConfigurationHeader}"
Text="{ext:Locale GameSpecificConfigurationHeader}"
TextAlignment="Center"
TextWrapping="Wrap"
Foreground="{DynamicResource Warning}" />
@@ -110,7 +110,7 @@
<TextBlock
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{ext:Locale UserConfigurationHeader}"
Text="{ext:Locale GameSpecificConfigurationHeader}"
TextAlignment="Center"
TextWrapping="Wrap" />
</Border>

View File

@@ -160,7 +160,7 @@
<TextBlock
HorizontalAlignment="Stretch"
IsVisible="{Binding HasIndependentConfiguration}"
Text="{ext:Locale UserConfigurationHeader}"
Text="{ext:Locale GameSpecificConfigurationHeader}"
TextAlignment="Start"
TextWrapping="Wrap"
Foreground="{DynamicResource Warning}" />

View File

@@ -1,6 +1,7 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Input;
using Avalonia.Layout;
using Avalonia.Media;
using Avalonia.Threading;
@@ -384,6 +385,10 @@ namespace Ryujinx.Ava.UI.Helpers
Position = parent.PointToScreen(new Point()),
ShowInTaskbar = false,
};
#if DEBUG
_contentDialogOverlayWindow.AttachDevTools(new KeyGesture(Key.F12, KeyModifiers.Control));
#endif
parent.PositionChanged += OverlayOnPositionChanged;

View File

@@ -1747,7 +1747,7 @@ namespace Ryujinx.Ava.UI.ViewModels
string titleId = AppHost.Device.Processes.ActiveApplication.ProgramIdText.ToUpper();
AmiiboWindow window = new(ShowAll, LastScannedAmiiboId, titleId);
await window.ShowDialog(Window);
await StyleableAppWindow.ShowAsync(window);
if (window.IsScanned)
{

View File

@@ -19,6 +19,7 @@ using Ryujinx.Common.Utilities;
using Ryujinx.HLE.HOS.Services.Nfc.AmiiboDecryption;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
@@ -133,20 +134,20 @@ namespace Ryujinx.Ava.UI.Views.Main
if (ViewModel.SelectedApplication is null) // Checks if game data exists
{
await Window.SettingsWindow.ShowDialog(Window);
await StyleableAppWindow.ShowAsync(Window.SettingsWindow);
}
else
{
bool userConfigExist = Program.FindGameConfig(Program.GetDirGameUserConfig(ViewModel.SelectedApplication.IdString, false, false));
bool customConfigExists = File.Exists(Program.GetDirGameUserConfig(ViewModel.SelectedApplication.IdString));
if (!ViewModel.IsGameRunning || !userConfigExist)
if (!ViewModel.IsGameRunning || !customConfigExists)
{
await Window.SettingsWindow.ShowDialog(Window); // The game is not running, or if the user configuration does not exist
}
else
{
// If there is a custom configuration in the folder
await new GameSpecificSettingsWindow(ViewModel, userConfigExist).ShowDialog((Window)ViewModel.TopLevel);
await StyleableAppWindow.ShowAsync(new GameSpecificSettingsWindow(ViewModel, customConfigExists));
}
}
@@ -175,11 +176,13 @@ namespace Ryujinx.Ava.UI.Views.Main
string name = ViewModel.AppHost.Device.Processes.ActiveApplication.ApplicationControlProperties.Title[(int)ViewModel.AppHost.Device.System.State.DesiredTitleLanguage].NameString.ToString();
await new CheatWindow(
Window.VirtualFileSystem,
ViewModel.AppHost.Device.Processes.ActiveApplication.ProgramIdText,
name,
ViewModel.SelectedApplication.Path).ShowDialog(Window);
await StyleableAppWindow.ShowAsync(
new CheatWindow(
Window.VirtualFileSystem,
ViewModel.AppHost.Device.Processes.ActiveApplication.ProgramIdText,
name,
ViewModel.SelectedApplication.Path)
);
ViewModel.AppHost.Device.EnableCheats();
}

View File

@@ -12,7 +12,7 @@
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
xmlns:helper="clr-namespace:Ryujinx.Common.Helper;assembly=Ryujinx.Common"
Width="1100"
Height="768"
Height="910"
MinWidth="800"
MinHeight="480"
WindowStartupLocation="CenterOwner"

View File

@@ -47,11 +47,6 @@ namespace Ryujinx.Ava.UI.Windows
InitializeComponent();
Load();
#if DEBUG
this.AttachDevTools(new KeyGesture(Key.F12, KeyModifiers.Alt));
#endif
}
public void SaveSettings()
@@ -65,7 +60,6 @@ namespace Ryujinx.Ava.UI.Windows
Pages.Children.Clear();
NavPanel.SelectionChanged += NavPanelOnSelectionChanged;
NavPanel.SelectedItem = NavPanel.MenuItems.ElementAt(0);
}
private void NavPanelOnSelectionChanged(object sender, NavigationViewSelectionChangedEventArgs e)

View File

@@ -12,7 +12,7 @@
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
xmlns:helper="clr-namespace:Ryujinx.Common.Helper;assembly=Ryujinx.Common"
Width="1100"
Height="768"
Height="918"
MinWidth="800"
MinHeight="480"
WindowStartupLocation="CenterOwner"

View File

@@ -1,6 +1,4 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.ViewModels;
@@ -8,7 +6,6 @@ using Ryujinx.HLE.FileSystem;
using Ryujinx.Input;
using System;
using System.Linq;
using Key = Avalonia.Input.Key;
namespace Ryujinx.Ava.UI.Windows
{
@@ -27,10 +24,6 @@ namespace Ryujinx.Ava.UI.Windows
InitializeComponent();
Load();
#if DEBUG
this.AttachDevTools(new KeyGesture(Key.F12, KeyModifiers.Alt));
#endif
}
public SettingsWindow()

View File

@@ -1,15 +1,26 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Media;
using Avalonia.Platform;
using FluentAvalonia.UI.Windowing;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.ViewModels;
using System.Threading.Tasks;
namespace Ryujinx.Ava.UI.Windows
{
public abstract class StyleableAppWindow : AppWindow
{
public static async Task ShowAsync(StyleableAppWindow appWindow, Window owner = null)
{
#if DEBUG
appWindow.AttachDevTools(new KeyGesture(Key.F12, KeyModifiers.Control));
#endif
await appWindow.ShowDialog(owner ?? RyujinxApp.MainWindow);
}
protected StyleableAppWindow()
{
WindowStartupLocation = WindowStartupLocation.CenterOwner;
@@ -36,6 +47,14 @@ namespace Ryujinx.Ava.UI.Windows
public abstract class StyleableWindow : Window
{
public static async Task ShowAsync(StyleableWindow window, Window owner = null)
{
#if DEBUG
window.AttachDevTools(new KeyGesture(Key.F12, KeyModifiers.Control));
#endif
await window.ShowDialog(owner ?? RyujinxApp.MainWindow);
}
protected StyleableWindow()
{
WindowStartupLocation = WindowStartupLocation.CenterOwner;

View File

@@ -43,17 +43,9 @@ namespace Ryujinx.Ava
private const int ConnectionCount = 4;
private static string _buildVer;
private static readonly string _platformExt =
RunningPlatform.IsMacOS
? "macos_universal.app.tar.gz"
: RunningPlatform.IsWindows
? "win_x64.zip"
: RunningPlatform.IsX64Linux
? "linux_x64.tar.gz"
: RunningPlatform.IsArmLinux
? "linux_arm64.tar.gz"
: throw new PlatformNotSupportedException();
private static readonly string _platformExt = BuildPlatformExtension();
private static string _buildUrl;
private static long _buildSize;
@@ -780,5 +772,34 @@ namespace Ryujinx.Ava
public static void CleanupUpdate() =>
Directory.GetFiles(_homeDir, "*.ryuold", SearchOption.AllDirectories)
.ForEach(File.Delete);
private static string BuildPlatformExtension()
{
if (RunningPlatform.IsMacOS)
return "macos_universal.app.tar.gz";
#pragma warning disable CS8509 // It is exhaustive for any values this can contain.
string osPrefix = RunningPlatform.CurrentOS switch
{
OperatingSystemType.Linux => "linux",
OperatingSystemType.Windows => "win"
};
string archSuffix = RunningPlatform.Architecture switch
{
Architecture.Arm64 => "arm64",
Architecture.X64 => "x64",
_ => throw new PlatformNotSupportedException($"Unknown architecture {Enum.GetName(RunningPlatform.Architecture)}."),
};
string fileExtension = RunningPlatform.CurrentOS switch
#pragma warning restore CS8509
{
OperatingSystemType.Linux => "tar.gz",
OperatingSystemType.Windows => "zip"
};
return $"{osPrefix}_{archSuffix}.{fileExtension}";
}
}
}

View File

@@ -59,12 +59,13 @@ namespace Ryujinx.Ava.Utilities.PlayReport
"Race" => "Racing",
_ => FormattedValue.ForceReset
};
private static FormattedValue PokemonSVUnionCircle(SingleValue value)
=> value.Matched.BoxedValue is 0 ? "Playing Alone" : "Playing in a group";
private static FormattedValue PokemonSVArea(SingleValue value)
=> value.Matched.StringValue switch
private static FormattedValue PokemonSV(MultiValue values)
{
string playStatus = values.Matched[0].BoxedValue is 0 ? "Playing Alone" : "Playing in a group";
FormattedValue locations = values.Matched[1].ToString() switch
{
// Base Game Locations
"a_w01" => "South Area One",
@@ -92,10 +93,13 @@ namespace Ryujinx.Ava.Utilities.PlayReport
"a_w24" => "South Paldean Sea",
"a_w25" => "West Paldean Sea",
"a_w26" => "East Paldean Sea",
"a_w27" => "Nouth Paldean Sea",
"a_w27" => "North Paldean Sea",
//TODO DLC Locations
_ => FormattedValue.ForceReset
};
return$"{playStatus} in {locations}";
}
private static FormattedValue SuperSmashBrosUltimate_Mode(SparseMultiValue values)
{
@@ -115,6 +119,11 @@ namespace Ryujinx.Ava.Utilities.PlayReport
return $"Achievement Unlocked - ID: {anniversary}";
}
if (values.Matched.ContainsKey("is_created"))
{
return "Edited a Custom Stage!";
}
if (values.Matched.ContainsKey("adv_slot"))
{
return

View File

@@ -59,9 +59,8 @@ namespace Ryujinx.Ava.Utilities.PlayReport
.AddSpec(
["0100a3d008c5c000", "01008f6008c5e000"],
spec => spec
.WithDescription("based on what area of Paldea you're exploring.")
.AddValueFormatter("area_no", PokemonSVArea)
.AddValueFormatter("team_circle", PokemonSVUnionCircle)
.WithDescription("based on if you're playing alone or in a group and what area of Paldea you're exploring.")
.AddMultiValueFormatter(["team_circle", "area_no"], PokemonSV)
)
.AddSpec(
"01006a800016e000",
@@ -71,7 +70,7 @@ namespace Ryujinx.Ava.Utilities.PlayReport
[
// Metadata to figure out what PlayReport we have.
"match_mode", "match_submode", "anniversary", "fighter", "reason", "challenge_count",
"adv_slot",
"adv_slot", "is_created",
// List of Fighters
"player_1_fighter", "player_2_fighter", "player_3_fighter", "player_4_fighter",
"player_5_fighter", "player_6_fighter", "player_7_fighter", "player_8_fighter",