diff --git a/src/locales/ar/translation.json b/src/locales/ar/translation.json index 95cf0958c..a5d623a84 100644 --- a/src/locales/ar/translation.json +++ b/src/locales/ar/translation.json @@ -511,7 +511,13 @@ "hide": "Hide", "create_steam_shortcut_modal_vr_flag": "VR Game (In Steam: update the VR flag or delete/recreate the shortcut)", "create_steam_shortcut_modal_create_button": "Create", - "create_steam_shortcut_modal_cancel_button": "Cancel" + "create_steam_shortcut_modal_cancel_button": "Cancel", + "crackwatch_status": "كراك ووتش", + "cracked": "مكسور", + "uncracked": "غير مكسور", + "crack_group": "المجموعة", + "crack_date": "تاريخ الكراك", + "protection": "الحماية" }, "activation": { "title": "تفعيل Hydra", diff --git a/src/locales/be/translation.json b/src/locales/be/translation.json index 8be9c0cb6..e1ce326e1 100644 --- a/src/locales/be/translation.json +++ b/src/locales/be/translation.json @@ -508,7 +508,13 @@ "hide": "Hide", "create_steam_shortcut_modal_vr_flag": "VR Game (In Steam: update the VR flag or delete/recreate the shortcut)", "create_steam_shortcut_modal_create_button": "Create", - "create_steam_shortcut_modal_cancel_button": "Cancel" + "create_steam_shortcut_modal_cancel_button": "Cancel", + "crackwatch_status": "CrackWatch", + "cracked": "Узламана", + "uncracked": "Не ўзламана", + "crack_group": "Група", + "crack_date": "Дата ўзлому", + "protection": "Абарона" }, "activation": { "title": "Актываваць Hydra", diff --git a/src/locales/bg/translation.json b/src/locales/bg/translation.json index 8931b9f92..76471e30f 100644 --- a/src/locales/bg/translation.json +++ b/src/locales/bg/translation.json @@ -511,7 +511,13 @@ "hide": "Hide", "create_steam_shortcut_modal_vr_flag": "VR Game (In Steam: update the VR flag or delete/recreate the shortcut)", "create_steam_shortcut_modal_create_button": "Create", - "create_steam_shortcut_modal_cancel_button": "Cancel" + "create_steam_shortcut_modal_cancel_button": "Cancel", + "crackwatch_status": "CrackWatch", + "cracked": "Кракнато", + "uncracked": "Некракнато", + "crack_group": "Група", + "crack_date": "Дата на крака", + "protection": "Защита" }, "activation": { "title": "Активирай Hydra", diff --git a/src/locales/ca/translation.json b/src/locales/ca/translation.json index 206ca5432..f7d6e5503 100644 --- a/src/locales/ca/translation.json +++ b/src/locales/ca/translation.json @@ -511,7 +511,13 @@ "hide": "Hide", "create_steam_shortcut_modal_vr_flag": "VR Game (In Steam: update the VR flag or delete/recreate the shortcut)", "create_steam_shortcut_modal_create_button": "Create", - "create_steam_shortcut_modal_cancel_button": "Cancel" + "create_steam_shortcut_modal_cancel_button": "Cancel", + "crackwatch_status": "CrackWatch", + "cracked": "Crackejat", + "uncracked": "No crackejat", + "crack_group": "Grup", + "crack_date": "Data del crack", + "protection": "Protecció" }, "activation": { "title": "Activa l'Hydra", diff --git a/src/locales/cs/translation.json b/src/locales/cs/translation.json index 7322a95f2..b96d8180a 100644 --- a/src/locales/cs/translation.json +++ b/src/locales/cs/translation.json @@ -511,7 +511,13 @@ "hide": "Hide", "create_steam_shortcut_modal_vr_flag": "VR Game (In Steam: update the VR flag or delete/recreate the shortcut)", "create_steam_shortcut_modal_create_button": "Create", - "create_steam_shortcut_modal_cancel_button": "Cancel" + "create_steam_shortcut_modal_cancel_button": "Cancel", + "crackwatch_status": "CrackWatch", + "cracked": "Cracknuto", + "uncracked": "Nedostupný crack", + "crack_group": "Skupina", + "crack_date": "Datum cracku", + "protection": "Ochrana" }, "activation": { "title": "Aktivovat hydru", diff --git a/src/locales/da/translation.json b/src/locales/da/translation.json index 3bcb5ec15..dcb25d645 100644 --- a/src/locales/da/translation.json +++ b/src/locales/da/translation.json @@ -511,7 +511,13 @@ "hide": "Hide", "create_steam_shortcut_modal_vr_flag": "VR Game (In Steam: update the VR flag or delete/recreate the shortcut)", "create_steam_shortcut_modal_create_button": "Create", - "create_steam_shortcut_modal_cancel_button": "Cancel" + "create_steam_shortcut_modal_cancel_button": "Cancel", + "crackwatch_status": "CrackWatch", + "cracked": "Cracket", + "uncracked": "Ikke cracket", + "crack_group": "Gruppe", + "crack_date": "Crack-dato", + "protection": "Beskyttelse" }, "activation": { "title": "Aktivér Hydra", diff --git a/src/locales/de/translation.json b/src/locales/de/translation.json index e6d616883..117a72b44 100644 --- a/src/locales/de/translation.json +++ b/src/locales/de/translation.json @@ -511,7 +511,13 @@ "hide": "Hide", "create_steam_shortcut_modal_vr_flag": "VR Game (In Steam: update the VR flag or delete/recreate the shortcut)", "create_steam_shortcut_modal_create_button": "Create", - "create_steam_shortcut_modal_cancel_button": "Cancel" + "create_steam_shortcut_modal_cancel_button": "Cancel", + "crackwatch_status": "CrackWatch", + "cracked": "Gecrackt", + "uncracked": "Nicht gecrackt", + "crack_group": "Gruppe", + "crack_date": "Crack-Datum", + "protection": "Schutz" }, "activation": { "title": "Hydra aktivieren", diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index f886f42e8..c5fb67673 100755 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -834,7 +834,13 @@ "create_steam_shortcut_modal_cancel_button": "Cancel", "delete_shortcut_success": "Steam shortcut deleted successfully", "delete_shortcut_error": "Failed to delete Steam shortcut", - "delete_steam_shortcut": "Delete Steam shortcut" + "delete_steam_shortcut": "Delete Steam shortcut", + "crackwatch_status": "CrackWatch", + "cracked": "Cracked", + "uncracked": "Uncracked", + "crack_group": "Group", + "crack_date": "Crack date", + "protection": "Protection" }, "activation": { "title": "Activate Hydra", diff --git a/src/locales/es/translation.json b/src/locales/es/translation.json index 5c87d516c..0e6f47439 100644 --- a/src/locales/es/translation.json +++ b/src/locales/es/translation.json @@ -809,7 +809,13 @@ "torrent_files_disclaimer": "Opción avanzada: si no sabés lo que hace esto, dejá la selección predeterminada.", "winetricks_not_available_tooltip": "Winetricks no está disponible en tu PATH", "create_steam_shortcut_modal_create_button": "Crear", - "create_steam_shortcut_modal_cancel_button": "Cancelar" + "create_steam_shortcut_modal_cancel_button": "Cancelar", + "crackwatch_status": "CrackWatch", + "cracked": "Crackeado", + "uncracked": "Sin crackear", + "crack_group": "Grupo", + "crack_date": "Fecha del crack", + "protection": "Protección" }, "activation": { "title": "Activar Hydra", diff --git a/src/locales/et/translation.json b/src/locales/et/translation.json index 3eb10acb2..83ff2f48b 100644 --- a/src/locales/et/translation.json +++ b/src/locales/et/translation.json @@ -511,7 +511,13 @@ "hide": "Hide", "create_steam_shortcut_modal_vr_flag": "VR Game (In Steam: update the VR flag or delete/recreate the shortcut)", "create_steam_shortcut_modal_create_button": "Create", - "create_steam_shortcut_modal_cancel_button": "Cancel" + "create_steam_shortcut_modal_cancel_button": "Cancel", + "crackwatch_status": "CrackWatch", + "cracked": "Crack'itud", + "uncracked": "Crack'imata", + "crack_group": "Rühm", + "crack_date": "Crack'i kuupäev", + "protection": "Kaitse" }, "activation": { "title": "Aktiveeri Hydra", diff --git a/src/locales/fa/translation.json b/src/locales/fa/translation.json index 6848d576d..e15762c3f 100644 --- a/src/locales/fa/translation.json +++ b/src/locales/fa/translation.json @@ -508,7 +508,13 @@ "hide": "Hide", "create_steam_shortcut_modal_vr_flag": "VR Game (In Steam: update the VR flag or delete/recreate the shortcut)", "create_steam_shortcut_modal_create_button": "Create", - "create_steam_shortcut_modal_cancel_button": "Cancel" + "create_steam_shortcut_modal_cancel_button": "Cancel", + "crackwatch_status": "CrackWatch", + "cracked": "کرک‌شده", + "uncracked": "کرک‌نشده", + "crack_group": "گروه", + "crack_date": "تاریخ کرک", + "protection": "حفاظت" }, "activation": { "title": "فعال کردن هایدرا", diff --git a/src/locales/fi/translation.json b/src/locales/fi/translation.json index 5218e2750..a549843ac 100644 --- a/src/locales/fi/translation.json +++ b/src/locales/fi/translation.json @@ -511,7 +511,13 @@ "hide": "Hide", "create_steam_shortcut_modal_vr_flag": "VR Game (In Steam: update the VR flag or delete/recreate the shortcut)", "create_steam_shortcut_modal_create_button": "Create", - "create_steam_shortcut_modal_cancel_button": "Cancel" + "create_steam_shortcut_modal_cancel_button": "Cancel", + "crackwatch_status": "CrackWatch", + "cracked": "Crackattu", + "uncracked": "Ei crackattu", + "crack_group": "Ryhmä", + "crack_date": "Crackin päivämäärä", + "protection": "Suojaus" }, "activation": { "title": "Aktivoi Hydra", diff --git a/src/locales/fr/translation.json b/src/locales/fr/translation.json index 2bafbca3f..ae36edfc5 100644 --- a/src/locales/fr/translation.json +++ b/src/locales/fr/translation.json @@ -545,7 +545,13 @@ "download_error_torrent_no_files_selected": "Sélectionnez au moins un fichier torrent pour démarrer le téléchargement.", "download_error_torrent_invalid_file_selection": "Les fichiers torrent sélectionnés sont invalides. Veuillez recharger et réessayer.", "download_error_torrent_too_many_files": "Ce torrent contient trop de fichiers pour être affiché en toute sécurité.", - "download_error_torrent_files_unavailable": "Impossible de récupérer les fichiers torrent. Veuillez réessayer." + "download_error_torrent_files_unavailable": "Impossible de récupérer les fichiers torrent. Veuillez réessayer.", + "crackwatch_status": "CrackWatch", + "cracked": "Cracké", + "uncracked": "Non cracké", + "crack_group": "Groupe", + "crack_date": "Date du crack", + "protection": "Protection" }, "activation": { "title": "Activer Hydra", diff --git a/src/locales/hu/translation.json b/src/locales/hu/translation.json index 6ce83c166..deb978f14 100644 --- a/src/locales/hu/translation.json +++ b/src/locales/hu/translation.json @@ -534,7 +534,13 @@ "create_steam_shortcut_modal_cancel_button": "Mégse", "delete_shortcut_success": "Steam parancsikon sikeresen törölve", "delete_shortcut_error": "Nem sikerült törölni a Steam parancsikont", - "delete_steam_shortcut": "Steam parancsikon törlése" + "delete_steam_shortcut": "Steam parancsikon törlése", + "crackwatch_status": "CrackWatch", + "cracked": "Feltörve", + "uncracked": "Nincs feltörve", + "crack_group": "Csoport", + "crack_date": "Feltörés dátuma", + "protection": "Védelem" }, "activation": { "title": "Hydra Aktiválása", diff --git a/src/locales/id/translation.json b/src/locales/id/translation.json index a74928170..4bbe98c5a 100644 --- a/src/locales/id/translation.json +++ b/src/locales/id/translation.json @@ -534,7 +534,13 @@ "create_steam_shortcut_modal_cancel_button": "Batal", "delete_shortcut_success": "Pintasan Steam berhasil dihapus", "delete_shortcut_error": "Gagal menghapus pintasan Steam", - "delete_steam_shortcut": "Hapus pintasan Steam" + "delete_steam_shortcut": "Hapus pintasan Steam", + "crackwatch_status": "CrackWatch", + "cracked": "Sudah di-crack", + "uncracked": "Belum di-crack", + "crack_group": "Grup", + "crack_date": "Tanggal crack", + "protection": "Proteksi" }, "activation": { "title": "Aktifkan Hydra", diff --git a/src/locales/it/translation.json b/src/locales/it/translation.json index 6b7eddfb0..e7fbc3cd6 100644 --- a/src/locales/it/translation.json +++ b/src/locales/it/translation.json @@ -511,7 +511,13 @@ "download_error_alldebrid_unavailable": "AllDebrid is temporarily unavailable. Please try again later.", "create_steam_shortcut_modal_vr_flag": "VR Game (In Steam: update the VR flag or delete/recreate the shortcut)", "create_steam_shortcut_modal_create_button": "Create", - "create_steam_shortcut_modal_cancel_button": "Cancel" + "create_steam_shortcut_modal_cancel_button": "Cancel", + "crackwatch_status": "CrackWatch", + "cracked": "Craccato", + "uncracked": "Non craccato", + "crack_group": "Gruppo", + "crack_date": "Data del crack", + "protection": "Protezione" }, "activation": { "title": "Attiva Hydra", diff --git a/src/locales/ja/translation.json b/src/locales/ja/translation.json index 778f97b5e..5132fc6de 100644 --- a/src/locales/ja/translation.json +++ b/src/locales/ja/translation.json @@ -534,7 +534,13 @@ "create_steam_shortcut_modal_cancel_button": "キャンセル", "delete_shortcut_success": "Steamショートカットを削除しました", "delete_shortcut_error": "Steamショートカットの削除に失敗しました", - "delete_steam_shortcut": "Steamショートカットを削除" + "delete_steam_shortcut": "Steamショートカットを削除", + "crackwatch_status": "CrackWatch", + "cracked": "クラック済み", + "uncracked": "未クラック", + "crack_group": "グループ", + "crack_date": "クラック日", + "protection": "保護" }, "activation": { "title": "Hydraをアクティベート", diff --git a/src/locales/kk/translation.json b/src/locales/kk/translation.json index 785365371..6c10d7ea7 100644 --- a/src/locales/kk/translation.json +++ b/src/locales/kk/translation.json @@ -511,7 +511,13 @@ "hide": "Hide", "create_steam_shortcut_modal_vr_flag": "VR Game (In Steam: update the VR flag or delete/recreate the shortcut)", "create_steam_shortcut_modal_create_button": "Create", - "create_steam_shortcut_modal_cancel_button": "Cancel" + "create_steam_shortcut_modal_cancel_button": "Cancel", + "crackwatch_status": "CrackWatch", + "cracked": "Бұзылған", + "uncracked": "Бұзылмаған", + "crack_group": "Топ", + "crack_date": "Бұзу күні", + "protection": "Қорғаныс" }, "activation": { "title": "Hydra-ны белсендіру", diff --git a/src/locales/ko/translation.json b/src/locales/ko/translation.json index 01019fc11..7d588180c 100644 --- a/src/locales/ko/translation.json +++ b/src/locales/ko/translation.json @@ -508,7 +508,13 @@ "hide": "Hide", "create_steam_shortcut_modal_vr_flag": "VR Game (In Steam: update the VR flag or delete/recreate the shortcut)", "create_steam_shortcut_modal_create_button": "Create", - "create_steam_shortcut_modal_cancel_button": "Cancel" + "create_steam_shortcut_modal_cancel_button": "Cancel", + "crackwatch_status": "CrackWatch", + "cracked": "크랙됨", + "uncracked": "크랙되지 않음", + "crack_group": "그룹", + "crack_date": "크랙 날짜", + "protection": "보호" }, "activation": { "title": "Hydra 실행", diff --git a/src/locales/lv/translation.json b/src/locales/lv/translation.json index 361cb6430..1fcf25e8c 100644 --- a/src/locales/lv/translation.json +++ b/src/locales/lv/translation.json @@ -511,7 +511,13 @@ "hide": "Hide", "create_steam_shortcut_modal_vr_flag": "VR Game (In Steam: update the VR flag or delete/recreate the shortcut)", "create_steam_shortcut_modal_create_button": "Create", - "create_steam_shortcut_modal_cancel_button": "Cancel" + "create_steam_shortcut_modal_cancel_button": "Cancel", + "crackwatch_status": "CrackWatch", + "cracked": "Uzlauzts", + "uncracked": "Nav uzlauzts", + "crack_group": "Grupa", + "crack_date": "Uzlaušanas datums", + "protection": "Aizsardzība" }, "activation": { "title": "Aktivizēt Hydra", diff --git a/src/locales/nb/translation.json b/src/locales/nb/translation.json index f291954a9..050efc597 100644 --- a/src/locales/nb/translation.json +++ b/src/locales/nb/translation.json @@ -511,7 +511,13 @@ "hide": "Hide", "create_steam_shortcut_modal_vr_flag": "VR Game (In Steam: update the VR flag or delete/recreate the shortcut)", "create_steam_shortcut_modal_create_button": "Create", - "create_steam_shortcut_modal_cancel_button": "Cancel" + "create_steam_shortcut_modal_cancel_button": "Cancel", + "crackwatch_status": "CrackWatch", + "cracked": "Cracket", + "uncracked": "Ikke cracket", + "crack_group": "Gruppe", + "crack_date": "Crack-dato", + "protection": "Beskyttelse" }, "activation": { "title": "Aktivér Hydra", diff --git a/src/locales/nl/translation.json b/src/locales/nl/translation.json index ea5b4f0ba..1cd001c6e 100644 --- a/src/locales/nl/translation.json +++ b/src/locales/nl/translation.json @@ -508,7 +508,13 @@ "hide": "Hide", "create_steam_shortcut_modal_vr_flag": "VR Game (In Steam: update the VR flag or delete/recreate the shortcut)", "create_steam_shortcut_modal_create_button": "Create", - "create_steam_shortcut_modal_cancel_button": "Cancel" + "create_steam_shortcut_modal_cancel_button": "Cancel", + "crackwatch_status": "CrackWatch", + "cracked": "Gekraakt", + "uncracked": "Niet gekraakt", + "crack_group": "Groep", + "crack_date": "Kraakdatum", + "protection": "Beveiliging" }, "activation": { "title": "Activeer Hydra", diff --git a/src/locales/pl/translation.json b/src/locales/pl/translation.json index 468111644..ffc8d46cb 100644 --- a/src/locales/pl/translation.json +++ b/src/locales/pl/translation.json @@ -580,7 +580,13 @@ "transfer_unknown_size": "nieznany", "not_enough_space_detail": "Za mało miejsca — potrzeba {{needed}}, dostępne jest tylko {{available}}.", "detecting_drives": "Wykrywanie napędów...", - "browse": "Przeglądaj" + "browse": "Przeglądaj", + "crackwatch_status": "CrackWatch", + "cracked": "Crackowane", + "uncracked": "Niecrackowane", + "crack_group": "Grupa", + "crack_date": "Data cracka", + "protection": "Ochrona" }, "activation": { "title": "Aktywuj Hydra", diff --git a/src/locales/pt-BR/translation.json b/src/locales/pt-BR/translation.json index c743fb297..45fa28ce4 100755 --- a/src/locales/pt-BR/translation.json +++ b/src/locales/pt-BR/translation.json @@ -534,7 +534,13 @@ "launch_options_description_linux": "Adicione argumentos de inicialização do jogo ou use %command% para envolver o comando de inicialização.", "create_steam_shortcut_modal_vr_flag": "Jogo VR (Na Steam: atualize a flag VR ou exclua/recrie o atalho)", "create_steam_shortcut_modal_create_button": "Criar", - "create_steam_shortcut_modal_cancel_button": "Cancelar" + "create_steam_shortcut_modal_cancel_button": "Cancelar", + "crackwatch_status": "CrackWatch", + "cracked": "Crackeado", + "uncracked": "Sem crack", + "crack_group": "Grupo", + "crack_date": "Data do crack", + "protection": "Proteção" }, "activation": { "title": "Ativação", diff --git a/src/locales/pt-PT/translation.json b/src/locales/pt-PT/translation.json index f27e08f7f..601b89b80 100644 --- a/src/locales/pt-PT/translation.json +++ b/src/locales/pt-PT/translation.json @@ -471,7 +471,13 @@ "launch_options_description_linux": "Add game launch arguments, or use %command% to wrap the launch command.", "create_steam_shortcut_modal_vr_flag": "VR Game (In Steam: update the VR flag or delete/recreate the shortcut)", "create_steam_shortcut_modal_create_button": "Create", - "create_steam_shortcut_modal_cancel_button": "Cancel" + "create_steam_shortcut_modal_cancel_button": "Cancel", + "crackwatch_status": "CrackWatch", + "cracked": "Crackado", + "uncracked": "Sem crack", + "crack_group": "Grupo", + "crack_date": "Data do crack", + "protection": "Proteção" }, "activation": { "title": "Ativação", diff --git a/src/locales/ro/translation.json b/src/locales/ro/translation.json index e3295b9ed..72660ee3e 100644 --- a/src/locales/ro/translation.json +++ b/src/locales/ro/translation.json @@ -508,7 +508,13 @@ "hide": "Hide", "create_steam_shortcut_modal_vr_flag": "VR Game (In Steam: update the VR flag or delete/recreate the shortcut)", "create_steam_shortcut_modal_create_button": "Create", - "create_steam_shortcut_modal_cancel_button": "Cancel" + "create_steam_shortcut_modal_cancel_button": "Cancel", + "crackwatch_status": "CrackWatch", + "cracked": "Spart", + "uncracked": "Nespart", + "crack_group": "Grup", + "crack_date": "Data spargerii", + "protection": "Protecție" }, "activation": { "title": "Activează Hydra", diff --git a/src/locales/ru/translation.json b/src/locales/ru/translation.json index 87ecd0703..6dff17add 100644 --- a/src/locales/ru/translation.json +++ b/src/locales/ru/translation.json @@ -576,7 +576,13 @@ "create_steam_shortcut_modal_cancel_button": "Отмена", "delete_shortcut_success": "Ярлык Steam успешно удалён", "delete_shortcut_error": "Не удалось удалить ярлык Steam", - "delete_steam_shortcut": "Удалить ярлык Steam" + "delete_steam_shortcut": "Удалить ярлык Steam", + "crackwatch_status": "CrackWatch", + "cracked": "Взломана", + "uncracked": "Не взломана", + "crack_group": "Группа", + "crack_date": "Дата взлома", + "protection": "Защита" }, "activation": { "title": "Активировать Hydra", diff --git a/src/locales/sl/translation.json b/src/locales/sl/translation.json index 188f1a4d9..932fa803e 100644 --- a/src/locales/sl/translation.json +++ b/src/locales/sl/translation.json @@ -511,7 +511,13 @@ "source_offline": "Source is offline", "create_steam_shortcut_modal_vr_flag": "VR Game (In Steam: update the VR flag or delete/recreate the shortcut)", "create_steam_shortcut_modal_create_button": "Create", - "create_steam_shortcut_modal_cancel_button": "Cancel" + "create_steam_shortcut_modal_cancel_button": "Cancel", + "crackwatch_status": "CrackWatch", + "cracked": "Vlomljeno", + "uncracked": "Ni vlomljeno", + "crack_group": "Skupina", + "crack_date": "Datum vloma", + "protection": "Zaščita" }, "activation": { "title": "Aktiviraj Hydra", diff --git a/src/locales/sv/translation.json b/src/locales/sv/translation.json index 88e265cdd..e4e13c531 100644 --- a/src/locales/sv/translation.json +++ b/src/locales/sv/translation.json @@ -511,7 +511,13 @@ "hide": "Hide", "create_steam_shortcut_modal_vr_flag": "VR Game (In Steam: update the VR flag or delete/recreate the shortcut)", "create_steam_shortcut_modal_create_button": "Create", - "create_steam_shortcut_modal_cancel_button": "Cancel" + "create_steam_shortcut_modal_cancel_button": "Cancel", + "crackwatch_status": "CrackWatch", + "cracked": "Crackad", + "uncracked": "Inte crackad", + "crack_group": "Grupp", + "crack_date": "Crack-datum", + "protection": "Skydd" }, "activation": { "title": "Aktivera Hydra", diff --git a/src/locales/tr/translation.json b/src/locales/tr/translation.json index bf8f87ea1..e4aec0d06 100644 --- a/src/locales/tr/translation.json +++ b/src/locales/tr/translation.json @@ -511,7 +511,13 @@ "source_offline": "Source is offline", "create_steam_shortcut_modal_vr_flag": "VR Game (In Steam: update the VR flag or delete/recreate the shortcut)", "create_steam_shortcut_modal_create_button": "Create", - "create_steam_shortcut_modal_cancel_button": "Cancel" + "create_steam_shortcut_modal_cancel_button": "Cancel", + "crackwatch_status": "CrackWatch", + "cracked": "Kırıldı", + "uncracked": "Kırılmadı", + "crack_group": "Grup", + "crack_date": "Kırılma tarihi", + "protection": "Koruma" }, "activation": { "title": "Hydra'yı Etkinleştir", diff --git a/src/locales/uk/translation.json b/src/locales/uk/translation.json index 31e3654c0..b03618a11 100644 --- a/src/locales/uk/translation.json +++ b/src/locales/uk/translation.json @@ -511,7 +511,13 @@ "hide": "Hide", "create_steam_shortcut_modal_vr_flag": "VR Game (In Steam: update the VR flag or delete/recreate the shortcut)", "create_steam_shortcut_modal_create_button": "Create", - "create_steam_shortcut_modal_cancel_button": "Cancel" + "create_steam_shortcut_modal_cancel_button": "Cancel", + "crackwatch_status": "CrackWatch", + "cracked": "Зламано", + "uncracked": "Не зламано", + "crack_group": "Група", + "crack_date": "Дата злому", + "protection": "Захист" }, "activation": { "title": "Активувати Hydra", diff --git a/src/locales/uz/translation.json b/src/locales/uz/translation.json index 9ba08a4f5..48381c490 100644 --- a/src/locales/uz/translation.json +++ b/src/locales/uz/translation.json @@ -511,7 +511,13 @@ "hide": "Hide", "create_steam_shortcut_modal_vr_flag": "VR Game (In Steam: update the VR flag or delete/recreate the shortcut)", "create_steam_shortcut_modal_create_button": "Create", - "create_steam_shortcut_modal_cancel_button": "Cancel" + "create_steam_shortcut_modal_cancel_button": "Cancel", + "crackwatch_status": "CrackWatch", + "cracked": "Crack qilingan", + "uncracked": "Crack qilinmagan", + "crack_group": "Guruh", + "crack_date": "Crack sanasi", + "protection": "Himoya" }, "activation": { "title": "Hydra'ni faollashtirish", diff --git a/src/locales/zh/translation.json b/src/locales/zh/translation.json index 839ef3706..ffe1a6aba 100644 --- a/src/locales/zh/translation.json +++ b/src/locales/zh/translation.json @@ -511,7 +511,13 @@ "source_offline": "Source is offline", "create_steam_shortcut_modal_vr_flag": "VR Game (In Steam: update the VR flag or delete/recreate the shortcut)", "create_steam_shortcut_modal_create_button": "Create", - "create_steam_shortcut_modal_cancel_button": "Cancel" + "create_steam_shortcut_modal_cancel_button": "Cancel", + "crackwatch_status": "CrackWatch", + "cracked": "已破解", + "uncracked": "未破解", + "crack_group": "破解组", + "crack_date": "破解日期", + "protection": "保护" }, "activation": { "title": "激活 Hydra", diff --git a/src/main/events/catalogue/get-crackwatch-status.ts b/src/main/events/catalogue/get-crackwatch-status.ts new file mode 100644 index 000000000..e4f5a2fef --- /dev/null +++ b/src/main/events/catalogue/get-crackwatch-status.ts @@ -0,0 +1,42 @@ +import type { CrackWatchStatus, GameShop } from "@types"; +import { registerEvent } from "../register-event"; +import { CrackWatchService } from "@main/services"; +import { crackwatchCacheSublevel, levelKeys } from "@main/level"; + +const LOCAL_CACHE_EXPIRATION = 1000 * 60 * 60 * 24; // 24 hours + +const getCrackWatchStatus = async ( + _event: Electron.IpcMainInvokeEvent, + objectId: string, + shop: GameShop, + title: string +): Promise => { + if (shop !== "steam" || !title) { + return null; + } + + const cached = await crackwatchCacheSublevel.get( + levelKeys.game(shop, objectId) + ); + + if (cached && cached.updatedAt + LOCAL_CACHE_EXPIRATION > Date.now()) { + const { updatedAt: _updatedAt, ...status } = cached; + return status; + } + + const status = await CrackWatchService.getStatusByTitleAndAppId( + title, + objectId + ); + + if (status) { + await crackwatchCacheSublevel.put(levelKeys.game(shop, objectId), { + ...status, + updatedAt: Date.now(), + }); + } + + return status; +}; + +registerEvent("getCrackWatchStatus", getCrackWatchStatus); diff --git a/src/main/events/catalogue/index.ts b/src/main/events/catalogue/index.ts index a6e87e28f..a5b817338 100644 --- a/src/main/events/catalogue/index.ts +++ b/src/main/events/catalogue/index.ts @@ -2,4 +2,5 @@ import "./get-game-assets"; import "./get-game-repacks"; import "./get-game-shop-details"; import "./get-game-stats"; +import "./get-crackwatch-status"; import "./get-random-game"; diff --git a/src/main/level/sublevels/crackwatch-cache.ts b/src/main/level/sublevels/crackwatch-cache.ts new file mode 100644 index 000000000..e150ed5d2 --- /dev/null +++ b/src/main/level/sublevels/crackwatch-cache.ts @@ -0,0 +1,11 @@ +import type { CrackWatchStatus } from "@types"; + +import { db } from "../level"; +import { levelKeys } from "./keys"; + +export const crackwatchCacheSublevel = db.sublevel< + string, + CrackWatchStatus & { updatedAt: number } +>(levelKeys.crackwatchCache, { + valueEncoding: "json", +}); diff --git a/src/main/level/sublevels/index.ts b/src/main/level/sublevels/index.ts index 7aec8b392..5b051374e 100644 --- a/src/main/level/sublevels/index.ts +++ b/src/main/level/sublevels/index.ts @@ -3,6 +3,7 @@ export * from "./games"; export * from "./game-shop-assets"; export * from "./game-shop-cache"; export * from "./game-stats-cache"; +export * from "./crackwatch-cache"; export * from "./game-achievements"; export * from "./keys"; export * from "./themes"; diff --git a/src/main/level/sublevels/keys.ts b/src/main/level/sublevels/keys.ts index f8a22b9f5..0d3c4d77d 100644 --- a/src/main/level/sublevels/keys.ts +++ b/src/main/level/sublevels/keys.ts @@ -8,6 +8,7 @@ export const levelKeys = { themes: "themes", gameShopAssets: "gameShopAssets", gameStatsCache: "gameStatsAssets", + crackwatchCache: "crackwatchCache", gameShopCache: "gameShopCache", gameShopCacheItem: (shop: GameShop, objectId: string, language: string) => `${shop}:${objectId}:${language}`, diff --git a/src/main/services/crackwatch.ts b/src/main/services/crackwatch.ts new file mode 100644 index 000000000..0724611be --- /dev/null +++ b/src/main/services/crackwatch.ts @@ -0,0 +1,152 @@ +import axios, { type AxiosInstance } from "axios"; + +import type { CrackWatchStatus } from "@types"; +import { logger } from "./logger"; + +const NUXT_DATA_REGEX = + /]*id="__NUXT_DATA__"[^>]*>([\s\S]*?)<\/script>/; + +const titleToSlug = (title: string): string => + title + .toLowerCase() + .replace(/['’']/g, "") + .replace(/[^a-z0-9]+/g, "-") + .replace(/^-+|-+$/g, ""); + +const derefNuxtState = (arr: unknown[]): ((value: unknown) => unknown) => { + const cache = new Map(); + const resolve = (value: unknown, depth: number): unknown => { + if (depth > 32) return null; + if (typeof value === "number") { + if (!Number.isInteger(value) || value < 0 || value >= arr.length) { + return value; + } + if (cache.has(value)) return cache.get(value); + cache.set(value, null); // break cycles + const resolved = resolve(arr[value], depth + 1); + cache.set(value, resolved); + return resolved; + } + if (value === null || typeof value !== "object") return value; + if (Array.isArray(value)) { + return value.map((v) => resolve(v, depth + 1)); + } + const out: Record = {}; + for (const [k, v] of Object.entries(value)) { + out[k] = resolve(v, depth + 1); + } + return out; + }; + return (value) => resolve(value, 0); +}; + +interface GameStatusEntry { + slug?: string; + title?: string; + protections?: string | null; + hacked_groups_en?: string | null; + crack_date?: string | null; + steam_prod_id?: number | string | null; + description_en?: string | null; + specs_info?: unknown; +} + +const isMainGameEntry = (entry: GameStatusEntry): boolean => + entry !== null && + typeof entry === "object" && + // Related-game cards lack description_en/specs_info — only the focused game has them. + (entry.description_en !== undefined || entry.specs_info !== undefined); + +class CrackWatchServiceClass { + private readonly client: AxiosInstance; + + constructor() { + this.client = axios.create({ + baseURL: "https://gamestatus.info", + timeout: 8000, + headers: { + Accept: "text/html,application/xhtml+xml", + "User-Agent": + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Hydra/CrackWatch", + }, + }); + } + + async getStatusByTitleAndAppId( + title: string, + appId: string + ): Promise { + const slug = titleToSlug(title); + if (!slug) return null; + + try { + const { data: html } = await this.client.get(`/${slug}/en`, { + responseType: "text", + validateStatus: (s) => s >= 200 && s < 400, + }); + + const match = NUXT_DATA_REGEX.exec(html); + if (!match) { + logger.warn(`CrackWatch: __NUXT_DATA__ not found for slug=${slug}`); + return null; + } + + const arr = JSON.parse(match[1]) as unknown[]; + if (!Array.isArray(arr)) return null; + + const deref = derefNuxtState(arr); + + let game: GameStatusEntry | null = null; + for (const entry of arr) { + if ( + entry && + typeof entry === "object" && + !Array.isArray(entry) && + "slug" in entry && + "crack_date" in entry && + "steam_prod_id" in entry && + isMainGameEntry(entry as GameStatusEntry) + ) { + const resolved = deref(entry) as GameStatusEntry; + if (String(resolved.steam_prod_id) === String(appId)) { + game = resolved; + break; + } + } + } + + if (!game) { + logger.info( + `CrackWatch: no entry matched appId=${appId} on slug=${slug}` + ); + return null; + } + + const crackDate = + typeof game.crack_date === "string" && game.crack_date.length > 0 + ? game.crack_date + : null; + + return { + isCracked: crackDate !== null, + crackDate, + crackGroup: + typeof game.hacked_groups_en === "string" && game.hacked_groups_en + ? game.hacked_groups_en + : null, + protection: + typeof game.protections === "string" && game.protections + ? game.protections + : null, + }; + } catch (err) { + logger.warn( + `CrackWatch lookup failed for slug=${slug} appId=${appId}`, + err + ); + return null; + } + } +} + +export const CrackWatchService = new CrackWatchServiceClass(); diff --git a/src/main/services/index.ts b/src/main/services/index.ts index 98dd39bbb..50268bd91 100644 --- a/src/main/services/index.ts +++ b/src/main/services/index.ts @@ -27,3 +27,4 @@ export * from "./local-api"; export * from "./notifications/local-notifications"; export * from "./rom-save-manager"; export * from "./rom-scraper"; +export * from "./crackwatch"; diff --git a/src/preload/index.ts b/src/preload/index.ts index 0378ee3fd..6b3540624 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -83,6 +83,8 @@ contextBridge.exposeInMainWorld("electron", { getRandomGame: () => ipcRenderer.invoke("getRandomGame"), getGameStats: (objectId: string, shop: GameShop) => ipcRenderer.invoke("getGameStats", objectId, shop), + getCrackWatchStatus: (objectId: string, shop: GameShop, title: string) => + ipcRenderer.invoke("getCrackWatchStatus", objectId, shop, title), getGameAssets: (objectId: string, shop: GameShop) => ipcRenderer.invoke("getGameAssets", objectId, shop), getGameRepacks: (shop: GameShop, objectId: string) => diff --git a/src/renderer/src/declaration.d.ts b/src/renderer/src/declaration.d.ts index ec65266a5..9446510af 100644 --- a/src/renderer/src/declaration.d.ts +++ b/src/renderer/src/declaration.d.ts @@ -40,6 +40,7 @@ import type { RomEntry, RssFeed, NewsArticle, + CrackWatchStatus, } from "@types"; import type { AxiosProgressEvent } from "axios"; @@ -86,6 +87,11 @@ declare global { ) => Promise; getRandomGame: () => Promise; getGameStats: (objectId: string, shop: GameShop) => Promise; + getCrackWatchStatus: ( + objectId: string, + shop: GameShop, + title: string + ) => Promise; getGameAssets: ( objectId: string, shop: GameShop diff --git a/src/renderer/src/pages/game-details/sidebar/crackwatch-section.tsx b/src/renderer/src/pages/game-details/sidebar/crackwatch-section.tsx new file mode 100644 index 000000000..e60b8ff5f --- /dev/null +++ b/src/renderer/src/pages/game-details/sidebar/crackwatch-section.tsx @@ -0,0 +1,80 @@ +import Skeleton, { SkeletonTheme } from "react-loading-skeleton"; +import { useTranslation } from "react-i18next"; +import type { CrackWatchStatus } from "@types"; +import { SidebarSection } from "../sidebar-section/sidebar-section"; +import { useDate } from "@renderer/hooks"; +import "./sidebar.scss"; + +export interface CrackWatchSectionProps { + data: CrackWatchStatus | null; + isLoading: boolean; +} + +export function CrackWatchSection({ data, isLoading }: CrackWatchSectionProps) { + const { t } = useTranslation("game_details"); + const { formatDate } = useDate(); + + if (!isLoading && !data) return null; + + return ( + + +
+ {data ? ( + <> + + {data.isCracked ? t("cracked") : t("uncracked")} + + +
+ {data.protection && ( +
+ {t("protection")} + {data.protection} +
+ )} + + {data.crackGroup && ( +
+ + {t("crack_group")} + + {data.crackGroup} +
+ )} + + {data.crackDate && ( +
+ {t("crack_date")} + + {formatDate(data.crackDate)} + +
+ )} +
+ + ) : ( + <> + +
+ {Array.from({ length: 3 }).map((_, index) => ( + + ))} +
+ + )} +
+
+
+ ); +} diff --git a/src/renderer/src/pages/game-details/sidebar/sidebar.scss b/src/renderer/src/pages/game-details/sidebar/sidebar.scss index 324904d17..20bd80a68 100755 --- a/src/renderer/src/pages/game-details/sidebar/sidebar.scss +++ b/src/renderer/src/pages/game-details/sidebar/sidebar.scss @@ -104,6 +104,69 @@ } } +.crackwatch { + &__container { + padding: calc(globals.$spacing-unit * 2); + display: flex; + flex-direction: column; + gap: calc(globals.$spacing-unit * 1.5); + } + + &__badge { + align-self: flex-start; + display: inline-flex; + align-items: center; + gap: calc(globals.$spacing-unit / 2); + padding: calc(globals.$spacing-unit / 2) calc(globals.$spacing-unit * 1.5); + border-radius: 999px; + font-weight: 700; + font-size: globals.$small-font-size; + text-transform: uppercase; + letter-spacing: 0.5px; + + &--cracked { + background: rgba(46, 160, 67, 0.15); + color: #3fb950; + border: 1px solid rgba(46, 160, 67, 0.4); + } + + &--uncracked { + background: rgba(248, 81, 73, 0.15); + color: #f85149; + border: 1px solid rgba(248, 81, 73, 0.4); + } + } + + &__details { + display: flex; + flex-direction: column; + gap: globals.$spacing-unit; + } + + &__row { + display: flex; + justify-content: space-between; + align-items: center; + gap: globals.$spacing-unit; + font-size: globals.$small-font-size; + } + + &__label { + color: globals.$muted-color; + } + + &__value { + color: #fff; + font-weight: 600; + text-align: right; + } + + &__skeleton { + border-radius: 8px; + height: 24px; + } +} + .stats { &__section { display: flex; diff --git a/src/renderer/src/pages/game-details/sidebar/sidebar.tsx b/src/renderer/src/pages/game-details/sidebar/sidebar.tsx index a1b1282bf..4830ca1b5 100755 --- a/src/renderer/src/pages/game-details/sidebar/sidebar.tsx +++ b/src/renderer/src/pages/game-details/sidebar/sidebar.tsx @@ -1,5 +1,6 @@ import { useContext, useEffect, useState } from "react"; import type { + CrackWatchStatus, HowLongToBeatCategory, SteamAppDetails, UserAchievement, @@ -16,6 +17,7 @@ import { StarIcon, } from "@primer/octicons-react"; import { HowLongToBeatSection } from "./how-long-to-beat-section"; +import { CrackWatchSection } from "./crackwatch-section"; import { SidebarSection } from "../sidebar-section/sidebar-section"; import { buildGameAchievementPath } from "@renderer/helpers"; import "./sidebar.scss"; @@ -61,6 +63,11 @@ export function Sidebar() { data: HowLongToBeatCategory[] | null; }>({ isLoading: true, data: null }); + const [crackwatch, setCrackwatch] = useState<{ + isLoading: boolean; + data: CrackWatchStatus | null; + }>({ isLoading: true, data: null }); + const { userDetails } = useUserDetails(); const [activeRequirement, setActiveRequirement] = useState("minimum"); @@ -93,6 +100,21 @@ export function Sidebar() { } }, [objectId, shop]); + useEffect(() => { + if (objectId) { + setCrackwatch({ isLoading: true, data: null }); + + window.electron + .getCrackWatchStatus(objectId, shop, gameTitle) + .then((data) => { + setCrackwatch({ isLoading: false, data }); + }) + .catch(() => { + setCrackwatch({ isLoading: false, data: null }); + }); + } + }, [objectId, shop, gameTitle]); + return (