mirror of
https://github.com/hydralauncher/hydra.git
synced 2026-06-02 06:14:48 +02:00
fix: translate compatibility UI and prepare 3.9.2
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "hydralauncher",
|
||||
"version": "3.9.1",
|
||||
"version": "3.9.2",
|
||||
"description": "Hydra",
|
||||
"main": "./out/main/index.js",
|
||||
"author": "Los Broxas",
|
||||
|
||||
@@ -203,6 +203,13 @@
|
||||
"protondb_tier": "Tier",
|
||||
"protondb_score": "Score",
|
||||
"protondb_reports": "Based on {{count}} reports",
|
||||
"protondb_tier_borked": "Borked",
|
||||
"protondb_tier_bronze": "Bronze",
|
||||
"protondb_tier_silver": "Silver",
|
||||
"protondb_tier_gold": "Gold",
|
||||
"protondb_tier_platinum": "Platinum",
|
||||
"protondb_tier_unknown": "Unknown",
|
||||
"protondb_badge_title": "ProtonDB {{tier}}",
|
||||
"deck_compatibility": "Steam Deck",
|
||||
"deck_verified": "Verified",
|
||||
"deck_playable": "Playable",
|
||||
|
||||
@@ -139,7 +139,18 @@
|
||||
"genres": "Géneros",
|
||||
"tags": "Etiquetas",
|
||||
"publishers": "Editores",
|
||||
"protondb": "ProtonDB",
|
||||
"download_sources": "Descargando fuentes",
|
||||
"compatibility_requirements": "Compatibilidad con Linux",
|
||||
"protondb_minimum": "Nivel mínimo de ProtonDB",
|
||||
"steam_deck_minimum": "Compatibilidad mínima con Steam Deck",
|
||||
"steam_deck_compatible": "Compatible con Steam Deck",
|
||||
"compatibility_any": "Cualquiera",
|
||||
"protondb_silver_plus": "Plata+",
|
||||
"protondb_gold_plus": "Oro+",
|
||||
"protondb_platinum_only": "Solo platino",
|
||||
"deck_playable_plus": "Jugable+",
|
||||
"deck_verified_only": "Solo verificado",
|
||||
"result_count": "{{resultCount}} resultados",
|
||||
"filter_count": "{{filterCount}} disponible",
|
||||
"clear_filters": "Limpiar {{filterCount}} seleccionados"
|
||||
@@ -170,6 +181,22 @@
|
||||
"minutes": "minutos",
|
||||
"amount_hours": "{{amount}} horas",
|
||||
"amount_minutes": "{{amount}} minutos",
|
||||
"protondb_tier": "Nivel",
|
||||
"protondb_score": "Puntuación",
|
||||
"protondb_reports": "Basado en {{count}} reportes",
|
||||
"protondb_tier_borked": "Roto",
|
||||
"protondb_tier_bronze": "Bronce",
|
||||
"protondb_tier_silver": "Plata",
|
||||
"protondb_tier_gold": "Oro",
|
||||
"protondb_tier_platinum": "Platino",
|
||||
"protondb_tier_unknown": "Desconocido",
|
||||
"protondb_badge_title": "ProtonDB {{tier}}",
|
||||
"deck_compatibility": "Steam Deck",
|
||||
"deck_verified": "Verificado",
|
||||
"deck_playable": "Jugable",
|
||||
"deck_unsupported": "No compatible",
|
||||
"deck_unknown": "Desconocido",
|
||||
"view_on_protondb": "Ver en ProtonDB",
|
||||
"accuracy": "{{accuracy}}% completista",
|
||||
"add_to_library": "Añadir a la librería",
|
||||
"already_in_library": "Ya está en la librería",
|
||||
@@ -507,6 +534,9 @@
|
||||
"quit_app_instead_hiding": "No ocultar Hydra cuando se cierra",
|
||||
"launch_with_system": "Iniciar Hydra con el sistema",
|
||||
"general": "General",
|
||||
"content_gameplay": "Contenido y jugabilidad",
|
||||
"integrations": "Integraciones",
|
||||
"compatibility": "Compatibilidad",
|
||||
"behavior": "Comportamiento",
|
||||
"app_basics": "Básicos de la aplicación",
|
||||
"startup_behavior": "Comportamiento al iniciar",
|
||||
@@ -531,6 +561,8 @@
|
||||
"validate_download_source": "Validar",
|
||||
"remove_download_source": "Remover",
|
||||
"add_download_source": "Añadir fuente",
|
||||
"failed_add_download_source": "No se pudo agregar la fuente de descarga. Intentá de nuevo.",
|
||||
"download_source_already_exists": "Esta URL de fuente de descarga ya existe.",
|
||||
"download_count_zero": "Sin opciones de descarga",
|
||||
"download_count_one": "{{countFormatted}} opción de descarga",
|
||||
"download_count_other": "{{countFormatted}} opciones de descarga",
|
||||
@@ -538,9 +570,16 @@
|
||||
"add_download_source_description": "Introducí la URL del archivo .json",
|
||||
"download_source_up_to_date": "Actualizado",
|
||||
"download_source_errored": "Error",
|
||||
"download_source_pending_matching": "Se actualizará pronto",
|
||||
"download_source_matched": "Actualizada",
|
||||
"download_source_matching": "Actualizando",
|
||||
"download_source_failed": "Error",
|
||||
"download_source_no_information": "Sin información disponible",
|
||||
"sync_download_sources": "Sincronizar fuentes",
|
||||
"removed_download_source": "Fuente de descarga eliminada",
|
||||
"removed_download_sources": "Fuente de descarga eliminadas",
|
||||
"removed_all_download_sources": "Se eliminaron todas las fuentes de descarga",
|
||||
"download_sources_synced_successfully": "Todas las fuentes de descarga están sincronizadas",
|
||||
"cancel_button_confirmation_delete_all_sources": "No",
|
||||
"confirm_button_confirmation_delete_all_sources": "Si, eliminar todo",
|
||||
"title_confirmation_delete_all_sources": "Eliminar todas las fuentes de descarga",
|
||||
@@ -573,6 +612,7 @@
|
||||
"seed_after_download_complete": "Sembrar después de completar una descarga",
|
||||
"show_hidden_achievement_description": "Mostrar logros ocultos antes de desbloquearlos",
|
||||
"account": "Cuenta",
|
||||
"hydra_cloud": "Hydra Cloud",
|
||||
"no_users_blocked": "No has bloqueado a ningún usuario",
|
||||
"subscription_active_until": "Tu Hydra Cloud está activo hasta {{date}}",
|
||||
"manage_subscription": "Administrar suscripción",
|
||||
@@ -635,11 +675,18 @@
|
||||
"theme_imported": "Tema importado correctamente",
|
||||
"enable_friend_request_notifications": "Cuando recibís una solicitud de amistad",
|
||||
"enable_auto_install": "Descargar actualizaciones automáticamente",
|
||||
"run_games_with_gamemode": "Ejecutar juegos con GameMode por defecto",
|
||||
"run_games_with_mangohud": "Ejecutar juegos con MangoHud por defecto",
|
||||
"common_redist": "Common redistributables",
|
||||
"common_redist_description": "Los common redistributables son requeridos para algunos juegos. Es recomendable instalarlos para evitar algunos problemas.",
|
||||
"install_common_redist": "Instalar",
|
||||
"installing_common_redist": "Instalando…",
|
||||
"show_download_speed_in_megabytes": "Mostrar velocidad de descarga en megabytes por segundo",
|
||||
"max_download_speed": "Velocidad máxima de descarga ({{unit}})",
|
||||
"max_download_speed_hint": "Indicá 0 o dejalo vacío para velocidad de descarga ilimitada en {{unit}}.",
|
||||
"max_download_speed_unit_megabytes": "MB/s",
|
||||
"max_download_speed_unit_megabits": "Mbps",
|
||||
"max_download_speed_unlimited": "Ilimitada",
|
||||
"extract_files_by_default": "Extraer archivos por defecto después de descargar",
|
||||
"enable_steam_achievements": "Habilitar búsqueda de logros de Steam",
|
||||
"enable_new_download_options_badges": "Mostrar badges de nuevas opciones de descarga",
|
||||
@@ -681,6 +728,7 @@
|
||||
"proton_source_steam": "Instalado por Steam",
|
||||
"proton_source_compatibility_tools": "Instalado en compatibilitytools.d de Steam",
|
||||
"change_achievement_sound": "Cambiar sonido de logro",
|
||||
"remove_achievement_sound": "Quitar sonido de logro",
|
||||
"preview_sound": "Vista previa del sonido"
|
||||
},
|
||||
"notifications": {
|
||||
|
||||
@@ -158,6 +158,22 @@
|
||||
"minutes": "minutos",
|
||||
"amount_hours": "{{amount}} horas",
|
||||
"amount_minutes": "{{amount}} minutos",
|
||||
"protondb_tier": "Nível",
|
||||
"protondb_score": "Pontuação",
|
||||
"protondb_reports": "Com base em {{count}} relatos",
|
||||
"protondb_tier_borked": "Quebrado",
|
||||
"protondb_tier_bronze": "Bronze",
|
||||
"protondb_tier_silver": "Prata",
|
||||
"protondb_tier_gold": "Ouro",
|
||||
"protondb_tier_platinum": "Platina",
|
||||
"protondb_tier_unknown": "Desconhecido",
|
||||
"protondb_badge_title": "ProtonDB {{tier}}",
|
||||
"deck_compatibility": "Steam Deck",
|
||||
"deck_verified": "Verificado",
|
||||
"deck_playable": "Jogável",
|
||||
"deck_unsupported": "Não compatível",
|
||||
"deck_unknown": "Desconhecido",
|
||||
"view_on_protondb": "Ver no ProtonDB",
|
||||
"accuracy": "{{accuracy}}% de precisão",
|
||||
"add_to_library": "Adicionar à biblioteca",
|
||||
"already_in_library": "Já está na biblioteca",
|
||||
@@ -324,7 +340,7 @@
|
||||
"save_changes": "Salvar mudanças",
|
||||
"required_field": "Este campo é obrigatório",
|
||||
"max_length_field": "Este campo deve ter menos de {{length}} caracteres",
|
||||
"freeze_backup": "Fixar para não ser apagado por backups automáticos",
|
||||
"freeze_backup": "Fixar",
|
||||
"unfreeze_backup": "Remover dos fixados",
|
||||
"backup_frozen": "Backup fixado",
|
||||
"backup_unfrozen": "Backup removido dos fixados",
|
||||
@@ -501,6 +517,9 @@
|
||||
"quit_app_instead_hiding": "Encerrar o Hydra em vez de apenas minimizá-lo ao fechar",
|
||||
"launch_with_system": "Iniciar o Hydra junto com o sistema",
|
||||
"general": "Geral",
|
||||
"content_gameplay": "Conteúdo e jogabilidade",
|
||||
"integrations": "Integrações",
|
||||
"compatibility": "Compatibilidade",
|
||||
"behavior": "Comportamento",
|
||||
"app_basics": "Noções básicas do aplicativo",
|
||||
"startup_behavior": "Comportamento na inicialização",
|
||||
@@ -641,11 +660,18 @@
|
||||
"theme_imported": "Tema importado com sucesso",
|
||||
"enable_friend_request_notifications": "Quando um pedido de amizade é recebido",
|
||||
"enable_auto_install": "Baixar atualizações automaticamente",
|
||||
"run_games_with_gamemode": "Executar jogos com GameMode por padrão",
|
||||
"run_games_with_mangohud": "Executar jogos com MangoHud por padrão",
|
||||
"common_redist": "Componentes recomendados",
|
||||
"common_redist_description": "Componentes recomendados são necessários para executar alguns jogos. A instalação deles é recomendada para evitar problemas.",
|
||||
"install_common_redist": "Instalar",
|
||||
"installing_common_redist": "Instalando…",
|
||||
"show_download_speed_in_megabytes": "Exibir taxas de download em megabytes por segundo",
|
||||
"max_download_speed": "Velocidade máxima de download ({{unit}})",
|
||||
"max_download_speed_hint": "Defina 0 ou deixe em branco para velocidade de download ilimitada em {{unit}}.",
|
||||
"max_download_speed_unit_megabytes": "MB/s",
|
||||
"max_download_speed_unit_megabits": "Mbps",
|
||||
"max_download_speed_unlimited": "Ilimitada",
|
||||
"extract_files_by_default": "Extrair arquivos automaticamente após o download",
|
||||
"enable_steam_achievements": "Habilitar busca por conquistas da Steam",
|
||||
"enable_new_download_options_badges": "Mostrar badges de novas opções de download",
|
||||
@@ -731,7 +757,18 @@
|
||||
"genres": "Gêneros",
|
||||
"tags": "Marcadores",
|
||||
"publishers": "Distribuidoras",
|
||||
"protondb": "ProtonDB",
|
||||
"download_sources": "Fontes de download",
|
||||
"compatibility_requirements": "Compatibilidade com Linux",
|
||||
"protondb_minimum": "Nível mínimo do ProtonDB",
|
||||
"steam_deck_minimum": "Suporte mínimo ao Steam Deck",
|
||||
"steam_deck_compatible": "Compatível com Steam Deck",
|
||||
"compatibility_any": "Qualquer",
|
||||
"protondb_silver_plus": "Prata+",
|
||||
"protondb_gold_plus": "Ouro+",
|
||||
"protondb_platinum_only": "Somente platina",
|
||||
"deck_playable_plus": "Jogável+",
|
||||
"deck_verified_only": "Somente verificado",
|
||||
"result_count": "{{resultCount}} resultados",
|
||||
"filter_count": "{{filterCount}} disponíveis",
|
||||
"clear_filters": "Limpar {{filterCount}} selecionados"
|
||||
|
||||
@@ -367,7 +367,7 @@
|
||||
"save_changes": "Guardar alterações",
|
||||
"required_field": "Este campo é obrigatório",
|
||||
"max_length_field": "Este campo deve ter menos de {{length}} caracteres",
|
||||
"freeze_backup": "Fixar para não ser substituído por backups automáticos",
|
||||
"freeze_backup": "Fixar",
|
||||
"unfreeze_backup": "Desafixar",
|
||||
"backup_frozen": "Backup fixado",
|
||||
"backup_unfrozen": "Backup desafixado",
|
||||
|
||||
@@ -139,7 +139,18 @@
|
||||
"genres": "Жанры",
|
||||
"tags": "Теги",
|
||||
"publishers": "Издательства",
|
||||
"protondb": "ProtonDB",
|
||||
"download_sources": "Источники загрузки",
|
||||
"compatibility_requirements": "Совместимость с Linux",
|
||||
"protondb_minimum": "Минимальный уровень ProtonDB",
|
||||
"steam_deck_minimum": "Минимальная поддержка Steam Deck",
|
||||
"steam_deck_compatible": "Совместимо со Steam Deck",
|
||||
"compatibility_any": "Любая",
|
||||
"protondb_silver_plus": "Серебро+",
|
||||
"protondb_gold_plus": "Золото+",
|
||||
"protondb_platinum_only": "Только платина",
|
||||
"deck_playable_plus": "Играбельно+",
|
||||
"deck_verified_only": "Только подтверждено",
|
||||
"result_count": "{{resultCount}} результатов",
|
||||
"filter_count": "{{filterCount}} доступных",
|
||||
"clear_filters": "Очистить {{filterCount}} выбранных"
|
||||
@@ -170,6 +181,22 @@
|
||||
"minutes": "минут",
|
||||
"amount_hours": "{{amount}} часов",
|
||||
"amount_minutes": "{{amount}} минут",
|
||||
"protondb_tier": "Уровень",
|
||||
"protondb_score": "Оценка",
|
||||
"protondb_reports": "На основе {{count}} отчетов",
|
||||
"protondb_tier_borked": "Сломано",
|
||||
"protondb_tier_bronze": "Бронза",
|
||||
"protondb_tier_silver": "Серебро",
|
||||
"protondb_tier_gold": "Золото",
|
||||
"protondb_tier_platinum": "Платина",
|
||||
"protondb_tier_unknown": "Неизвестно",
|
||||
"protondb_badge_title": "ProtonDB {{tier}}",
|
||||
"deck_compatibility": "Steam Deck",
|
||||
"deck_verified": "Подтверждено",
|
||||
"deck_playable": "Играбельно",
|
||||
"deck_unsupported": "Не поддерживается",
|
||||
"deck_unknown": "Неизвестно",
|
||||
"view_on_protondb": "Открыть в ProtonDB",
|
||||
"accuracy": "точность {{accuracy}}%",
|
||||
"add_to_library": "Добавить в библиотеку",
|
||||
"already_in_library": "Уже в библиотеке",
|
||||
@@ -507,6 +534,9 @@
|
||||
"quit_app_instead_hiding": "Закрывать приложение вместо сворачивания в трей",
|
||||
"launch_with_system": "Запускать Hydra вместе с системой",
|
||||
"general": "Основные",
|
||||
"content_gameplay": "Контент и игровой процесс",
|
||||
"integrations": "Интеграции",
|
||||
"compatibility": "Совместимость",
|
||||
"behavior": "Поведение",
|
||||
"app_basics": "Основные настройки приложения",
|
||||
"startup_behavior": "Поведение при запуске",
|
||||
@@ -647,11 +677,18 @@
|
||||
"theme_imported": "Тема успешно импортирована",
|
||||
"enable_friend_request_notifications": "При получении запроса на добавление в друзья",
|
||||
"enable_auto_install": "Загружать обновления автоматически",
|
||||
"run_games_with_gamemode": "Запускать игры с GameMode по умолчанию",
|
||||
"run_games_with_mangohud": "Запускать игры с MangoHud по умолчанию",
|
||||
"common_redist": "Библиотеки",
|
||||
"common_redist_description": "Для запуска некоторых игр требуются библиотеки. Во избежание проблем рекомендуется установить их.",
|
||||
"install_common_redist": "Установить",
|
||||
"installing_common_redist": "Установка…",
|
||||
"show_download_speed_in_megabytes": "Показать скорость загрузки в мегабайтах в секунду",
|
||||
"max_download_speed": "Максимальная скорость загрузки ({{unit}})",
|
||||
"max_download_speed_hint": "Укажите 0 или оставьте поле пустым для неограниченной скорости загрузки в {{unit}}.",
|
||||
"max_download_speed_unit_megabytes": "MB/s",
|
||||
"max_download_speed_unit_megabits": "Mbps",
|
||||
"max_download_speed_unlimited": "Без ограничений",
|
||||
"extract_files_by_default": "Извлекать файлы по умолчанию после загрузки",
|
||||
"enable_steam_achievements": "Включить поиск достижений Steam",
|
||||
"enable_new_download_options_badges": "Показывать значки новых вариантов загрузки",
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
import { CloudSync, HydraApi, logger, WindowManager } from "@main/services";
|
||||
import {
|
||||
CloudSync,
|
||||
HydraApi,
|
||||
logger,
|
||||
WindowManager,
|
||||
Wine,
|
||||
} from "@main/services";
|
||||
import fs from "node:fs";
|
||||
import * as tar from "tar";
|
||||
import { registerEvent } from "../register-event";
|
||||
@@ -98,6 +104,10 @@ const downloadGameArtifact = async (
|
||||
) => {
|
||||
try {
|
||||
const game = await gamesSublevel.get(levelKeys.game(shop, objectId));
|
||||
const effectiveWinePrefixPath = Wine.getEffectivePrefixPath(
|
||||
game?.winePrefixPath,
|
||||
objectId
|
||||
);
|
||||
|
||||
const {
|
||||
downloadUrl,
|
||||
@@ -152,7 +162,7 @@ const downloadGameArtifact = async (
|
||||
backupPath,
|
||||
objectId,
|
||||
normalizePath(homeDir),
|
||||
game?.winePrefixPath,
|
||||
effectiveWinePrefixPath,
|
||||
artifactWinePrefixPath
|
||||
);
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { registerEvent } from "../register-event";
|
||||
import type { GameShop } from "@types";
|
||||
import { Ludusavi } from "@main/services";
|
||||
import { Ludusavi, Wine } from "@main/services";
|
||||
import { gamesSublevel, levelKeys } from "@main/level";
|
||||
|
||||
const getGameBackupPreview = async (
|
||||
@@ -10,7 +10,11 @@ const getGameBackupPreview = async (
|
||||
) => {
|
||||
const game = await gamesSublevel.get(levelKeys.game(shop, objectId));
|
||||
|
||||
return Ludusavi.getBackupPreview(shop, objectId, game?.winePrefixPath);
|
||||
return Ludusavi.getBackupPreview(
|
||||
shop,
|
||||
objectId,
|
||||
Wine.getEffectivePrefixPath(game?.winePrefixPath, objectId)
|
||||
);
|
||||
};
|
||||
|
||||
registerEvent("getGameBackupPreview", getGameBackupPreview);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { registerEvent } from "../register-event";
|
||||
import { logger } from "@main/services";
|
||||
import { logger, Wine } from "@main/services";
|
||||
import sudo from "sudo-prompt";
|
||||
import { app } from "electron";
|
||||
import { PythonRPC } from "@main/services/python-rpc";
|
||||
@@ -51,7 +51,10 @@ const closeGame = async (
|
||||
return false;
|
||||
}
|
||||
|
||||
const expectedPrefix = game.winePrefixPath?.toLowerCase();
|
||||
const expectedPrefix = Wine.getEffectivePrefixPath(
|
||||
game.winePrefixPath,
|
||||
game.objectId
|
||||
)?.toLowerCase();
|
||||
const processPrefix =
|
||||
runningProcess.environ?.STEAM_COMPAT_DATA_PATH?.toLowerCase();
|
||||
|
||||
|
||||
@@ -21,16 +21,22 @@ const getLibrary = async (): Promise<LibraryGame[]> => {
|
||||
.map(async ([key, game]) => {
|
||||
const download = await downloadsSublevel.get(key);
|
||||
const gameAssets = await gamesShopAssetsSublevel.get(key);
|
||||
const achievements = await gameAchievementsSublevel.get(key).catch(() => null);
|
||||
const achievements = await gameAchievementsSublevel
|
||||
.get(key)
|
||||
.catch(() => null);
|
||||
|
||||
const validAchievementNames = new Set(
|
||||
achievements?.achievements?.map((a) => (a.name ?? "").toUpperCase()) || []
|
||||
achievements?.achievements?.map((a) =>
|
||||
(a.name ?? "").toUpperCase()
|
||||
) || []
|
||||
);
|
||||
|
||||
const unlockedAchievementCount =
|
||||
achievements?.unlockedAchievements?.filter(
|
||||
(unlocked) =>
|
||||
validAchievementNames.has((unlocked.name ?? "").toUpperCase()) && unlocked.unlockTime > 0
|
||||
validAchievementNames.has(
|
||||
(unlocked.name ?? "").toUpperCase()
|
||||
) && unlocked.unlockTime > 0
|
||||
).length ??
|
||||
game.unlockedAchievementCount ??
|
||||
0;
|
||||
|
||||
@@ -7,7 +7,7 @@ import { getDownloadsPath } from "../helpers/get-downloads-path";
|
||||
import { registerEvent } from "../register-event";
|
||||
import { downloadsSublevel, gamesSublevel, levelKeys } from "@main/level";
|
||||
import { GameShop } from "@types";
|
||||
import { logger, Umu } from "@main/services";
|
||||
import { logger, Umu, Wine } from "@main/services";
|
||||
|
||||
const launchInstallerWithWine = async (filePath: string): Promise<boolean> => {
|
||||
return await new Promise<boolean>((resolve) => {
|
||||
@@ -102,6 +102,10 @@ const openGameInstaller = async (
|
||||
const downloadKey = levelKeys.game(shop, objectId);
|
||||
const download = await downloadsSublevel.get(downloadKey);
|
||||
const game = await gamesSublevel.get(downloadKey).catch(() => null);
|
||||
const effectiveWinePrefixPath = Wine.getEffectivePrefixPath(
|
||||
game?.winePrefixPath,
|
||||
objectId
|
||||
);
|
||||
|
||||
if (!download?.folderName) return true;
|
||||
|
||||
@@ -128,7 +132,7 @@ const openGameInstaller = async (
|
||||
if (fs.existsSync(setupPath)) {
|
||||
return await executeGameInstaller(setupPath, {
|
||||
gameId: objectId,
|
||||
winePrefixPath: game?.winePrefixPath,
|
||||
winePrefixPath: effectiveWinePrefixPath,
|
||||
protonPath: game?.protonPath,
|
||||
});
|
||||
}
|
||||
@@ -143,7 +147,7 @@ const openGameInstaller = async (
|
||||
path.join(gamePath, gamePathExecutableFiles[0]),
|
||||
{
|
||||
gameId: objectId,
|
||||
winePrefixPath: game?.winePrefixPath,
|
||||
winePrefixPath: effectiveWinePrefixPath,
|
||||
protonPath: game?.protonPath,
|
||||
}
|
||||
);
|
||||
|
||||
@@ -21,6 +21,7 @@ import { publishCombinedNewAchievementNotification } from "../notifications";
|
||||
import { db, gamesSublevel, levelKeys } from "@main/level";
|
||||
import { WindowManager } from "../window-manager";
|
||||
import { setTimeout } from "node:timers/promises";
|
||||
import { Wine } from "../wine";
|
||||
|
||||
const fileStats: Map<string, number> = new Map();
|
||||
const fltFiles: Map<string, Set<string>> = new Map();
|
||||
@@ -61,7 +62,11 @@ const watchAchievementsWithWine = async () => {
|
||||
.values()
|
||||
.all()
|
||||
.then((games) =>
|
||||
games.filter((game) => !game.isDeleted && game.winePrefixPath)
|
||||
games.filter(
|
||||
(game) =>
|
||||
!game.isDeleted &&
|
||||
!!Wine.getEffectivePrefixPath(game.winePrefixPath, game.objectId)
|
||||
)
|
||||
);
|
||||
|
||||
for (const game of games) {
|
||||
|
||||
@@ -5,6 +5,7 @@ import { Cracker } from "@shared";
|
||||
import { achievementsLogger } from "../logger";
|
||||
import { SystemPath } from "../system-path";
|
||||
import { getSteamLocation, getSteamUsersIds } from "../steam";
|
||||
import { Wine } from "../wine";
|
||||
import { db, levelKeys } from "@main/level";
|
||||
|
||||
const getAppDataPath = () => {
|
||||
@@ -252,12 +253,14 @@ export const getAlternativeObjectIds = (objectId: string) => {
|
||||
|
||||
export const findAchievementFiles = (game: Game) => {
|
||||
const achievementFiles: AchievementFile[] = [];
|
||||
const effectiveWinePrefixPath =
|
||||
Wine.getEffectivePrefixPath(game.winePrefixPath, game.objectId) ?? "";
|
||||
|
||||
for (const cracker of crackers) {
|
||||
for (const { folderPath, fileLocation } of getPathFromCracker(cracker)) {
|
||||
for (const objectId of getAlternativeObjectIds(game.objectId)) {
|
||||
const filePath = path.join(
|
||||
game.winePrefixPath ?? "",
|
||||
effectiveWinePrefixPath,
|
||||
folderPath,
|
||||
...mapFileLocationWithObjectId(fileLocation, objectId)
|
||||
);
|
||||
@@ -331,11 +334,14 @@ export const findAchievementFileInExecutableDirectory = (
|
||||
return [];
|
||||
}
|
||||
|
||||
const effectiveWinePrefixPath =
|
||||
Wine.getEffectivePrefixPath(game.winePrefixPath, game.objectId) ?? "";
|
||||
|
||||
return [
|
||||
{
|
||||
type: Cracker.userstats,
|
||||
filePath: path.join(
|
||||
game.winePrefixPath ?? "",
|
||||
effectiveWinePrefixPath,
|
||||
game.executablePath,
|
||||
"..",
|
||||
"SteamData",
|
||||
@@ -345,7 +351,7 @@ export const findAchievementFileInExecutableDirectory = (
|
||||
{
|
||||
type: Cracker._3dm,
|
||||
filePath: path.join(
|
||||
game.winePrefixPath ?? "",
|
||||
effectiveWinePrefixPath,
|
||||
game.executablePath,
|
||||
"..",
|
||||
"3DMGAME",
|
||||
|
||||
@@ -15,6 +15,7 @@ import { Ludusavi } from "./ludusavi";
|
||||
import { formatDate, SubscriptionRequiredError } from "@shared";
|
||||
import i18next, { t } from "i18next";
|
||||
import { SystemPath } from "./system-path";
|
||||
import { Wine } from "./wine";
|
||||
|
||||
export class CloudSync {
|
||||
public static getWindowsLikeUserProfilePath(winePrefixPath?: string | null) {
|
||||
@@ -118,11 +119,15 @@ export class CloudSync {
|
||||
}
|
||||
|
||||
const game = await gamesSublevel.get(levelKeys.game(shop, objectId));
|
||||
const effectiveWinePrefixPath = Wine.getEffectivePrefixPath(
|
||||
game?.winePrefixPath,
|
||||
objectId
|
||||
);
|
||||
|
||||
const bundleLocation = await this.bundleBackup(
|
||||
shop,
|
||||
objectId,
|
||||
game?.winePrefixPath ?? null
|
||||
effectiveWinePrefixPath
|
||||
);
|
||||
|
||||
const stat = await fs.promises.stat(bundleLocation);
|
||||
@@ -135,10 +140,12 @@ export class CloudSync {
|
||||
shop,
|
||||
objectId,
|
||||
hostname: os.hostname(),
|
||||
winePrefixPath: game?.winePrefixPath
|
||||
? fs.realpathSync(game.winePrefixPath)
|
||||
winePrefixPath: effectiveWinePrefixPath
|
||||
? fs.existsSync(effectiveWinePrefixPath)
|
||||
? fs.realpathSync(effectiveWinePrefixPath)
|
||||
: effectiveWinePrefixPath
|
||||
: null,
|
||||
homeDir: this.getWindowsLikeUserProfilePath(game?.winePrefixPath ?? null),
|
||||
homeDir: this.getWindowsLikeUserProfilePath(effectiveWinePrefixPath),
|
||||
downloadOptionTitle,
|
||||
platform: process.platform,
|
||||
label,
|
||||
|
||||
@@ -23,7 +23,6 @@ export class GofileApi {
|
||||
private static readonly defaultUserAgent = "Mozilla/5.0";
|
||||
private static readonly language = "en-US";
|
||||
private static readonly timeoutMs = 15000;
|
||||
private static readonly maxRetries = 3;
|
||||
private static token: string;
|
||||
|
||||
private static get userAgent() {
|
||||
@@ -53,10 +52,6 @@ export class GofileApi {
|
||||
return headers;
|
||||
}
|
||||
|
||||
private static async sleep(ms: number) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
public static async authorize() {
|
||||
const requestHeaders = {
|
||||
...this.getBaseHeaders(),
|
||||
@@ -64,41 +59,30 @@ export class GofileApi {
|
||||
"X-BL": this.language,
|
||||
};
|
||||
|
||||
for (let retry = 0; retry < this.maxRetries; retry += 1) {
|
||||
try {
|
||||
const response = await axios.post<{
|
||||
status: string;
|
||||
data: GofileAccountsReponse;
|
||||
}>("https://api.gofile.io/accounts", undefined, {
|
||||
headers: requestHeaders,
|
||||
timeout: this.timeoutMs,
|
||||
});
|
||||
try {
|
||||
const response = await axios.post<{
|
||||
status: string;
|
||||
data: GofileAccountsReponse;
|
||||
}>("https://api.gofile.io/accounts", undefined, {
|
||||
headers: requestHeaders,
|
||||
timeout: this.timeoutMs,
|
||||
});
|
||||
|
||||
if (response.data.status === "ok") {
|
||||
this.token = response.data.data.token;
|
||||
return this.token;
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`Account creation failed: ${response.data.status ?? "unknown"}`
|
||||
);
|
||||
} catch (error) {
|
||||
const isLastAttempt = retry === this.maxRetries - 1;
|
||||
if (isLastAttempt) {
|
||||
if (axios.isAxiosError(error) && error.code === "ECONNABORTED") {
|
||||
throw new Error(
|
||||
"Account creation timed out after multiple retries"
|
||||
);
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
await this.sleep(2 ** retry * 1000);
|
||||
if (response.data.status === "ok") {
|
||||
this.token = response.data.data.token;
|
||||
return this.token;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error("Account creation failed after all retries");
|
||||
throw new Error(
|
||||
`Account creation failed: ${response.data.status ?? "unknown"}`
|
||||
);
|
||||
} catch (error) {
|
||||
if (axios.isAxiosError(error) && error.code === "ECONNABORTED") {
|
||||
throw new Error("Account creation timed out");
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
public static async getDownloadLink(id: string, password?: string) {
|
||||
|
||||
@@ -11,6 +11,7 @@ import { PowerSaveBlockerManager } from "./power-save-blocker";
|
||||
import path from "path";
|
||||
import { AchievementWatcherManager } from "./achievements/achievement-watcher-manager";
|
||||
import { MAIN_LOOP_INTERVAL } from "@main/constants";
|
||||
import { Wine } from "./wine";
|
||||
|
||||
export const gamesPlaytime = new Map<
|
||||
string,
|
||||
@@ -172,7 +173,10 @@ const hasLinuxCompatibilityProcessMatch = (
|
||||
const executableName = path.basename(executablePath).toLowerCase();
|
||||
const executableNameWithoutExtension = executableName.replace(/\.exe$/i, "");
|
||||
const executableDirectory = path.dirname(executablePath).toLowerCase();
|
||||
const expectedWinePrefix = game.winePrefixPath?.toLowerCase();
|
||||
const expectedWinePrefix = Wine.getEffectivePrefixPath(
|
||||
game.winePrefixPath,
|
||||
game.objectId
|
||||
)?.toLowerCase();
|
||||
|
||||
return linuxProcesses.some((process) => {
|
||||
if (process.cwd !== executableDirectory) {
|
||||
|
||||
@@ -188,8 +188,6 @@ export function CloudSyncPanel({
|
||||
|
||||
const disableActions =
|
||||
uploadingBackup || restoringBackup || deletingArtifact || freezingArtifact;
|
||||
const isMissingWinePrefix =
|
||||
window.electron.platform === "linux" && !game?.winePrefixPath;
|
||||
|
||||
if (!hasActiveSubscription) {
|
||||
return (
|
||||
@@ -247,13 +245,10 @@ export function CloudSyncPanel({
|
||||
<Button
|
||||
type="button"
|
||||
onClick={() => uploadSaveGame(lastDownloadedOption?.title ?? null)}
|
||||
tooltip={isMissingWinePrefix ? t("missing_wine_prefix") : undefined}
|
||||
tooltipPlace="left"
|
||||
disabled={
|
||||
disableActions ||
|
||||
!backupPreview?.overall.totalGames ||
|
||||
hasReachedLimit ||
|
||||
isMissingWinePrefix
|
||||
hasReachedLimit
|
||||
}
|
||||
>
|
||||
{uploadingBackup ? (
|
||||
|
||||
Reference in New Issue
Block a user