mirror of
https://git.sanhost.net/sanasol/hytale-f2p.git
synced 2026-02-26 14:51:48 -03:00
Compare commits
6 Commits
fix/patche
...
fix/steamd
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9ec97f9d33 | ||
|
|
ee18455b4b | ||
|
|
a5c931b26d | ||
|
|
661a0c9eed | ||
|
|
9025800820 | ||
|
|
34ee099ae2 |
@@ -4,6 +4,9 @@ const i18n = (() => {
|
||||
let translations = {};
|
||||
const availableLanguages = [
|
||||
{ code: 'en', name: 'English' },
|
||||
{ code: 'fr', name: 'Français' },
|
||||
{ code: 'de', name: 'Deutsch' },
|
||||
{ code: 'sv', name: 'Svenska' },
|
||||
{ code: 'es-ES', name: 'Español (España)' },
|
||||
{ code: 'pt-BR', name: 'Portuguese (Brazil)' },
|
||||
{ code: 'tr-TR', name: 'Turkish (Turkey)' },
|
||||
|
||||
283
GUI/locales/de.json
Normal file
283
GUI/locales/de.json
Normal file
@@ -0,0 +1,283 @@
|
||||
{
|
||||
"nav": {
|
||||
"play": "Spielen",
|
||||
"mods": "Mods",
|
||||
"news": "Neuigkeiten",
|
||||
"chat": "Spieler-Chat",
|
||||
"settings": "Einstellungen"
|
||||
},
|
||||
"header": {
|
||||
"playersLabel": "Spieler:",
|
||||
"manageProfiles": "Profile verwalten",
|
||||
"defaultProfile": "Standard"
|
||||
},
|
||||
"install": {
|
||||
"title": "KOSTENLOSER LAUNCHER",
|
||||
"playerName": "Spielername",
|
||||
"playerNamePlaceholder": "Namen eingeben",
|
||||
"gameBranch": "Spielversion",
|
||||
"releaseVersion": "Release (Stabil)",
|
||||
"preReleaseVersion": "Pre-Release (Experimentell)",
|
||||
"customInstallation": "Benutzerdefinierte Installation",
|
||||
"installationFolder": "Installationsordner",
|
||||
"pathPlaceholder": "Standardspeicherort",
|
||||
"browse": "Durchsuchen",
|
||||
"installButton": "HYTALE INSTALLIEREN",
|
||||
"installing": "INSTALLIERE..."
|
||||
},
|
||||
"play": {
|
||||
"ready": "BEREIT ZUM SPIELEN",
|
||||
"subtitle": "Starte Hytale und beginne das Abenteuer",
|
||||
"playButton": "HYTALE SPIELEN",
|
||||
"latestNews": "NEUESTE NACHRICHTEN",
|
||||
"viewAll": "ALLE ANZEIGEN",
|
||||
"checking": "ÜBERPRÜFE...",
|
||||
"play": "SPIELEN"
|
||||
},
|
||||
"mods": {
|
||||
"searchPlaceholder": "Mods suchen...",
|
||||
"myMods": "MEINE MODS",
|
||||
"previous": "ZURÜCK",
|
||||
"next": "WEITER",
|
||||
"page": "Seite",
|
||||
"of": "von",
|
||||
"modalTitle": "MEINE MODS",
|
||||
"noModsFound": "Keine Mods gefunden",
|
||||
"noModsFoundDesc": "Versuche deine Suche anzupassen",
|
||||
"noModsInstalled": "Keine Mods installiert",
|
||||
"noModsInstalledDesc": "Füge Mods von CurseForge hinzu oder importiere lokale Dateien",
|
||||
"view": "ANZEIGEN",
|
||||
"install": "INSTALLIEREN",
|
||||
"installed": "INSTALLIERT",
|
||||
"enable": "AKTIVIEREN",
|
||||
"disable": "DEAKTIVIEREN",
|
||||
"active": "AKTIV",
|
||||
"disabled": "DEAKTIVIERT",
|
||||
"delete": "Mod löschen",
|
||||
"noDescription": "Keine Beschreibung verfügbar",
|
||||
"confirmDelete": "Möchtest du \"{name}\" wirklich löschen?",
|
||||
"confirmDeleteDesc": "Diese Aktion kann nicht rückgängig gemacht werden.",
|
||||
"confirmDeletion": "Löschung bestätigen",
|
||||
"apiKeyRequired": "API-Schlüssel erforderlich",
|
||||
"apiKeyRequiredDesc": "CurseForge API-Schlüssel wird benötigt, um Mods zu durchsuchen"
|
||||
},
|
||||
"news": {
|
||||
"title": "ALLE NACHRICHTEN",
|
||||
"readMore": "Mehr lesen"
|
||||
},
|
||||
"chat": {
|
||||
"title": "SPIELER-CHAT",
|
||||
"pickColor": "Farbe",
|
||||
"inputPlaceholder": "Nachricht eingeben...",
|
||||
"send": "Senden",
|
||||
"online": "online",
|
||||
"charCounter": "{current}/{max}",
|
||||
"secureChat": "Sicherer Chat - Links werden zensiert",
|
||||
"joinChat": "Chat beitreten",
|
||||
"chooseUsername": "Wähle einen Benutzernamen, um dem Spieler-Chat beizutreten",
|
||||
"username": "Benutzername",
|
||||
"usernamePlaceholder": "Benutzernamen eingeben...",
|
||||
"usernameHint": "3-20 Zeichen, nur Buchstaben, Zahlen, - und _",
|
||||
"joinButton": "Chat beitreten",
|
||||
"colorModal": {
|
||||
"title": "Benutzernamenfarbe anpassen",
|
||||
"chooseSolid": "Wähle eine einfarbige Farbe:",
|
||||
"customColor": "Benutzerdefinierte Farbe:",
|
||||
"preview": "Vorschau:",
|
||||
"previewUsername": "Benutzername",
|
||||
"apply": "Farbe anwenden"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"title": "EINSTELLUNGEN",
|
||||
"java": "Java Runtime",
|
||||
"useCustomJava": "Benutzerdefinierten Java-Pfad verwenden",
|
||||
"javaDescription": "Ersetze die mitgelieferte Java-Installation durch deine eigene",
|
||||
"javaPath": "Java-Ausführungsdatei-Pfad",
|
||||
"javaPathPlaceholder": "Java-Pfad auswählen...",
|
||||
"javaBrowse": "Durchsuchen",
|
||||
"javaHint": "Wähle den Java-Installationsordner (unterstützt Windows, Mac, Linux)",
|
||||
"discord": "Discord-Integration",
|
||||
"enableRPC": "Discord Rich Presence aktivieren",
|
||||
"discordDescription": "Zeige deine Launcher-Aktivität auf Discord",
|
||||
"game": "Spieloptionen",
|
||||
"playerName": "Spielername",
|
||||
"playerNamePlaceholder": "Spielernamen eingeben",
|
||||
"playerNameHint": "Dieser Name wird im Spiel verwendet (1-16 Zeichen)",
|
||||
"openGameLocation": "Spielordner öffnen",
|
||||
"openGameLocationDesc": "Öffne den Spielinstallationsordner",
|
||||
"account": "Spieler-UUID-Verwaltung",
|
||||
"currentUUID": "Aktuelle UUID",
|
||||
"uuidPlaceholder": "UUID wird geladen...",
|
||||
"copyUUID": "UUID kopieren",
|
||||
"regenerateUUID": "UUID neu generieren",
|
||||
"uuidHint": "Deine eindeutige Spielerkennung für diesen Benutzernamen",
|
||||
"manageUUIDs": "Alle UUIDs verwalten",
|
||||
"manageUUIDsDesc": "Alle Spieler-UUIDs anzeigen und verwalten",
|
||||
"language": "Sprache",
|
||||
"selectLanguage": "Sprache auswählen",
|
||||
"repairGame": "Spiel reparieren",
|
||||
"reinstallGame": "Spieldateien neu installieren (behält Daten)",
|
||||
"gpuPreference": "GPU-Präferenz",
|
||||
"gpuHint": "Wähle deine bevorzugte GPU (Linux: betrifft DRI_PRIME)",
|
||||
"gpuAuto": "Auto",
|
||||
"gpuIntegrated": "Integriert",
|
||||
"gpuDedicated": "Dediziert",
|
||||
"logs": "SYSTEMPROTOKOLLE",
|
||||
"logsCopy": "Kopieren",
|
||||
"logsRefresh": "Aktualisieren",
|
||||
"logsFolder": "Ordner öffnen",
|
||||
"logsLoading": "Protokolle werden geladen...",
|
||||
"closeLauncher": "Launcher-Verhalten",
|
||||
"closeOnStart": "Launcher beim Spielstart schließen",
|
||||
"closeOnStartDescription": "Schließe den Launcher automatisch, nachdem Hytale gestartet wurde",
|
||||
"hwAccel": "Hardware-Beschleunigung",
|
||||
"hwAccelDescription": "Hardware-Beschleunigung für den Launcher aktivieren",
|
||||
"gameBranch": "Spiel-Branch",
|
||||
"branchRelease": "Release",
|
||||
"branchPreRelease": "Pre-Release",
|
||||
"branchHint": "Wechsel zwischen stabiler Release- und experimenteller Pre-Release-Version",
|
||||
"branchWarning": "Das Ändern des Branches lädt eine andere Spielversion herunter und installiert sie",
|
||||
"branchSwitching": "Wechsle zu {branch}...",
|
||||
"branchSwitched": "Erfolgreich zu {branch} gewechselt!",
|
||||
"installRequired": "Installation erforderlich",
|
||||
"branchInstallConfirm": "Das Spiel wird für den {branch}-Branch installiert. Fortfahren?"
|
||||
},
|
||||
"uuid": {
|
||||
"modalTitle": "UUID-Verwaltung",
|
||||
"currentUserUUID": "Aktuelle Benutzer-UUID",
|
||||
"allPlayerUUIDs": "Alle Spieler-UUIDs",
|
||||
"generateNew": "Neue UUID generieren",
|
||||
"loadingUUIDs": "UUIDs werden geladen...",
|
||||
"setCustomUUID": "Benutzerdefinierte UUID festlegen",
|
||||
"customPlaceholder": "Benutzerdefinierte UUID eingeben (Format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)",
|
||||
"setUUID": "UUID festlegen",
|
||||
"warning": "Warnung: Das Festlegen einer benutzerdefinierten UUID ändert deine aktuelle Spieleridentität",
|
||||
"copyTooltip": "UUID kopieren",
|
||||
"regenerateTooltip": "Neue UUID generieren"
|
||||
},
|
||||
"profiles": {
|
||||
"modalTitle": "Profile verwalten",
|
||||
"newProfilePlaceholder": "Neuer Profilname",
|
||||
"createProfile": "Profil erstellen"
|
||||
},
|
||||
"discord": {
|
||||
"notificationText": "Tritt unserer Discord-Community bei!",
|
||||
"joinButton": "Discord beitreten"
|
||||
},
|
||||
"common": {
|
||||
"confirm": "Bestätigen",
|
||||
"cancel": "Abbrechen",
|
||||
"save": "Speichern",
|
||||
"close": "Schließen",
|
||||
"delete": "Löschen",
|
||||
"edit": "Bearbeiten",
|
||||
"loading": "Lädt...",
|
||||
"apply": "Anwenden",
|
||||
"install": "Installieren"
|
||||
},
|
||||
"notifications": {
|
||||
"gameDataNotFound": "Fehler: Spieldaten nicht gefunden",
|
||||
"gameUpdatedSuccess": "Spiel erfolgreich aktualisiert! 🎉",
|
||||
"updateFailed": "Update fehlgeschlagen: {error}",
|
||||
"updateError": "Update-Fehler: {error}",
|
||||
"discordEnabled": "Discord Rich Presence aktiviert",
|
||||
"discordDisabled": "Discord Rich Presence deaktiviert",
|
||||
"discordSaveFailed": "Discord-Einstellung konnte nicht gespeichert werden",
|
||||
"playerNameRequired": "Bitte gib einen gültigen Spielernamen ein",
|
||||
"playerNameSaved": "Spielername erfolgreich gespeichert",
|
||||
"playerNameSaveFailed": "Spielername konnte nicht gespeichert werden",
|
||||
"uuidCopied": "UUID in die Zwischenablage kopiert!",
|
||||
"uuidCopyFailed": "UUID konnte nicht kopiert werden",
|
||||
"uuidRegenNotAvailable": "UUID-Neugenerierung nicht verfügbar",
|
||||
"uuidRegenFailed": "UUID konnte nicht neu generiert werden",
|
||||
"uuidGenerated": "Neue UUID erfolgreich generiert!",
|
||||
"uuidGeneratedShort": "Neue UUID generiert!",
|
||||
"uuidGenerateFailed": "Neue UUID konnte nicht generiert werden",
|
||||
"uuidRequired": "Bitte gib eine UUID ein",
|
||||
"uuidInvalidFormat": "Ungültiges UUID-Format",
|
||||
"uuidSetFailed": "Benutzerdefinierte UUID konnte nicht festgelegt werden",
|
||||
"uuidSetSuccess": "Benutzerdefinierte UUID erfolgreich festgelegt!",
|
||||
"uuidDeleteFailed": "UUID konnte nicht gelöscht werden",
|
||||
"uuidDeleteSuccess": "UUID erfolgreich gelöscht!",
|
||||
"modsDownloading": "{name} wird heruntergeladen...",
|
||||
"modsTogglingMod": "Mod wird umgeschaltet...",
|
||||
"modsDeletingMod": "Mod wird gelöscht...",
|
||||
"modsLoadingMods": "Mods von CurseForge werden geladen...",
|
||||
"modsInstalledSuccess": "{name} erfolgreich installiert! 🎉",
|
||||
"modsDeletedSuccess": "{name} erfolgreich gelöscht",
|
||||
"modsDownloadFailed": "Mod konnte nicht heruntergeladen werden: {error}",
|
||||
"modsToggleFailed": "Mod konnte nicht umgeschaltet werden: {error}",
|
||||
"modsDeleteFailed": "Mod konnte nicht gelöscht werden: {error}",
|
||||
"modsModNotFound": "Mod-Informationen nicht gefunden",
|
||||
"hwAccelSaved": "Hardware-Beschleunigungseinstellung gespeichert",
|
||||
"hwAccelSaveFailed": "Hardware-Beschleunigungseinstellung konnte nicht gespeichert werden",
|
||||
"javaPathCopied": "Java-Pfad in die Zwischenablage kopiert!",
|
||||
"javaPathCopyFailed": "Java-Pfad konnte nicht kopiert werden",
|
||||
"javaPathSaved": "Java-Pfad erfolgreich gespeichert!",
|
||||
"javaPathSaveFailed": "Java-Pfad konnte nicht gespeichert werden",
|
||||
"javaPathInvalid": "Ungültiger Java-Pfad",
|
||||
"javaPathReset": "Java-Pfad auf Standardwerte zurückgesetzt",
|
||||
"gameLocationError": "Spielordner konnte nicht geöffnet werden",
|
||||
"launcherRestartRequired": "Launcher-Neustart erforderlich, um Änderungen anzuwenden",
|
||||
"gameRepairConfirm": "Möchtest du das Spiel wirklich reparieren? Dies wird alle Spieldateien neu installieren.",
|
||||
"gameRepairInProgress": "Spiel wird repariert...",
|
||||
"gameRepairSuccess": "Spiel erfolgreich repariert!",
|
||||
"gameRepairFailed": "Spielreparatur fehlgeschlagen: {error}",
|
||||
"invalidUsername": "Ungültiger Benutzername",
|
||||
"usernameInUse": "Benutzername bereits vergeben",
|
||||
"chatJoinSuccess": "Du bist dem Chat beigetreten!",
|
||||
"chatJoinFailed": "Chat-Beitritt fehlgeschlagen",
|
||||
"messageTooLong": "Nachricht zu lang",
|
||||
"messageSent": "Nachricht gesendet",
|
||||
"messageSendFailed": "Nachricht konnte nicht gesendet werden",
|
||||
"colorUpdated": "Farbe aktualisiert!",
|
||||
"colorUpdateFailed": "Farbe konnte nicht aktualisiert werden",
|
||||
"profileCreated": "Profil erfolgreich erstellt!",
|
||||
"profileCreateFailed": "Profil konnte nicht erstellt werden",
|
||||
"profileDeleted": "Profil gelöscht",
|
||||
"profileDeleteFailed": "Profil konnte nicht gelöscht werden",
|
||||
"profileSwitched": "Profil gewechselt zu: {name}",
|
||||
"profileSwitchFailed": "Profilwechsel fehlgeschlagen",
|
||||
"invalidProfileName": "Ungültiger Profilname",
|
||||
"profileNameExists": "Ein Profil mit diesem Namen existiert bereits",
|
||||
"noInternet": "Keine Internetverbindung",
|
||||
"checkInternetConnection": "Überprüfe deine Internetverbindung",
|
||||
"serverError": "Serverfehler. Bitte versuche es später erneut.",
|
||||
"unknownError": "Ein unbekannter Fehler ist aufgetreten"
|
||||
},
|
||||
"confirm": {
|
||||
"defaultTitle": "Aktion bestätigen",
|
||||
"regenerateUuidTitle": "Neue UUID generieren",
|
||||
"regenerateUuidMessage": "Möchtest du wirklich eine neue UUID generieren? Dies ändert deine Spieleridentität.",
|
||||
"regenerateUuidButton": "Generieren",
|
||||
"setCustomUuidTitle": "Benutzerdefinierte UUID festlegen",
|
||||
"setCustomUuidMessage": "Möchtest du wirklich diese benutzerdefinierte UUID festlegen? Dies ändert deine Spieleridentität.",
|
||||
"setCustomUuidButton": "UUID festlegen",
|
||||
"deleteUuidTitle": "UUID löschen",
|
||||
"deleteUuidMessage": "Möchtest du wirklich die UUID für \"{username}\" löschen? Diese Aktion kann nicht rückgängig gemacht werden.",
|
||||
"deleteUuidButton": "Löschen",
|
||||
"uninstallGameTitle": "Spiel deinstallieren",
|
||||
"uninstallGameMessage": "Möchtest du Hytale wirklich deinstallieren? Alle Spieldateien werden gelöscht.",
|
||||
"uninstallGameButton": "Deinstallieren"
|
||||
},
|
||||
"progress": {
|
||||
"initializing": "Initialisiere...",
|
||||
"downloading": "Lädt herunter...",
|
||||
"installing": "Installiere...",
|
||||
"extracting": "Entpacke...",
|
||||
"verifying": "Überprüfe...",
|
||||
"switchingProfile": "Profil wird gewechselt...",
|
||||
"profileSwitched": "Profil gewechselt!",
|
||||
"startingGame": "Spiel wird gestartet...",
|
||||
"launching": "STARTET...",
|
||||
"uninstallingGame": "Spiel wird deinstalliert...",
|
||||
"gameUninstalled": "Spiel erfolgreich deinstalliert!",
|
||||
"uninstallFailed": "Deinstallation fehlgeschlagen: {error}",
|
||||
"startingUpdate": "Obligatorisches Spiel-Update wird gestartet...",
|
||||
"installationComplete": "Installation erfolgreich abgeschlossen!",
|
||||
"installationFailed": "Installation fehlgeschlagen: {error}",
|
||||
"installingGameFiles": "Spieldateien werden installiert...",
|
||||
"installComplete": "Installation abgeschlossen!"
|
||||
}
|
||||
}
|
||||
235
GUI/locales/fr.json
Normal file
235
GUI/locales/fr.json
Normal file
@@ -0,0 +1,235 @@
|
||||
{
|
||||
"nav": {
|
||||
"play": "Jouer",
|
||||
"mods": "Mods",
|
||||
"news": "Actualités",
|
||||
"chat": "Chat Joueurs",
|
||||
"settings": "Paramètres"
|
||||
},
|
||||
"header": {
|
||||
"playersLabel": "Joueurs:",
|
||||
"manageProfiles": "Gérer les Profils",
|
||||
"defaultProfile": "Par défaut"
|
||||
},
|
||||
"install": {
|
||||
"title": "LAUNCHER GRATUIT",
|
||||
"playerName": "Nom du Joueur",
|
||||
"playerNamePlaceholder": "Entrez votre nom",
|
||||
"gameBranch": "Version du Jeu",
|
||||
"releaseVersion": "Release (Stable)",
|
||||
"preReleaseVersion": "Pré-Release (Expérimental)",
|
||||
"customInstallation": "Installation Personnalisée",
|
||||
"installationFolder": "Dossier d'Installation",
|
||||
"pathPlaceholder": "Emplacement par défaut",
|
||||
"browse": "Parcourir",
|
||||
"installButton": "INSTALLER HYTALE",
|
||||
"installing": "INSTALLATION..."
|
||||
},
|
||||
"play": {
|
||||
"ready": "PRÊT À JOUER",
|
||||
"subtitle": "Lancez Hytale et entrez dans l'aventure",
|
||||
"playButton": "JOUER À HYTALE",
|
||||
"latestNews": "DERNIÈRES ACTUALITÉS",
|
||||
"viewAll": "VOIR TOUT",
|
||||
"checking": "VÉRIFICATION...",
|
||||
"play": "JOUER"
|
||||
},
|
||||
"mods": {
|
||||
"searchPlaceholder": "Rechercher des mods...",
|
||||
"myMods": "MES MODS",
|
||||
"previous": "PRÉCÉDENT",
|
||||
"next": "SUIVANT",
|
||||
"page": "Page",
|
||||
"of": "sur",
|
||||
"modalTitle": "MES MODS",
|
||||
"noModsFound": "Aucun Mod Trouvé",
|
||||
"noModsFoundDesc": "Essayez d'ajuster votre recherche",
|
||||
"noModsInstalled": "Aucun Mod Installé",
|
||||
"noModsInstalledDesc": "Ajoutez des mods depuis CurseForge ou importez des fichiers locaux",
|
||||
"view": "VOIR",
|
||||
"install": "INSTALLER",
|
||||
"installed": "INSTALLÉ",
|
||||
"enable": "ACTIVER",
|
||||
"disable": "DÉSACTIVER",
|
||||
"active": "ACTIF",
|
||||
"disabled": "DÉSACTIVÉ",
|
||||
"delete": "Supprimer le mod",
|
||||
"noDescription": "Aucune description disponible",
|
||||
"confirmDelete": "Êtes-vous sûr de vouloir supprimer \"{name}\" ?",
|
||||
"confirmDeleteDesc": "Cette action est irréversible.",
|
||||
"confirmDeletion": "Confirmer la Suppression",
|
||||
"apiKeyRequired": "Clé API Requise",
|
||||
"apiKeyRequiredDesc": "Une clé API CurseForge est nécessaire pour parcourir les mods"
|
||||
},
|
||||
"news": {
|
||||
"title": "TOUTES LES ACTUALITÉS",
|
||||
"readMore": "Lire Plus"
|
||||
},
|
||||
"chat": {
|
||||
"title": "CHAT JOUEURS",
|
||||
"pickColor": "Couleur",
|
||||
"inputPlaceholder": "Tapez votre message...",
|
||||
"send": "Envoyer",
|
||||
"online": "en ligne",
|
||||
"charCounter": "{current}/{max}",
|
||||
"secureChat": "Chat sécurisé - Les liens sont censurés",
|
||||
"joinChat": "Rejoindre le Chat",
|
||||
"chooseUsername": "Choisissez un nom d'utilisateur pour rejoindre le Chat Joueurs",
|
||||
"username": "Nom d'utilisateur",
|
||||
"usernamePlaceholder": "Entrez votre nom d'utilisateur...",
|
||||
"usernameHint": "3-20 caractères, lettres, chiffres, - et _ uniquement",
|
||||
"joinButton": "Rejoindre le Chat",
|
||||
"colorModal": {
|
||||
"title": "Personnaliser la Couleur du Nom",
|
||||
"chooseSolid": "Choisissez une couleur unie:",
|
||||
"customColor": "Couleur personnalisée:",
|
||||
"preview": "Aperçu:",
|
||||
"previewUsername": "Nom d'utilisateur",
|
||||
"apply": "Appliquer la Couleur"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"title": "PARAMÈTRES",
|
||||
"java": "Java Runtime",
|
||||
"useCustomJava": "Utiliser un Chemin Java Personnalisé",
|
||||
"javaDescription": "Remplacer le Java intégré par votre propre installation",
|
||||
"javaPath": "Chemin de l'Exécutable Java",
|
||||
"javaPathPlaceholder": "Sélectionnez le chemin Java...",
|
||||
"javaBrowse": "Parcourir",
|
||||
"javaHint": "Sélectionnez le dossier d'installation de Java (compatible Windows, Mac, Linux)",
|
||||
"discord": "Intégration Discord",
|
||||
"enableRPC": "Activer Discord Rich Presence",
|
||||
"discordDescription": "Afficher votre activité du launcher sur Discord",
|
||||
"game": "Options de Jeu",
|
||||
"playerName": "Nom du Joueur",
|
||||
"playerNamePlaceholder": "Entrez le nom du joueur",
|
||||
"playerNameHint": "Ce nom sera utilisé en jeu (1-16 caractères)",
|
||||
"openGameLocation": "Ouvrir l'Emplacement du Jeu",
|
||||
"openGameLocationDesc": "Ouvrir le dossier d'installation du jeu",
|
||||
"account": "Gestion UUID Joueur",
|
||||
"currentUUID": "UUID Actuel",
|
||||
"uuidPlaceholder": "Chargement UUID...",
|
||||
"copyUUID": "Copier UUID",
|
||||
"regenerateUUID": "Régénérer UUID",
|
||||
"uuidHint": "Votre identifiant unique de joueur pour ce nom d'utilisateur",
|
||||
"manageUUIDs": "Gérer Tous les UUIDs",
|
||||
"manageUUIDsDesc": "Voir et gérer tous les UUIDs de joueurs",
|
||||
"language": "Langue",
|
||||
"selectLanguage": "Sélectionner la Langue",
|
||||
"repairGame": "Réparer le Jeu",
|
||||
"reinstallGame": "Réinstaller les fichiers du jeu (préserve les données)",
|
||||
"gpuPreference": "Préférence GPU",
|
||||
"gpuHint": "Sélectionnez votre GPU préféré (Linux: affecte DRI_PRIME)",
|
||||
"gpuAuto": "Auto",
|
||||
"gpuIntegrated": "Intégré",
|
||||
"gpuDedicated": "Dédié",
|
||||
"logs": "JOURNAUX SYSTÈME",
|
||||
"logsCopy": "Copier",
|
||||
"logsRefresh": "Actualiser",
|
||||
"logsFolder": "Ouvrir le Dossier",
|
||||
"logsLoading": "Chargement des journaux...",
|
||||
"closeLauncher": "Comportement du Launcher",
|
||||
"closeOnStart": "Fermer le Launcher au démarrage du jeu",
|
||||
"closeOnStartDescription": "Fermer automatiquement le launcher après le lancement d'Hytale",
|
||||
"hwAccel": "Accélération Matérielle",
|
||||
"hwAccelDescription": "Activer l'accélération matérielle pour le launcher",
|
||||
"gameBranch": "Branche du Jeu",
|
||||
"branchRelease": "Release",
|
||||
"branchPreRelease": "Pré-Release",
|
||||
"branchHint": "Basculer entre la version stable release et la pré-release expérimentale",
|
||||
"branchWarning": "Changer de branche téléchargera et installera une version différente du jeu",
|
||||
"branchSwitching": "Passage à {branch}...",
|
||||
"branchSwitched": "Passage à {branch} réussi!",
|
||||
"installRequired": "Installation Requise",
|
||||
"branchInstallConfirm": "Le jeu sera installé pour la branche {branch}. Continuer?"
|
||||
},
|
||||
"uuid": {
|
||||
"modalTitle": "Gestion UUID",
|
||||
"currentUserUUID": "UUID Utilisateur Actuel",
|
||||
"allPlayerUUIDs": "Tous les UUIDs Joueurs",
|
||||
"generateNew": "Générer Nouvel UUID",
|
||||
"loadingUUIDs": "Chargement des UUIDs...",
|
||||
"setCustomUUID": "Définir UUID Personnalisé",
|
||||
"customPlaceholder": "Entrez UUID personnalisé (format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)",
|
||||
"setUUID": "Définir UUID",
|
||||
"warning": "Attention: Définir un UUID personnalisé changera votre identité de joueur actuelle",
|
||||
"copyTooltip": "Copier UUID",
|
||||
"regenerateTooltip": "Générer Nouvel UUID"
|
||||
},
|
||||
"profiles": {
|
||||
"modalTitle": "Gérer les Profils",
|
||||
"newProfilePlaceholder": "Nom du Nouveau Profil",
|
||||
"createProfile": "Créer un Profil"
|
||||
},
|
||||
"discord": {
|
||||
"notificationText": "Rejoignez notre communauté Discord!",
|
||||
"joinButton": "Rejoindre Discord"
|
||||
},
|
||||
"common": {
|
||||
"confirm": "Confirmer",
|
||||
"cancel": "Annuler",
|
||||
"save": "Sauvegarder",
|
||||
"close": "Fermer",
|
||||
"delete": "Supprimer",
|
||||
"edit": "Modifier",
|
||||
"loading": "Chargement...",
|
||||
"apply": "Appliquer",
|
||||
"install": "Installer"
|
||||
},
|
||||
"notifications": {
|
||||
"gameDataNotFound": "Erreur: Données du jeu introuvables",
|
||||
"gameUpdatedSuccess": "Jeu mis à jour avec succès! 🎉",
|
||||
"updateFailed": "Mise à jour échouée: {error}",
|
||||
"updateError": "Erreur de mise à jour: {error}",
|
||||
"discordEnabled": "Discord Rich Presence activé",
|
||||
"discordDisabled": "Discord Rich Presence désactivé",
|
||||
"discordSaveFailed": "Échec de la sauvegarde des paramètres Discord",
|
||||
"playerNameRequired": "Veuillez entrer un nom de joueur valide",
|
||||
"playerNameSaved": "Nom du joueur sauvegardé avec succès",
|
||||
"playerNameSaveFailed": "Échec de la sauvegarde du nom du joueur",
|
||||
"uuidCopied": "UUID copié dans le presse-papiers!",
|
||||
"uuidCopyFailed": "Échec de la copie de l'UUID",
|
||||
"uuidRegenNotAvailable": "Régénération UUID non disponible",
|
||||
"uuidRegenFailed": "Échec de la régénération de l'UUID",
|
||||
"uuidGenerated": "Nouvel UUID généré avec succès!",
|
||||
"uuidGeneratedShort": "Nouvel UUID généré!",
|
||||
"uuidGenerateFailed": "Échec de la génération du nouvel UUID",
|
||||
"uuidRequired": "Veuillez entrer un UUID",
|
||||
"uuidInvalidFormat": "Format UUID invalide",
|
||||
"uuidSetFailed": "Échec de la définition de l'UUID personnalisé",
|
||||
"uuidSetSuccess": "UUID personnalisé défini avec succès!",
|
||||
"javaPathCopied": "Chemin Java copié dans le presse-papiers!",
|
||||
"javaPathCopyFailed": "Échec de la copie du chemin Java",
|
||||
"javaPathSaved": "Chemin Java sauvegardé avec succès!",
|
||||
"javaPathSaveFailed": "Échec de la sauvegarde du chemin Java",
|
||||
"javaPathInvalid": "Chemin Java invalide",
|
||||
"javaPathReset": "Chemin Java réinitialisé aux valeurs par défaut",
|
||||
"gameLocationError": "Impossible d'ouvrir l'emplacement du jeu",
|
||||
"launcherRestartRequired": "Redémarrage du launcher requis pour appliquer les modifications",
|
||||
"gameRepairConfirm": "Êtes-vous sûr de vouloir réparer le jeu? Cela réinstallera tous les fichiers du jeu.",
|
||||
"gameRepairInProgress": "Réparation du jeu en cours...",
|
||||
"gameRepairSuccess": "Jeu réparé avec succès!",
|
||||
"gameRepairFailed": "Échec de la réparation du jeu: {error}",
|
||||
"invalidUsername": "Nom d'utilisateur invalide",
|
||||
"usernameInUse": "Nom d'utilisateur déjà utilisé",
|
||||
"chatJoinSuccess": "Vous avez rejoint le chat!",
|
||||
"chatJoinFailed": "Échec de la connexion au chat",
|
||||
"messageTooLong": "Message trop long",
|
||||
"messageSent": "Message envoyé",
|
||||
"messageSendFailed": "Échec de l'envoi du message",
|
||||
"colorUpdated": "Couleur mise à jour!",
|
||||
"colorUpdateFailed": "Échec de la mise à jour de la couleur",
|
||||
"profileCreated": "Profil créé avec succès!",
|
||||
"profileCreateFailed": "Échec de la création du profil",
|
||||
"profileDeleted": "Profil supprimé",
|
||||
"profileDeleteFailed": "Échec de la suppression du profil",
|
||||
"profileSwitched": "Profil changé vers: {name}",
|
||||
"profileSwitchFailed": "Échec du changement de profil",
|
||||
"invalidProfileName": "Nom de profil invalide",
|
||||
"profileNameExists": "Un profil avec ce nom existe déjà",
|
||||
"noInternet": "Pas de connexion Internet",
|
||||
"checkInternetConnection": "Vérifiez votre connexion Internet",
|
||||
"serverError": "Erreur serveur. Veuillez réessayer plus tard.",
|
||||
"unknownError": "Une erreur inconnue s'est produite"
|
||||
}
|
||||
}
|
||||
283
GUI/locales/sv.json
Normal file
283
GUI/locales/sv.json
Normal file
@@ -0,0 +1,283 @@
|
||||
{
|
||||
"nav": {
|
||||
"play": "Spela",
|
||||
"mods": "Moddar",
|
||||
"news": "Nyheter",
|
||||
"chat": "Spelarchatt",
|
||||
"settings": "Inställningar"
|
||||
},
|
||||
"header": {
|
||||
"playersLabel": "Spelare:",
|
||||
"manageProfiles": "Hantera profiler",
|
||||
"defaultProfile": "Standard"
|
||||
},
|
||||
"install": {
|
||||
"title": "GRATIS LAUNCHER",
|
||||
"playerName": "Spelarnamn",
|
||||
"playerNamePlaceholder": "Ange ditt namn",
|
||||
"gameBranch": "Spelversion",
|
||||
"releaseVersion": "Release (Stabil)",
|
||||
"preReleaseVersion": "Pre-Release (Experimentell)",
|
||||
"customInstallation": "Anpassad installation",
|
||||
"installationFolder": "Installationsmapp",
|
||||
"pathPlaceholder": "Standardplats",
|
||||
"browse": "Bläddra",
|
||||
"installButton": "INSTALLERA HYTALE",
|
||||
"installing": "INSTALLERAR..."
|
||||
},
|
||||
"play": {
|
||||
"ready": "REDO ATT SPELA",
|
||||
"subtitle": "Starta Hytale och börja äventyret",
|
||||
"playButton": "SPELA HYTALE",
|
||||
"latestNews": "SENASTE NYHETERNA",
|
||||
"viewAll": "VISA ALLA",
|
||||
"checking": "KONTROLLERAR...",
|
||||
"play": "SPELA"
|
||||
},
|
||||
"mods": {
|
||||
"searchPlaceholder": "Sök moddar...",
|
||||
"myMods": "MINA MODDAR",
|
||||
"previous": "FÖREGÅENDE",
|
||||
"next": "NÄSTA",
|
||||
"page": "Sida",
|
||||
"of": "av",
|
||||
"modalTitle": "MINA MODDAR",
|
||||
"noModsFound": "Inga moddar hittades",
|
||||
"noModsFoundDesc": "Försök justera din sökning",
|
||||
"noModsInstalled": "Inga moddar installerade",
|
||||
"noModsInstalledDesc": "Lägg till moddar från CurseForge eller importera lokala filer",
|
||||
"view": "VISA",
|
||||
"install": "INSTALLERA",
|
||||
"installed": "INSTALLERAD",
|
||||
"enable": "AKTIVERA",
|
||||
"disable": "INAKTIVERA",
|
||||
"active": "AKTIV",
|
||||
"disabled": "INAKTIVERAD",
|
||||
"delete": "Ta bort modd",
|
||||
"noDescription": "Ingen beskrivning tillgänglig",
|
||||
"confirmDelete": "Är du säker på att du vill ta bort \"{name}\"?",
|
||||
"confirmDeleteDesc": "Denna åtgärd kan inte ångras.",
|
||||
"confirmDeletion": "Bekräfta borttagning",
|
||||
"apiKeyRequired": "API-nyckel krävs",
|
||||
"apiKeyRequiredDesc": "CurseForge API-nyckel behövs för att bläddra bland moddar"
|
||||
},
|
||||
"news": {
|
||||
"title": "ALLA NYHETER",
|
||||
"readMore": "Läs mer"
|
||||
},
|
||||
"chat": {
|
||||
"title": "SPELARCHATT",
|
||||
"pickColor": "Färg",
|
||||
"inputPlaceholder": "Skriv ditt meddelande...",
|
||||
"send": "Skicka",
|
||||
"online": "online",
|
||||
"charCounter": "{current}/{max}",
|
||||
"secureChat": "Säker chatt - Länkar är censurerade",
|
||||
"joinChat": "Gå med i chatten",
|
||||
"chooseUsername": "Välj ett användarnamn för att gå med i spelarchartten",
|
||||
"username": "Användarnamn",
|
||||
"usernamePlaceholder": "Ange ditt användarnamn...",
|
||||
"usernameHint": "3-20 tecken, endast bokstäver, siffror, - och _",
|
||||
"joinButton": "Gå med i chatten",
|
||||
"colorModal": {
|
||||
"title": "Anpassa användarnamnsfargen",
|
||||
"chooseSolid": "Välj en enfärgad färg:",
|
||||
"customColor": "Anpassad färg:",
|
||||
"preview": "Förhandsvisning:",
|
||||
"previewUsername": "Användarnamn",
|
||||
"apply": "Använd färg"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"title": "INSTÄLLNINGAR",
|
||||
"java": "Java Runtime",
|
||||
"useCustomJava": "Använd anpassad Java-sökväg",
|
||||
"javaDescription": "Ersätt den medföljande Java-installationen med din egen",
|
||||
"javaPath": "Java-körbar fil-sökväg",
|
||||
"javaPathPlaceholder": "Välj Java-sökväg...",
|
||||
"javaBrowse": "Bläddra",
|
||||
"javaHint": "Välj Java-installationsmappen (stöder Windows, Mac, Linux)",
|
||||
"discord": "Discord-integration",
|
||||
"enableRPC": "Aktivera Discord Rich Presence",
|
||||
"discordDescription": "Visa din launcher-aktivitet på Discord",
|
||||
"game": "Spelalternativ",
|
||||
"playerName": "Spelarnamn",
|
||||
"playerNamePlaceholder": "Ange spelarnamn",
|
||||
"playerNameHint": "Detta namn kommer att användas i spelet (1-16 tecken)",
|
||||
"openGameLocation": "Öppna spelplats",
|
||||
"openGameLocationDesc": "Öppna spelinstallationsmappen",
|
||||
"account": "Spelare UUID-hantering",
|
||||
"currentUUID": "Nuvarande UUID",
|
||||
"uuidPlaceholder": "Laddar UUID...",
|
||||
"copyUUID": "Kopiera UUID",
|
||||
"regenerateUUID": "Återskapa UUID",
|
||||
"uuidHint": "Din unika spelaridentifierare för detta användarnamn",
|
||||
"manageUUIDs": "Hantera alla UUID:er",
|
||||
"manageUUIDsDesc": "Visa och hantera alla spelare-UUID:er",
|
||||
"language": "Språk",
|
||||
"selectLanguage": "Välj språk",
|
||||
"repairGame": "Reparera spel",
|
||||
"reinstallGame": "Ominstallera spelfiler (bevarar data)",
|
||||
"gpuPreference": "GPU-preferens",
|
||||
"gpuHint": "Välj din föredragna GPU (Linux: påverkar DRI_PRIME)",
|
||||
"gpuAuto": "Auto",
|
||||
"gpuIntegrated": "Integrerad",
|
||||
"gpuDedicated": "Dedikerad",
|
||||
"logs": "SYSTEMLOGGAR",
|
||||
"logsCopy": "Kopiera",
|
||||
"logsRefresh": "Uppdatera",
|
||||
"logsFolder": "Öppna mapp",
|
||||
"logsLoading": "Laddar loggar...",
|
||||
"closeLauncher": "Launcher-beteende",
|
||||
"closeOnStart": "Stäng launcher vid spelstart",
|
||||
"closeOnStartDescription": "Stäng automatiskt launcher efter att Hytale har startats",
|
||||
"hwAccel": "Hårdvaruacceleration",
|
||||
"hwAccelDescription": "Aktivera hårdvaruacceleration för launchern",
|
||||
"gameBranch": "Spelgren",
|
||||
"branchRelease": "Release",
|
||||
"branchPreRelease": "Pre-Release",
|
||||
"branchHint": "Växla mellan stabil release- och experimentell pre-release-version",
|
||||
"branchWarning": "Att byta gren kommer att ladda ner och installera en annan spelversion",
|
||||
"branchSwitching": "Byter till {branch}...",
|
||||
"branchSwitched": "Bytte framgångsrikt till {branch}!",
|
||||
"installRequired": "Installation krävs",
|
||||
"branchInstallConfirm": "Spelet kommer att installeras för {branch}-grenen. Fortsätt?"
|
||||
},
|
||||
"uuid": {
|
||||
"modalTitle": "UUID-hantering",
|
||||
"currentUserUUID": "Nuvarande användar-UUID",
|
||||
"allPlayerUUIDs": "Alla spelare-UUID:er",
|
||||
"generateNew": "Generera ny UUID",
|
||||
"loadingUUIDs": "Laddar UUID:er...",
|
||||
"setCustomUUID": "Ange anpassad UUID",
|
||||
"customPlaceholder": "Ange anpassad UUID (format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)",
|
||||
"setUUID": "Ange UUID",
|
||||
"warning": "Varning: Att ange en anpassad UUID kommer att ändra din nuvarande spelaridentitet",
|
||||
"copyTooltip": "Kopiera UUID",
|
||||
"regenerateTooltip": "Generera ny UUID"
|
||||
},
|
||||
"profiles": {
|
||||
"modalTitle": "Hantera profiler",
|
||||
"newProfilePlaceholder": "Nytt profilnamn",
|
||||
"createProfile": "Skapa profil"
|
||||
},
|
||||
"discord": {
|
||||
"notificationText": "Gå med i vår Discord-gemenskap!",
|
||||
"joinButton": "Gå med i Discord"
|
||||
},
|
||||
"common": {
|
||||
"confirm": "Bekräfta",
|
||||
"cancel": "Avbryt",
|
||||
"save": "Spara",
|
||||
"close": "Stäng",
|
||||
"delete": "Ta bort",
|
||||
"edit": "Redigera",
|
||||
"loading": "Laddar...",
|
||||
"apply": "Verkställ",
|
||||
"install": "Installera"
|
||||
},
|
||||
"notifications": {
|
||||
"gameDataNotFound": "Fel: Speldata hittades inte",
|
||||
"gameUpdatedSuccess": "Spelet uppdaterades framgångsrikt! 🎉",
|
||||
"updateFailed": "Uppdatering misslyckades: {error}",
|
||||
"updateError": "Uppdateringsfel: {error}",
|
||||
"discordEnabled": "Discord Rich Presence aktiverad",
|
||||
"discordDisabled": "Discord Rich Presence inaktiverad",
|
||||
"discordSaveFailed": "Misslyckades med att spara Discord-inställning",
|
||||
"playerNameRequired": "Ange ett giltigt spelarnamn",
|
||||
"playerNameSaved": "Spelarnamn sparat framgångsrikt",
|
||||
"playerNameSaveFailed": "Misslyckades med att spara spelarnamn",
|
||||
"uuidCopied": "UUID kopierad till urklipp!",
|
||||
"uuidCopyFailed": "Misslyckades med att kopiera UUID",
|
||||
"uuidRegenNotAvailable": "UUID-återgenerering ej tillgänglig",
|
||||
"uuidRegenFailed": "Misslyckades med att återgenerera UUID",
|
||||
"uuidGenerated": "Ny UUID genererad framgångsrikt!",
|
||||
"uuidGeneratedShort": "Ny UUID genererad!",
|
||||
"uuidGenerateFailed": "Misslyckades med att generera ny UUID",
|
||||
"uuidRequired": "Ange en UUID",
|
||||
"uuidInvalidFormat": "Ogiltigt UUID-format",
|
||||
"uuidSetFailed": "Misslyckades med att ange anpassad UUID",
|
||||
"uuidSetSuccess": "Anpassad UUID angiven framgångsrikt!",
|
||||
"uuidDeleteFailed": "Misslyckades med att ta bort UUID",
|
||||
"uuidDeleteSuccess": "UUID borttagen framgångsrikt!",
|
||||
"modsDownloading": "Laddar ner {name}...",
|
||||
"modsTogglingMod": "Växlar modd...",
|
||||
"modsDeletingMod": "Tar bort modd...",
|
||||
"modsLoadingMods": "Laddar moddar från CurseForge...",
|
||||
"modsInstalledSuccess": "{name} installerad framgångsrikt! 🎉",
|
||||
"modsDeletedSuccess": "{name} borttagen framgångsrikt",
|
||||
"modsDownloadFailed": "Misslyckades med att ladda ner modd: {error}",
|
||||
"modsToggleFailed": "Misslyckades med att växla modd: {error}",
|
||||
"modsDeleteFailed": "Misslyckades med att ta bort modd: {error}",
|
||||
"modsModNotFound": "Moddinformation hittades inte",
|
||||
"hwAccelSaved": "Hårdvaruaccelerationsinställning sparad",
|
||||
"hwAccelSaveFailed": "Misslyckades med att spara hårdvaruaccelerationsinställning",
|
||||
"javaPathCopied": "Java-sökväg kopierad till urklipp!",
|
||||
"javaPathCopyFailed": "Misslyckades med att kopiera Java-sökväg",
|
||||
"javaPathSaved": "Java-sökväg sparad framgångsrikt!",
|
||||
"javaPathSaveFailed": "Misslyckades med att spara Java-sökväg",
|
||||
"javaPathInvalid": "Ogiltig Java-sökväg",
|
||||
"javaPathReset": "Java-sökväg återställd till standardvärden",
|
||||
"gameLocationError": "Kunde inte öppna spelplats",
|
||||
"launcherRestartRequired": "Launcher-omstart krävs för att tillämpa ändringar",
|
||||
"gameRepairConfirm": "Är du säker på att du vill reparera spelet? Detta kommer att ominstallera alla spelfiler.",
|
||||
"gameRepairInProgress": "Reparerar spel...",
|
||||
"gameRepairSuccess": "Spel reparerat framgångsrikt!",
|
||||
"gameRepairFailed": "Spelreparation misslyckades: {error}",
|
||||
"invalidUsername": "Ogiltigt användarnamn",
|
||||
"usernameInUse": "Användarnamn upptaget",
|
||||
"chatJoinSuccess": "Du har gått med i chatten!",
|
||||
"chatJoinFailed": "Misslyckades med att gå med i chatten",
|
||||
"messageTooLong": "Meddelande för långt",
|
||||
"messageSent": "Meddelande skickat",
|
||||
"messageSendFailed": "Misslyckades med att skicka meddelande",
|
||||
"colorUpdated": "Färg uppdaterad!",
|
||||
"colorUpdateFailed": "Misslyckades med att uppdatera färg",
|
||||
"profileCreated": "Profil skapad framgångsrikt!",
|
||||
"profileCreateFailed": "Misslyckades med att skapa profil",
|
||||
"profileDeleted": "Profil borttagen",
|
||||
"profileDeleteFailed": "Misslyckades med att ta bort profil",
|
||||
"profileSwitched": "Bytte profil till: {name}",
|
||||
"profileSwitchFailed": "Profilbyte misslyckades",
|
||||
"invalidProfileName": "Ogiltigt profilnamn",
|
||||
"profileNameExists": "En profil med detta namn finns redan",
|
||||
"noInternet": "Ingen internetanslutning",
|
||||
"checkInternetConnection": "Kontrollera din internetanslutning",
|
||||
"serverError": "Serverfel. Försök igen senare.",
|
||||
"unknownError": "Ett okänt fel inträffade"
|
||||
},
|
||||
"confirm": {
|
||||
"defaultTitle": "Bekräfta åtgärd",
|
||||
"regenerateUuidTitle": "Generera ny UUID",
|
||||
"regenerateUuidMessage": "Är du säker på att du vill generera en ny UUID? Detta kommer att ändra din spelaridentitet.",
|
||||
"regenerateUuidButton": "Generera",
|
||||
"setCustomUuidTitle": "Ange anpassad UUID",
|
||||
"setCustomUuidMessage": "Är du säker på att du vill ange denna anpassade UUID? Detta kommer att ändra din spelaridentitet.",
|
||||
"setCustomUuidButton": "Ange UUID",
|
||||
"deleteUuidTitle": "Ta bort UUID",
|
||||
"deleteUuidMessage": "Är du säker på att du vill ta bort UUID:n för \"{username}\"? Denna åtgärd kan inte ångras.",
|
||||
"deleteUuidButton": "Ta bort",
|
||||
"uninstallGameTitle": "Avinstallera spel",
|
||||
"uninstallGameMessage": "Är du säker på att du vill avinstallera Hytale? Alla spelfiler kommer att tas bort.",
|
||||
"uninstallGameButton": "Avinstallera"
|
||||
},
|
||||
"progress": {
|
||||
"initializing": "Initierar...",
|
||||
"downloading": "Laddar ner...",
|
||||
"installing": "Installerar...",
|
||||
"extracting": "Extraherar...",
|
||||
"verifying": "Verifierar...",
|
||||
"switchingProfile": "Byter profil...",
|
||||
"profileSwitched": "Profil bytt!",
|
||||
"startingGame": "Startar spel...",
|
||||
"launching": "STARTAR...",
|
||||
"uninstallingGame": "Avinstallerar spel...",
|
||||
"gameUninstalled": "Spel avinstallerat framgångsrikt!",
|
||||
"uninstallFailed": "Avinstallation misslyckades: {error}",
|
||||
"startingUpdate": "Startar obligatorisk speluppdatering...",
|
||||
"installationComplete": "Installation slutförd framgångsrikt!",
|
||||
"installationFailed": "Installation misslyckades: {error}",
|
||||
"installingGameFiles": "Installerar spelfiler...",
|
||||
"installComplete": "Installation slutförd!"
|
||||
}
|
||||
}
|
||||
18
README.md
18
README.md
@@ -7,6 +7,7 @@
|
||||
<p><small>An unofficial cross-platform launcher for Hytale with automatic updates and multiplayer support (all OS supported)</small></p>
|
||||
</header>
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
@@ -160,10 +161,13 @@
|
||||
<p id="fn2"><sup>Note 2</sup> Using Discrete/Dedicated GPU (dGPU) must have 8 GB RAM minimum.</p>
|
||||
<p id="fn3"><sup>Note 3</sup> Using Integrated GPU (dGPU) must have 12 GB RAM minimum.</p>
|
||||
|
||||
> [!WARNING]
|
||||
> Our launcher has **not yet** supported Offline Mode (playing Hytale without internet).
|
||||
> We will surely add the feature as soon as possible. Kindly wait for the update.
|
||||
|
||||
---
|
||||
|
||||
### 🪟 Windows Prequisites
|
||||
* **
|
||||
* **Java JDK 25:**
|
||||
* [Oracle](https://www.oracle.com/java/technologies/downloads/#jdk25-windows), **no** support for Windows ARM64 in both version 25 and 21.
|
||||
* [Adoptium](https://adoptium.net/temurin/releases/?version=25), has Windows ARM64 support in version 21 only.
|
||||
@@ -178,8 +182,8 @@
|
||||
> [!WARNING]
|
||||
> Ubuntu-based Distro like ZorinOS or Pop!_OS or Linux Mint would encounter issues due to UbuntuLTS environment, [check this Discord post](https://discord.com/channels/1462260103951421493/1463662398501027973).
|
||||
|
||||
* Make sure you have already installed newest **GPU driver**, consult your distro docs or wiki.
|
||||
|
||||
* Make sure you have already installed newest **GPU driver** especially proprietary NVIDIA, consult your distro docs or wiki.
|
||||
* Also make sure that your GPU can be connected to EGL, try checking it first (again, consult your distro docs or wiki) before installing Hytale game via our launcher.
|
||||
* Install `libpng` package to avoid SDL3_Image error:
|
||||
* `libpng16-16 libpng-dev` for Ubuntu/Debian-based Distro
|
||||
* `libpng libpng-devel` for Fedora/RHEL-based Distro
|
||||
@@ -197,6 +201,10 @@
|
||||
* Click **More info**.
|
||||
* Click **Run anyway**.
|
||||
4. **Launch:** Once installed, you can launch the app directly from your Desktop or the Start menu.
|
||||
5. **Whitelist in Windows Firewall to Avoid "Server Failed to Boot" Error** [#192](https://github.com/amiayweb/Hytale-F2P/issues/192#issuecomment-3803042908)
|
||||
* Open the Windows Start Menu and search for `Allow an app through Windows Firewall`
|
||||
* Click "Change settings" (you may need Admin privileges) and Locate `HytaleClient.exe` in the list.
|
||||
* Ensure both the Private and Public checkboxes are checked. Click OK to save.
|
||||
|
||||
---
|
||||
|
||||
@@ -286,7 +294,7 @@ The `.zip` version is useful for users who prefer a portable installation or nee
|
||||
## Dedicated Server
|
||||
|
||||
> [!NOTE]
|
||||
> If you have already `HytaleServer.jar` in `HytaleF2P/{release/pre-release}/package/game/latest/Server`, you can use it to host local dedicated server.
|
||||
> If you already have the patched `HytaleServer.jar` in `HytaleF2P/{release/pre-release}/package/game/latest/Server`, you can use it to host local dedicated server.
|
||||
|
||||
> [!TIP]
|
||||
> Use services like Playit.gg, Tailscale, Radmin VPN to share UDP connection if setting up router as an admin is not possible.
|
||||
@@ -295,7 +303,7 @@ The `.zip` version is useful for users who prefer a portable installation or nee
|
||||
> `Hytale-F2P-Server.rar` file is needed to set up a server on non-playing hardware (such as VPS/server hosting).
|
||||
|
||||
> [!IMPORTANT]
|
||||
> See detailed information of setting up a server here: [SERVER.md](SERVER.md)
|
||||
> See detailed information of setting up a server here: [SERVER.md](SERVER.md). Download the latest patched JAR, the patched RAR, or the SH/BAT scripts from channel `#open-public-server` in our Discord Server.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -285,31 +285,54 @@ exec "$REAL_JAVA" "\${ARGS[@]}"
|
||||
const gpuEnv = setupGpuEnvironment(gpuPreference);
|
||||
Object.assign(env, gpuEnv);
|
||||
|
||||
// Linux memory allocator fixes for "free(): invalid pointer" crashes
|
||||
// on Steam Deck (glibc 2.41) and Ubuntu LTS
|
||||
if (process.platform === 'linux') {
|
||||
// Option 1: Disable glibc heap validation
|
||||
env.MALLOC_CHECK_ = '0';
|
||||
// Linux: Replace bundled libzstd.so with system version to fix glibc 2.41+ crash
|
||||
// The bundled libzstd causes "free(): invalid pointer" on Steam Deck / Ubuntu LTS
|
||||
if (process.platform === 'linux' && process.env.HYTALE_NO_LIBZSTD_FIX !== '1') {
|
||||
const clientDir = path.dirname(clientPath);
|
||||
const bundledLibzstd = path.join(clientDir, 'libzstd.so');
|
||||
const backupLibzstd = path.join(clientDir, 'libzstd.so.bundled');
|
||||
|
||||
// Option 2: Try to use jemalloc if available (more robust allocator)
|
||||
// User can set HYTALE_USE_JEMALLOC=1 to enable
|
||||
if (process.env.HYTALE_USE_JEMALLOC === '1') {
|
||||
const jemalloc = require('fs').existsSync('/usr/lib/libjemalloc.so.2')
|
||||
? '/usr/lib/libjemalloc.so.2'
|
||||
: require('fs').existsSync('/usr/lib/x86_64-linux-gnu/libjemalloc.so.2')
|
||||
? '/usr/lib/x86_64-linux-gnu/libjemalloc.so.2'
|
||||
: null;
|
||||
// Common system libzstd paths
|
||||
const systemLibzstdPaths = [
|
||||
'/usr/lib/libzstd.so.1', // Arch Linux, Steam Deck
|
||||
'/usr/lib/x86_64-linux-gnu/libzstd.so.1', // Debian/Ubuntu
|
||||
'/usr/lib64/libzstd.so.1' // Fedora/RHEL
|
||||
];
|
||||
|
||||
if (jemalloc) {
|
||||
env.LD_PRELOAD = jemalloc + (env.LD_PRELOAD ? ':' + env.LD_PRELOAD : '');
|
||||
console.log(`Linux: Using jemalloc allocator (${jemalloc})`);
|
||||
} else {
|
||||
console.log('Linux: jemalloc not found, using glibc with MALLOC_CHECK_=0');
|
||||
}
|
||||
} else {
|
||||
console.log('Linux: Using glibc with MALLOC_CHECK_=0 (set HYTALE_USE_JEMALLOC=1 to try jemalloc)');
|
||||
}
|
||||
}
|
||||
let systemLibzstd = null;
|
||||
for (const p of systemLibzstdPaths) {
|
||||
if (fs.existsSync(p)) {
|
||||
systemLibzstd = p;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (systemLibzstd && fs.existsSync(bundledLibzstd)) {
|
||||
try {
|
||||
const stats = fs.lstatSync(bundledLibzstd);
|
||||
|
||||
// Only replace if it's not already a symlink to system version
|
||||
if (!stats.isSymbolicLink()) {
|
||||
// Backup bundled version
|
||||
if (!fs.existsSync(backupLibzstd)) {
|
||||
fs.renameSync(bundledLibzstd, backupLibzstd);
|
||||
console.log(`Linux: Backed up bundled libzstd.so`);
|
||||
} else {
|
||||
fs.unlinkSync(bundledLibzstd);
|
||||
}
|
||||
|
||||
// Create symlink to system version
|
||||
fs.symlinkSync(systemLibzstd, bundledLibzstd);
|
||||
console.log(`Linux: Linked libzstd.so to system version (${systemLibzstd}) for glibc 2.41+ compatibility`);
|
||||
} else {
|
||||
const linkTarget = fs.readlinkSync(bundledLibzstd);
|
||||
console.log(`Linux: libzstd.so already linked to ${linkTarget}`);
|
||||
}
|
||||
} catch (libzstdError) {
|
||||
console.warn(`Linux: Could not replace libzstd.so: ${libzstdError.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
let spawnOptions = {
|
||||
|
||||
@@ -147,9 +147,8 @@ class ClientPatcher {
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace bytes in buffer with null-padding for shorter replacements
|
||||
* When new pattern is shorter than old, pads with 0x00 to prevent leftover bytes
|
||||
* that can cause memory corruption (free(): invalid pointer) on some systems
|
||||
* Replace bytes in buffer - only overwrites the length of new bytes
|
||||
* Prevents offset corruption by not expanding the replacement
|
||||
*/
|
||||
replaceBytes(buffer, oldBytes, newBytes) {
|
||||
let count = 0;
|
||||
@@ -163,20 +162,7 @@ class ClientPatcher {
|
||||
const positions = this.findAllOccurrences(result, oldBytes);
|
||||
|
||||
for (const pos of positions) {
|
||||
// Log offset and surrounding bytes for debugging
|
||||
const before = result.slice(Math.max(0, pos - 8), pos);
|
||||
const after = result.slice(pos + oldBytes.length, Math.min(result.length, pos + oldBytes.length + 8));
|
||||
console.log(` Patching at offset 0x${pos.toString(16)} (${pos})`);
|
||||
console.log(` Before: ${before.toString('hex')}`);
|
||||
console.log(` Old pattern: ${oldBytes.slice(0, 20).toString('hex')}${oldBytes.length > 20 ? '...' : ''}`);
|
||||
console.log(` After: ${after.toString('hex')}`);
|
||||
|
||||
// First fill the entire old pattern region with zeros
|
||||
// This prevents leftover bytes from causing memory corruption
|
||||
if (newBytes.length < oldBytes.length) {
|
||||
result.fill(0x00, pos, pos + oldBytes.length);
|
||||
}
|
||||
// Then write the new bytes
|
||||
// Only overwrite the length of the new bytes
|
||||
newBytes.copy(result, pos);
|
||||
count++;
|
||||
}
|
||||
@@ -184,65 +170,6 @@ class ClientPatcher {
|
||||
return { buffer: result, count };
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace bytes with skip/limit control (for debugging)
|
||||
* HYTALE_PATCH_SKIP: comma-separated indices to skip (e.g., "0,2" skips 1st and 3rd)
|
||||
* HYTALE_PATCH_LIMIT: max number of patches to apply
|
||||
*/
|
||||
replaceBytesLimited(buffer, oldBytes, newBytes, limit) {
|
||||
let count = 0;
|
||||
const result = Buffer.from(buffer);
|
||||
|
||||
if (newBytes.length > oldBytes.length) {
|
||||
console.warn(` Warning: New pattern (${newBytes.length}) longer than old (${oldBytes.length}), skipping`);
|
||||
return { buffer: result, count: 0 };
|
||||
}
|
||||
|
||||
// Parse skip list from env
|
||||
const skipIndices = (process.env.HYTALE_PATCH_SKIP || '')
|
||||
.split(',')
|
||||
.filter(s => s.trim())
|
||||
.map(s => parseInt(s.trim(), 10));
|
||||
if (skipIndices.length > 0) {
|
||||
console.log(` Skip indices: ${skipIndices.join(', ')}`);
|
||||
}
|
||||
|
||||
const positions = this.findAllOccurrences(result, oldBytes);
|
||||
|
||||
let patchedCount = 0;
|
||||
for (let i = 0; i < positions.length; i++) {
|
||||
const pos = positions[i];
|
||||
|
||||
// Log offset and surrounding bytes for debugging
|
||||
const before = result.slice(Math.max(0, pos - 8), pos);
|
||||
const after = result.slice(pos + oldBytes.length, Math.min(result.length, pos + oldBytes.length + 8));
|
||||
|
||||
if (skipIndices.includes(i)) {
|
||||
console.log(` [${i}] Skipping offset 0x${pos.toString(16)} (in skip list)`);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (patchedCount >= limit) {
|
||||
console.log(` [${i}] Skipping offset 0x${pos.toString(16)} (limit reached)`);
|
||||
continue;
|
||||
}
|
||||
|
||||
console.log(` [${i}] Patching at offset 0x${pos.toString(16)} (${pos})`);
|
||||
console.log(` Before: ${before.toString('hex')}`);
|
||||
console.log(` Old pattern: ${oldBytes.slice(0, 20).toString('hex')}${oldBytes.length > 20 ? '...' : ''}`);
|
||||
console.log(` After: ${after.toString('hex')}`);
|
||||
|
||||
if (newBytes.length < oldBytes.length) {
|
||||
result.fill(0x00, pos, pos + oldBytes.length);
|
||||
}
|
||||
newBytes.copy(result, pos);
|
||||
patchedCount++;
|
||||
count++;
|
||||
}
|
||||
|
||||
return { buffer: result, count };
|
||||
}
|
||||
|
||||
/**
|
||||
* UTF-8 domain replacement for Java JAR files.
|
||||
* Java stores strings in UTF-8 format in the constant pool.
|
||||
@@ -270,19 +197,11 @@ class ClientPatcher {
|
||||
* .NET AOT stores some strings in various formats:
|
||||
* - Standard UTF-16LE (each char is 2 bytes with \x00 high byte)
|
||||
* - Length-prefixed where last char may have metadata byte instead of \x00
|
||||
*
|
||||
* IMPORTANT: newDomain must be same length or shorter than oldDomain to avoid buffer overflow
|
||||
*/
|
||||
findAndReplaceDomainSmart(data, oldDomain, newDomain) {
|
||||
let count = 0;
|
||||
const result = Buffer.from(data);
|
||||
|
||||
// Safety check: new domain must not be longer than old
|
||||
if (newDomain.length > oldDomain.length) {
|
||||
console.warn(` Warning: New domain (${newDomain.length} chars) longer than old (${oldDomain.length} chars), skipping smart replacement`);
|
||||
return { buffer: result, count: 0 };
|
||||
}
|
||||
|
||||
const oldUtf16NoLast = this.stringToUtf16LE(oldDomain.slice(0, -1));
|
||||
const newUtf16NoLast = this.stringToUtf16LE(newDomain.slice(0, -1));
|
||||
|
||||
@@ -298,11 +217,6 @@ class ClientPatcher {
|
||||
const lastCharFirstByte = result[lastCharPos];
|
||||
|
||||
if (lastCharFirstByte === oldLastCharByte) {
|
||||
// Zero-fill the old region first if new is shorter
|
||||
if (newUtf16NoLast.length < oldUtf16NoLast.length) {
|
||||
result.fill(0x00, pos, pos + oldUtf16NoLast.length);
|
||||
}
|
||||
|
||||
newUtf16NoLast.copy(result, pos);
|
||||
|
||||
result[lastCharPos] = newLastCharByte;
|
||||
@@ -325,10 +239,6 @@ class ClientPatcher {
|
||||
/**
|
||||
* Apply all domain patches using length-prefixed format
|
||||
* This is the main patching method for variable-length domains
|
||||
*
|
||||
* Debug env vars:
|
||||
* HYTALE_SKIP_SENTRY_PATCH=1 - Skip sentry URL patch (biggest size change)
|
||||
* HYTALE_SKIP_SUBDOMAIN_PATCH=1 - Skip subdomain prefix patches
|
||||
*/
|
||||
applyDomainPatches(data, domain, protocol = 'https://') {
|
||||
let result = Buffer.from(data);
|
||||
@@ -337,79 +247,50 @@ class ClientPatcher {
|
||||
|
||||
console.log(` Patching strategy: ${strategy.description}`);
|
||||
|
||||
// 1. Patch telemetry/sentry URL (skip if debugging)
|
||||
if (process.env.HYTALE_SKIP_SENTRY_PATCH === '1') {
|
||||
console.log(` Skipping sentry patch (HYTALE_SKIP_SENTRY_PATCH=1)`);
|
||||
} else {
|
||||
const oldSentry = 'https://ca900df42fcf57d4dd8401a86ddd7da2@sentry.hytale.com/2';
|
||||
const newSentry = `${protocol}t@${domain}/2`;
|
||||
// 1. Patch telemetry/sentry URL
|
||||
const oldSentry = 'https://ca900df42fcf57d4dd8401a86ddd7da2@sentry.hytale.com/2';
|
||||
const newSentry = `${protocol}t@${domain}/2`;
|
||||
|
||||
console.log(` Patching sentry: ${oldSentry.slice(0, 30)}... -> ${newSentry}`);
|
||||
const sentryResult = this.replaceBytes(
|
||||
result,
|
||||
this.stringToLengthPrefixed(oldSentry),
|
||||
this.stringToLengthPrefixed(newSentry)
|
||||
);
|
||||
result = sentryResult.buffer;
|
||||
if (sentryResult.count > 0) {
|
||||
console.log(` Replaced ${sentryResult.count} sentry occurrence(s)`);
|
||||
totalCount += sentryResult.count;
|
||||
}
|
||||
console.log(` Patching sentry: ${oldSentry.slice(0, 30)}... -> ${newSentry}`);
|
||||
const sentryResult = this.replaceBytes(
|
||||
result,
|
||||
this.stringToLengthPrefixed(oldSentry),
|
||||
this.stringToLengthPrefixed(newSentry)
|
||||
);
|
||||
result = sentryResult.buffer;
|
||||
if (sentryResult.count > 0) {
|
||||
console.log(` Replaced ${sentryResult.count} sentry occurrence(s)`);
|
||||
totalCount += sentryResult.count;
|
||||
}
|
||||
|
||||
// 2. Patch main domain (hytale.com -> mainDomain)
|
||||
// Try length-prefixed format first, then fall back to pure UTF-16LE
|
||||
console.log(` Patching domain: ${ORIGINAL_DOMAIN} -> ${strategy.mainDomain}`);
|
||||
|
||||
// Check for HYTALE_PATCH_MODE env var to test different formats
|
||||
const patchMode = process.env.HYTALE_PATCH_MODE || 'length-prefixed';
|
||||
console.log(` Patch mode: ${patchMode}`);
|
||||
|
||||
let domainResult;
|
||||
if (patchMode === 'utf16le') {
|
||||
// Pure UTF-16LE replacement (no length prefix)
|
||||
const oldUtf16 = this.stringToUtf16LE(ORIGINAL_DOMAIN);
|
||||
const newUtf16 = this.stringToUtf16LE(strategy.mainDomain);
|
||||
console.log(` UTF-16LE: old=${oldUtf16.length} bytes, new=${newUtf16.length} bytes`);
|
||||
|
||||
// HYTALE_PATCH_LIMIT: only patch first N occurrences (for debugging)
|
||||
const patchLimit = parseInt(process.env.HYTALE_PATCH_LIMIT || '999', 10);
|
||||
console.log(` Patch limit: ${patchLimit}`);
|
||||
domainResult = this.replaceBytesLimited(result, oldUtf16, newUtf16, patchLimit);
|
||||
} else {
|
||||
// Length-prefixed format (default)
|
||||
domainResult = this.replaceBytes(
|
||||
result,
|
||||
this.stringToLengthPrefixed(ORIGINAL_DOMAIN),
|
||||
this.stringToLengthPrefixed(strategy.mainDomain)
|
||||
);
|
||||
}
|
||||
|
||||
const domainResult = this.replaceBytes(
|
||||
result,
|
||||
this.stringToLengthPrefixed(ORIGINAL_DOMAIN),
|
||||
this.stringToLengthPrefixed(strategy.mainDomain)
|
||||
);
|
||||
result = domainResult.buffer;
|
||||
if (domainResult.count > 0) {
|
||||
console.log(` Replaced ${domainResult.count} domain occurrence(s)`);
|
||||
totalCount += domainResult.count;
|
||||
}
|
||||
|
||||
// 3. Patch subdomain prefixes (skip if debugging)
|
||||
if (process.env.HYTALE_SKIP_SUBDOMAIN_PATCH === '1') {
|
||||
console.log(` Skipping subdomain patches (HYTALE_SKIP_SUBDOMAIN_PATCH=1)`);
|
||||
} else {
|
||||
const subdomains = ['https://tools.', 'https://sessions.', 'https://account-data.', 'https://telemetry.'];
|
||||
const newSubdomainPrefix = protocol + strategy.subdomainPrefix;
|
||||
// 3. Patch subdomain prefixes
|
||||
const subdomains = ['https://tools.', 'https://sessions.', 'https://account-data.', 'https://telemetry.'];
|
||||
const newSubdomainPrefix = protocol + strategy.subdomainPrefix;
|
||||
|
||||
for (const sub of subdomains) {
|
||||
console.log(` Patching subdomain: ${sub} -> ${newSubdomainPrefix}`);
|
||||
const subResult = this.replaceBytes(
|
||||
result,
|
||||
this.stringToLengthPrefixed(sub),
|
||||
this.stringToLengthPrefixed(newSubdomainPrefix)
|
||||
);
|
||||
result = subResult.buffer;
|
||||
if (subResult.count > 0) {
|
||||
console.log(` Replaced ${subResult.count} occurrence(s)`);
|
||||
totalCount += subResult.count;
|
||||
}
|
||||
for (const sub of subdomains) {
|
||||
console.log(` Patching subdomain: ${sub} -> ${newSubdomainPrefix}`);
|
||||
const subResult = this.replaceBytes(
|
||||
result,
|
||||
this.stringToLengthPrefixed(sub),
|
||||
this.stringToLengthPrefixed(newSubdomainPrefix)
|
||||
);
|
||||
result = subResult.buffer;
|
||||
if (subResult.count > 0) {
|
||||
console.log(` Replaced ${subResult.count} occurrence(s)`);
|
||||
totalCount += subResult.count;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -417,13 +298,38 @@ class ClientPatcher {
|
||||
}
|
||||
|
||||
/**
|
||||
* Patch Discord invite URLs - DISABLED
|
||||
* Was causing buffer overflow crashes on Steam Deck/Ubuntu LTS
|
||||
* The Discord URL in the game doesn't affect F2P functionality anyway
|
||||
* Patch Discord invite URLs from .gg/hytale to .gg/MHkEjepMQ7
|
||||
*/
|
||||
patchDiscordUrl(data) {
|
||||
// Disabled - no practical effect and was causing memory corruption
|
||||
return { buffer: Buffer.from(data), count: 0 };
|
||||
let count = 0;
|
||||
const result = Buffer.from(data);
|
||||
|
||||
const oldUrl = '.gg/hytale';
|
||||
const newUrl = '.gg/MHkEjepMQ7';
|
||||
|
||||
// Try length-prefixed format first
|
||||
const lpResult = this.replaceBytes(
|
||||
result,
|
||||
this.stringToLengthPrefixed(oldUrl),
|
||||
this.stringToLengthPrefixed(newUrl)
|
||||
);
|
||||
|
||||
if (lpResult.count > 0) {
|
||||
return { buffer: lpResult.buffer, count: lpResult.count };
|
||||
}
|
||||
|
||||
// Fallback to UTF-16LE
|
||||
const oldUtf16 = this.stringToUtf16LE(oldUrl);
|
||||
const newUtf16 = this.stringToUtf16LE(newUrl);
|
||||
|
||||
const positions = this.findAllOccurrences(result, oldUtf16);
|
||||
|
||||
for (const pos of positions) {
|
||||
newUtf16.copy(result, pos);
|
||||
count++;
|
||||
}
|
||||
|
||||
return { buffer: result, count };
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -577,14 +483,6 @@ class ClientPatcher {
|
||||
progressCallback('Patching domain references...', 50);
|
||||
}
|
||||
|
||||
// HYTALE_NOOP_TEST: Just read and write binary without any changes
|
||||
if (process.env.HYTALE_NOOP_TEST === '1') {
|
||||
console.log('NOOP TEST: Writing binary without modifications...');
|
||||
fs.writeFileSync(clientPath, data);
|
||||
this.markAsPatched(clientPath);
|
||||
return { success: true, patchCount: 0, noop: true };
|
||||
}
|
||||
|
||||
console.log('Applying domain patches (length-prefixed format)...');
|
||||
const { buffer: patchedData, count } = this.applyDomainPatches(data, newDomain);
|
||||
|
||||
@@ -592,17 +490,6 @@ class ClientPatcher {
|
||||
const { buffer: finalData, count: discordCount } = this.patchDiscordUrl(patchedData);
|
||||
|
||||
if (count === 0 && discordCount === 0) {
|
||||
// Check if we're in debug mode with skip - don't fallback if intentionally skipping
|
||||
const hasSkipList = (process.env.HYTALE_PATCH_SKIP || '').trim().length > 0;
|
||||
const noLegacyFallback = process.env.HYTALE_NO_LEGACY_FALLBACK === '1';
|
||||
|
||||
if (hasSkipList || noLegacyFallback) {
|
||||
console.log('No occurrences patched (skip list active or legacy fallback disabled)');
|
||||
fs.writeFileSync(clientPath, patchedData);
|
||||
this.markAsPatched(clientPath);
|
||||
return { success: true, patchCount: 0, skipped: true };
|
||||
}
|
||||
|
||||
console.log('No occurrences found - trying legacy UTF-16LE format...');
|
||||
|
||||
// Fallback to legacy patching for older binary formats
|
||||
|
||||
123
docs/STEAMDECK_CRASH_INVESTIGATION.md
Normal file
123
docs/STEAMDECK_CRASH_INVESTIGATION.md
Normal file
@@ -0,0 +1,123 @@
|
||||
# Steam Deck / Ubuntu LTS Crash Investigation
|
||||
|
||||
## Status: SOLVED
|
||||
|
||||
**Last updated:** 2026-01-27
|
||||
|
||||
**Solution:** Replace bundled `libzstd.so` with system version.
|
||||
|
||||
---
|
||||
|
||||
## Problem Summary
|
||||
|
||||
The Hytale F2P launcher's client patcher causes crashes on Steam Deck and Ubuntu LTS with the error:
|
||||
```
|
||||
free(): invalid pointer
|
||||
```
|
||||
or
|
||||
```
|
||||
SIGSEGV (Segmentation fault)
|
||||
```
|
||||
|
||||
The crash occurs after successful authentication, specifically right after "Finished handling RequiredAssets".
|
||||
|
||||
**Affected Systems:**
|
||||
- Steam Deck (glibc 2.41)
|
||||
- Ubuntu LTS
|
||||
|
||||
**Working Systems:**
|
||||
- macOS
|
||||
- Windows
|
||||
- Older Arch Linux (glibc < 2.41)
|
||||
|
||||
---
|
||||
|
||||
## Root Cause
|
||||
|
||||
The **bundled `libzstd.so`** in the game client is incompatible with glibc 2.41's stricter heap validation. When the game decompresses assets using this library, it triggers heap corruption detected by glibc 2.41.
|
||||
|
||||
The crash occurs in `libzstd.so` during `free()` after "Finished handling RequiredAssets" (asset decompression).
|
||||
|
||||
---
|
||||
|
||||
## Solution
|
||||
|
||||
Replace the bundled `libzstd.so` with the system's `libzstd.so.1`.
|
||||
|
||||
### Automatic (Launcher)
|
||||
|
||||
The launcher automatically detects and replaces `libzstd.so` on Linux systems. No manual action needed.
|
||||
|
||||
### Manual
|
||||
|
||||
```bash
|
||||
cd ~/.hytalef2p/release/package/game/latest/Client
|
||||
|
||||
# Backup bundled version
|
||||
mv libzstd.so libzstd.so.bundled
|
||||
|
||||
# Link to system version
|
||||
# Steam Deck / Arch Linux:
|
||||
ln -s /usr/lib/libzstd.so.1 libzstd.so
|
||||
|
||||
# Debian / Ubuntu:
|
||||
ln -s /usr/lib/x86_64-linux-gnu/libzstd.so.1 libzstd.so
|
||||
|
||||
# Fedora / RHEL:
|
||||
ln -s /usr/lib64/libzstd.so.1 libzstd.so
|
||||
```
|
||||
|
||||
### Restore Original
|
||||
|
||||
```bash
|
||||
cd ~/.hytalef2p/release/package/game/latest/Client
|
||||
rm libzstd.so
|
||||
mv libzstd.so.bundled libzstd.so
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Why This Works
|
||||
|
||||
1. The bundled `libzstd.so` was likely compiled with different allocator settings or an older toolchain
|
||||
2. glibc 2.41 has stricter heap validation that catches invalid memory operations
|
||||
3. The system `libzstd.so.1` is compiled with the system's glibc and uses compatible memory allocation patterns
|
||||
4. By using the system library, we avoid the incompatibility entirely
|
||||
|
||||
---
|
||||
|
||||
## Previous Investigation (for reference)
|
||||
|
||||
### What Was Tried Before Finding Solution
|
||||
|
||||
| Approach | Result |
|
||||
|----------|--------|
|
||||
| jemalloc allocator | Worked ~30% of time, not stable |
|
||||
| GLIBC_TUNABLES | No effect |
|
||||
| taskset (CPU pinning) | Single core too slow |
|
||||
| nice/chrt (scheduling) | No effect |
|
||||
| Various patching approaches | All crashed |
|
||||
|
||||
### Key Insight
|
||||
|
||||
The crash was in `libzstd.so`, not in our patched code. The patching just changed timing enough to expose the libzstd incompatibility more frequently.
|
||||
|
||||
---
|
||||
|
||||
## GDB Stack Trace (Historical)
|
||||
|
||||
```
|
||||
#0 0x00007ffff7d3f5a4 in ?? () from /usr/lib/libc.so.6
|
||||
#1 raise () from /usr/lib/libc.so.6
|
||||
#2 abort () from /usr/lib/libc.so.6
|
||||
#3-#4 ?? () from /usr/lib/libc.so.6
|
||||
#5 free () from /usr/lib/libc.so.6
|
||||
#6 ?? () from libzstd.so <-- CRASH POINT (bundled library)
|
||||
#7-#24 HytaleClient code (asset decompression)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Branch
|
||||
|
||||
`fix/steamdeck-libzstd`
|
||||
65
docs/STEAMDECK_DEBUG_COMMANDS.md
Normal file
65
docs/STEAMDECK_DEBUG_COMMANDS.md
Normal file
@@ -0,0 +1,65 @@
|
||||
# Steam Deck / Linux Crash Fix
|
||||
|
||||
## SOLUTION: Use system libzstd
|
||||
|
||||
The crash is caused by the bundled `libzstd.so` being incompatible with glibc 2.41's stricter heap validation.
|
||||
|
||||
### Automatic Fix
|
||||
|
||||
The launcher automatically replaces `libzstd.so` with the system version. No manual action needed.
|
||||
|
||||
### Manual Fix
|
||||
|
||||
```bash
|
||||
cd ~/.hytalef2p/release/package/game/latest/Client
|
||||
|
||||
# Backup and replace
|
||||
mv libzstd.so libzstd.so.bundled
|
||||
ln -s /usr/lib/libzstd.so.1 libzstd.so
|
||||
```
|
||||
|
||||
### Restore Original
|
||||
|
||||
```bash
|
||||
cd ~/.hytalef2p/release/package/game/latest/Client
|
||||
rm libzstd.so
|
||||
mv libzstd.so.bundled libzstd.so
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Debug Commands (for troubleshooting)
|
||||
|
||||
### Check libzstd Status
|
||||
|
||||
```bash
|
||||
# Check if symlinked
|
||||
ls -la ~/.hytalef2p/release/package/game/latest/Client/libzstd.so
|
||||
|
||||
# Find system libzstd
|
||||
find /usr/lib -name "libzstd.so*"
|
||||
```
|
||||
|
||||
### Binary Validation
|
||||
|
||||
```bash
|
||||
file ~/.hytalef2p/release/package/game/latest/Client/HytaleClient
|
||||
ldd ~/.hytalef2p/release/package/game/latest/Client/HytaleClient
|
||||
```
|
||||
|
||||
### Restore Client Binary
|
||||
|
||||
```bash
|
||||
cd ~/.hytalef2p/release/package/game/latest/Client
|
||||
cp HytaleClient.original HytaleClient
|
||||
rm -f HytaleClient.patched_custom
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Environment Variables
|
||||
|
||||
| Variable | Description | Example |
|
||||
|----------|-------------|---------|
|
||||
| `HYTALE_AUTH_DOMAIN` | Custom auth domain | `auth.sanasol.ws` |
|
||||
| `HYTALE_NO_LIBZSTD_FIX` | Disable libzstd replacement | `1` |
|
||||
Reference in New Issue
Block a user