mirror of
https://github.com/Zagrios/bs-manager.git
synced 2026-07-03 14:08:25 +02:00
Compare commits
97 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f77ff2561d | |||
| e0a7d460be | |||
| d3ab80350e | |||
| 7af4c2ab45 | |||
| 0a64e71cf8 | |||
| 18adf064f3 | |||
| bda39e787c | |||
| b48b79e6e7 | |||
| 2e71be3413 | |||
| 434a6c2cae | |||
| e9594e7992 | |||
| 03f8c7e152 | |||
| 8a8977737f | |||
| b0b52f437f | |||
| cda8a8a3f3 | |||
| d09fbb090c | |||
| 3c1faf8259 | |||
| ce89532f3d | |||
| 4eb8c06706 | |||
| aed34e67a3 | |||
| a12cefe45a | |||
| 68c174c29b | |||
| 8eec1fe611 | |||
| 9e9fc14e2b | |||
| 57b7ee00a1 | |||
| 2c0b33f2e6 | |||
| 972794ddca | |||
| 5cfb2d901c | |||
| e6e49865da | |||
| 675ae26287 | |||
| c608806cfb | |||
| 6b5ccfe08b | |||
| cb755215b5 | |||
| a54f81de42 | |||
| 08a8349d22 | |||
| a3503a1985 | |||
| a45af5baee | |||
| 35d1623e8b | |||
| 44e4889913 | |||
| 6196396060 | |||
| 3efcd40cdc | |||
| d74ba720f1 | |||
| 2fef88c70e | |||
| 8776b036af | |||
| 5a9d2879e8 | |||
| 33632ee6b3 | |||
| 38f8103040 | |||
| 46db881278 | |||
| d6e611545e | |||
| da61b4e619 | |||
| 26699c1867 | |||
| 057f8a8ac9 | |||
| 4c8c0f8f62 | |||
| be5cc9c645 | |||
| b82b5e00cd | |||
| c4e742c201 | |||
| 65fb281a64 | |||
| 0f5ea88a27 | |||
| 6a7668c851 | |||
| 498067e2f2 | |||
| 0b28cd61e8 | |||
| a47f17e766 | |||
| 576a9ba325 | |||
| 60c410671a | |||
| 097eb709c3 | |||
| 17e96996bb | |||
| cfae1d9516 | |||
| c3a1a613ff | |||
| aec9dd0628 | |||
| c978d4689e | |||
| 431494da94 | |||
| a9cc281d27 | |||
| f73b1cd855 | |||
| 8e9eccd7c5 | |||
| 8dc7d89057 | |||
| 40a537b967 | |||
| 291cb0c254 | |||
| b42a944293 | |||
| 0e25184764 | |||
| 0c53679f2c | |||
| 8771ac2adc | |||
| c2e77f2520 | |||
| b32d07d2d6 | |||
| 78fb06fc63 | |||
| 198f3cb3af | |||
| f82e185ec2 | |||
| 4d16fb8aaa | |||
| fb3675955b | |||
| 229e0a66ce | |||
| 5146900168 | |||
| 3741558ffb | |||
| 0821f1867e | |||
| ef72ba642a | |||
| 1aadf353ca | |||
| 825d07fac0 | |||
| 56976487f2 | |||
| c8003a8ae8 |
@@ -26,7 +26,8 @@ body:
|
||||
id: replication
|
||||
attributes:
|
||||
label: Steps to Reproduce
|
||||
description: Provide a link to a live example, or an unambiguous set of steps to reproduce this bug. Include code to reproduce, if relevant.
|
||||
description: Provide a link to a live example, or an unambiguous set of steps to reproduce this bug.
|
||||
Include code to reproduce, if relevant.
|
||||
placeholder: |
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
@@ -50,5 +51,6 @@ body:
|
||||
id: additional-context
|
||||
attributes:
|
||||
label: Additional Context
|
||||
description: Any other context that you may share about the issue. You may add your log files here.
|
||||
description: Any other context that you may share about the issue such as logs.
|
||||
To get the logs, follow these steps `Open BSManager -> Go to settings -> Scroll down -> Open Logs`.
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
lank_issues_enabled: true
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Discord Support
|
||||
url: https://discord.gg/uSqbHVpKdV
|
||||
|
||||
@@ -803,6 +803,15 @@
|
||||
"ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/4451340304252928024",
|
||||
"ReleaseDate": "1732107748",
|
||||
"year": "2024",
|
||||
"recommended": true
|
||||
"recommended": true
|
||||
},
|
||||
{
|
||||
"BSVersion": "1.40.0",
|
||||
"BSManifest": "8455036253884215709",
|
||||
"OculusBinaryId": "8259984977434542",
|
||||
"ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/507314379973199787",
|
||||
"ReleaseImg": "https://clan.fastly.steamstatic.com/images/32055887/65df6b24c3b8a7f415080ac01394917cedabf9fc.png",
|
||||
"ReleaseDate": "1733846128",
|
||||
"year": "2024"
|
||||
}
|
||||
]
|
||||
]
|
||||
@@ -118,5 +118,11 @@
|
||||
{
|
||||
"username": "Reflected Chop",
|
||||
"type": "gold"
|
||||
},
|
||||
{
|
||||
"username": "Furiouspupa"
|
||||
},
|
||||
{
|
||||
"username": "Maximilian"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -8,6 +8,10 @@
|
||||
"maps": "Maps",
|
||||
"playlists": "Playlists",
|
||||
"models": "Modelle",
|
||||
"avatars": "Avatare",
|
||||
"sabers": "Säbel",
|
||||
"platforms": "Plattformen",
|
||||
"blocks": "Blöcke",
|
||||
"cancel": "Abbrechen",
|
||||
"delete": "Löschen",
|
||||
"accept": "Akzeptieren",
|
||||
@@ -19,7 +23,13 @@
|
||||
"choose-folder": "Ordner Wählen",
|
||||
"unknown": "Ein unbekannter Fehler ist aufgetreten ¯\\_(ツ)_/¯",
|
||||
"warning": "Achtung",
|
||||
"continue": "Weiter"
|
||||
"continue": "Weiter",
|
||||
"shared": "Geteilt",
|
||||
"pin": "Anheften",
|
||||
"unpin": "Lösen"
|
||||
},
|
||||
"title-bar": {
|
||||
"outdated": "veraltet"
|
||||
},
|
||||
"nav-bar": {
|
||||
"add-version": "Version hinzufügen",
|
||||
@@ -40,9 +50,15 @@
|
||||
"debug-description": "Aktiviert das Ausgabeprotokollfenster für IPA. Dies zeigt die Debug-Konsole, welche die Mods verwenden.",
|
||||
"outdated-tippy": "Diese Version ist veraltet, und einige Mods oder Funktionen funktionieren möglicherweise nicht wie erwartet. Es wird empfohlen, die empfohlene Version ({recommendedVersion}) von Beat Saber zu verwenden, um die neuesten Funktionen und Fehlerbehebungen zu genießen.",
|
||||
"advanced-launch": {
|
||||
"button": "Erweiterter Start",
|
||||
"button": "Startoptionen",
|
||||
"placeholder": "Weitere Argumente, bspw: --revert; --nowait"
|
||||
}
|
||||
},
|
||||
"skipsteam": "Steam überspringen",
|
||||
"skipsteam-description": "Verhindert, dass Steam automatisch mit Beat Saber geöffnet wird. Aktivieren Sie dies, wenn Sie eine andere VR-Laufzeit wie WiVRn oder Monado verwenden, mit der SteamVR interferieren könnte.",
|
||||
"map-editor": "Karteneditor",
|
||||
"map-editor-description": "Starte den offiziellen Beat Saber-Karteneditor anstelle des Spiels.",
|
||||
"proton-logs": "Proton-Logs",
|
||||
"proton-logs-description": "Aktiviert die Aufzeichnung von Proton-Logs für diese Beat Saber-Installation auf \"{versionPath}\"."
|
||||
},
|
||||
"maps": {
|
||||
"search-bar": {
|
||||
@@ -106,6 +122,7 @@
|
||||
"latest": "Neueste",
|
||||
"description": "Beschreibung",
|
||||
"dropdown": {
|
||||
"import-mods": "Mods importieren",
|
||||
"uninstall-all": "Alle deinstallieren",
|
||||
"unselect-all": "Alle abwählen"
|
||||
}
|
||||
@@ -116,6 +133,10 @@
|
||||
"title": "Mods bereits installiert",
|
||||
"description": "Alle ausgewählten Mods sind bereits installiert"
|
||||
}
|
||||
},
|
||||
"drop-zone": {
|
||||
"text": "Importiere deine Mods",
|
||||
"subtext": "Ziehe deine \"zip\"- oder \"dll\"-Dateien hierher, um sie zu importieren"
|
||||
}
|
||||
},
|
||||
"dropdown": {
|
||||
@@ -129,7 +150,7 @@
|
||||
}
|
||||
},
|
||||
"available-versions": {
|
||||
"title": "Wähle eine Version",
|
||||
"title": "Eine Version herunterladen",
|
||||
"steam-release": "Versionsseite",
|
||||
"dropdown": {
|
||||
"refresh": "Aktualisiere die Versionen",
|
||||
@@ -258,6 +279,13 @@
|
||||
"error-notification": {
|
||||
"message": "Ein Fehler ist aufgetreten, die Einstellungen für symbolische Links können nicht geändert werden."
|
||||
}
|
||||
},
|
||||
"use-system-proxy": {
|
||||
"title": "System-Proxy verwenden",
|
||||
"description": "BSManager wird Netzwerkrequests über Ihren Systemproxy durchführen.",
|
||||
"error-notification": {
|
||||
"message": "Es ist ein Fehler aufgetreten, die Systemproxyeinstellungen können nicht geändert werden."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -308,6 +336,8 @@
|
||||
"msg": {
|
||||
"401": "Steam scheint uns Beat Saber nicht herunterladen zu lassen 😢",
|
||||
"404": "Steam-Server können nicht kontaktiert werden.",
|
||||
"ExeNotFoundWindows": "\"DepotDownloader.exe\" fehlt. Bitte überprüfen Sie, ob die ausführbare Datei von Ihrer Antivirensoftware quarantäneiert wurde.",
|
||||
"ExeNotFoundLinux": "Die ausführbare Datei \"DepotDownloader\" fehlt.",
|
||||
"Password": "Passwort ist ungültig.",
|
||||
"InvalidCredentials": "Ungültige Anmeldedaten, nicht genehmigte Verbindung oder zu viele Anmeldeversuche.",
|
||||
"NoManifest": "Es wurde kein Manifest gefunden.",
|
||||
@@ -341,8 +371,8 @@
|
||||
"UNABLE_TO_GET_MANIFEST": "Unmöglich, das für den Download erforderliche Manifest zu erhalten.",
|
||||
"VERIFY_INTEGRITY_FAILED": "Beim Überprüfen der Dateien ist ein Fehler aufgetreten.",
|
||||
"SOME_FILES_FAILED_TO_DOWNLOAD": "Einige Dateien konnten nicht heruntergeladen werden.",
|
||||
"OCULUS_LOGIN_TIMED_OUT": "Es hat zu lange gedauert, das Anmelde-Token abzurufen.",
|
||||
"OCULUS_LOGIN_WINDOW_CLOSED_BY_USER": "Das Meta-Anmeldefenster wurde geschlossen.",
|
||||
"META_LOGIN_TIMED_OUT": "Es hat zu lange gedauert, das Anmelde-Token abzurufen.",
|
||||
"META_LOGIN_WINDOW_CLOSED_BY_USER": "Das Meta-Anmeldefenster wurde geschlossen.",
|
||||
"NO_META_AUTH_TOKEN": "Unmöglich, das für den Download erforderliche Meta-Anmelde-Token zu erhalten.",
|
||||
"UNKNOWN_ERROR": "Ein unbekannter Fehler ist aufgetreten."
|
||||
}
|
||||
@@ -429,11 +459,13 @@
|
||||
"success": {
|
||||
"titles": {
|
||||
"BS_LAUNCHING": "Starte... 🚀",
|
||||
"STEAM_LAUNCHING": "Steam startet!"
|
||||
"STEAM_LAUNCHING": "Steam startet!",
|
||||
"SKIPPING_STEAM_LAUNCH": "Steam-Start überspringen"
|
||||
},
|
||||
"msg": {
|
||||
"BS_LAUNCHING": "Vergiss nicht, dich aufzuwärmen 😉",
|
||||
"STEAM_LAUNCHING": "Beat Saber wird automatisch nach Steam gestartet."
|
||||
"STEAM_LAUNCHING": "Beat Saber wird automatisch nach Steam gestartet.",
|
||||
"SKIPPING_STEAM_LAUNCH": "Ich hoffe, du weißt, was du tust :)"
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
@@ -526,6 +558,17 @@
|
||||
"no-mods": "Keine Mods sind in dieser Version installiert 😑"
|
||||
}
|
||||
}
|
||||
},
|
||||
"import-mod": {
|
||||
"titles": {
|
||||
"success": "Mod-Import abgeschlossen",
|
||||
"error": "Es ist ein Fehler beim Importieren der Mods aufgetreten"
|
||||
},
|
||||
"msgs": {
|
||||
"success": "Mods erfolgreich importiert.",
|
||||
"some-success": "Einige Mods wurden erfolgreich importiert.",
|
||||
"no-dlls": "Die Datei(en) enthalten keine \"dll\"-Dateien."
|
||||
}
|
||||
}
|
||||
},
|
||||
"maps": {
|
||||
@@ -582,6 +625,10 @@
|
||||
"ENOSPC": "Die Festplatte ist voll, machen Sie Platz und versuchen Sie es erneut.",
|
||||
"UNKNOWN_ERROR": "Ein unbekannter Fehler ist beim Verknüpfen des Ordners aufgetreten."
|
||||
}
|
||||
},
|
||||
"adding-error": {
|
||||
"title": "Das Hinzufügen des freigegebenen Ordners ist fehlgeschlagen",
|
||||
"msg": "Sie können \"{folder}\" nicht zu freigegebenen Ordnern hinzufügen."
|
||||
}
|
||||
},
|
||||
"create-launch-shortcut": {
|
||||
@@ -737,31 +784,29 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"link-maps": {
|
||||
"title": "Maps verknüpfen",
|
||||
"desc": "Das Verknüpfen von Maps ermöglicht es, Maps zwischen allen Versionen zu teilen. Nach der Verknüpfung profitiert diese Version von den geteilten Maps.",
|
||||
"info": "Das Hinzufügen und Löschen von Maps wird ebenfalls geteilt.",
|
||||
"keep-maps": {
|
||||
"label": "Maps behalten",
|
||||
"title": "\"Maps behalten\" verschiebt die Maps der aktuellen Version in den geteilten Maps-Ordner. Andernfalls gehen sie verloren."
|
||||
},
|
||||
"valid-btn": "Maps verknüpfen"
|
||||
},
|
||||
"unlink-maps": {
|
||||
"title": "Maps-Verknüpfung aufheben",
|
||||
"desc": "Bitte beachte, dass das Aufheben der Verknüpfung von Maps die Verwendung von geteilten Maps für diese Version nicht zulässt.",
|
||||
"keep-maps": {
|
||||
"label": "Maps behalten",
|
||||
"title": "\"Maps behalten\" kopiert die Maps aus dem geteilten Maps-Ordner in den Ordner der ausgewählten Version. Andernfalls gehen sie verloren."
|
||||
},
|
||||
"valid-btn": "Verknüpfung aufheben"
|
||||
},
|
||||
"delete-duplicate-maps": {
|
||||
"title": "Map entfernen?",
|
||||
"desc": "Nur die Karte \"{map}\" ist ein Duplikat. Bist du sicher, dass du sie löschen möchtest?",
|
||||
"desc-plural": "Es wurden {nb} doppelte Karten gefunden. Bist du sicher, dass du sie löschen möchtest?"
|
||||
}
|
||||
},
|
||||
"link-contents": {
|
||||
"title": "Die {contentType} verknüpfen",
|
||||
"p-1": "Das Verknüpfen der {contentType} ermöglicht das Teilen der {contentType} zwischen allen Versionen mit dieser Funktion. Sobald verknüpft, profitiert diese Version von geteilten {contentType}.",
|
||||
"p-2": "Um die Verknüpfungsfunktion zu nutzen, werden alle deine {itemsHtml} in den {sharedHtml}-Ordner verschoben. Danach wird ein symbolischer Link, der auf den gemeinsamen {contentType}-Ordner verweist, deinen ursprünglichen {contentType}-Ordner ersetzen.",
|
||||
"warning": "Bitte beachten Sie, dass alle Änderungen an den {contentType} auch alle Versionen mit dieser Funktion beeinflussen.",
|
||||
"what-is-a-symbolic-link": "Was ist ein symbolischer Link?",
|
||||
"i-need-help": "Ich brauche Hilfe",
|
||||
"valid-btn": "Die {contentType} verknüpfen"
|
||||
},
|
||||
"unlink-contents": {
|
||||
"title": "Die {contentType} entknüpfen",
|
||||
"p-1": "Achtung, das Entknüpfen der {contentType} deaktiviert die Nutzung von geteilten {contentType} für diese Version, bis es wieder aktiviert wird.",
|
||||
"p-2": "Um die Verknüpfung deiner {contentType} aufzuheben, wird der symbolische Link, der auf den {sharedHtml}-Ordner verweist, entfernt. Danach werden alle {contentType} aus dem geteilten Ordner in den {itemsHtml}-Ordner deiner Beat Saber-Version kopiert.",
|
||||
"do-not-copy-contents": "Die {contentType} nicht kopieren",
|
||||
"do-not-copy-contents-tip": "Wenn aktiviert, werden die {contentType} im geteilten Ordner nicht in deine Beat Saber-Version kopiert. Infolgedessen werden keine {contentType} in deiner Version verbleiben, nachdem die Verknüpfung aufgehoben wurde.",
|
||||
"valid-btn": "Die {contentType} entknüpfen"
|
||||
},
|
||||
"download-maps": {
|
||||
"search-btn": "Suchen",
|
||||
"loading-maps": "Lade Maps...",
|
||||
@@ -783,9 +828,14 @@
|
||||
"add-folder": "Ordner hinzufügen",
|
||||
"link-folder": "Ordner verknüpfen",
|
||||
"unlink-folder": "Ordner-Verknüpfung aufheben",
|
||||
"link-all": "Alle verlinken"
|
||||
"link-all": "Alle verlinken",
|
||||
"remove-from-the-list": "Aus der Liste entfernen"
|
||||
}
|
||||
},
|
||||
"adding-shared-folder": {
|
||||
"title": "Freigegebenen Ordner hinzufügen",
|
||||
"description": "Beim Verknüpfen des Ordners \"{folder}\" können Probleme auftreten. Möchten Sie ihn wirklich hinzufügen?"
|
||||
},
|
||||
"create-launch-shortcut": {
|
||||
"title": "Verknüpfung erstellen",
|
||||
"desc": "Das Erstellen einer Verknüpfung ermöglicht es dir, Beat Saber mit den ausgewählten Optionen zu starten, ohne durch BSManager zu gehen.",
|
||||
@@ -804,13 +854,12 @@
|
||||
"stay": "Angemeldet bleiben",
|
||||
"connect-to-meta": "Mit Meta verbinden"
|
||||
},
|
||||
"original-version-backup-oculus": {
|
||||
"title": "Achtung",
|
||||
"body": {
|
||||
"must-be-installed-once": "Sie müssen Beat Saber aus dem Oculus Store auf diesem Gerät installiert haben, andernfalls wird Beat Saber nach dem Start automatisch geschlossen.",
|
||||
"will-backup": "Um diese Version zu starten, wird der ursprüngliche Installationsordner von Beat Saber in deiner Oculus-Bibliothek umbenannt und beim Schließen von Beat Saber automatisch wiederhergestellt."
|
||||
},
|
||||
"not-remind-me": "Nicht mehr erinnern",
|
||||
"enable-oculus-sideloaded-apps": {
|
||||
"title": "Sideloading aktivieren",
|
||||
"info-1": "Um Beat Saber starten zu können, muss die Möglichkeit aktiviert werden, Sideloading-Apps auszuführen. BSManager wird Administratorrechte anfordern, um diese Funktion automatisch zu aktivieren.",
|
||||
"info-2": "Die Sideloading-Funktion ermöglicht das Starten von Spielen, die sich außerhalb Ihres Oculus-Bibliotheksordners befinden.",
|
||||
"info-3": "Nachdem das Sideloading aktiviert wurde, bleibt die Funktion aktiv, und Sie werden nicht mehr aufgefordert, sie zu aktivieren.",
|
||||
"i-want-to-do-it-myself": "Ich möchte es selbst machen",
|
||||
"understood": "Verstanden"
|
||||
},
|
||||
"enter-meta-token": {
|
||||
@@ -865,7 +914,8 @@
|
||||
"tags": "Stichwörter",
|
||||
"specificities": "Allgemein",
|
||||
"requirements": "Anforderungen",
|
||||
"exclude": "Ausschließen"
|
||||
"exclude": "Ausschließen",
|
||||
"leaderboard": "Rangliste"
|
||||
},
|
||||
"map-types": {
|
||||
"accuracy": "Präzision",
|
||||
@@ -916,11 +966,16 @@
|
||||
},
|
||||
"map-specificities": {
|
||||
"automapper": "KI",
|
||||
"ranked": "Ranked",
|
||||
"curated": "Curated",
|
||||
"verified": "Verifiziert",
|
||||
"fullSpread": "Full Spread"
|
||||
},
|
||||
"map-leaderboard": {
|
||||
"All": "Alle",
|
||||
"Ranked": "Ranked",
|
||||
"BeatLeader": "BeatLeader",
|
||||
"ScoreSaber": "ScoreSaber"
|
||||
},
|
||||
"map-excludes": {
|
||||
"installed": "Installiert"
|
||||
},
|
||||
@@ -1008,86 +1063,6 @@
|
||||
"no-models": "Keine Modelle gefunden.",
|
||||
"no-internet": "Keine Internetverbindung.",
|
||||
"error-occured": "Ein Fehler ist aufgetreten, versuche es später erneut."
|
||||
},
|
||||
"link-models": {
|
||||
"avatar": {
|
||||
"title": "Avatare verbinden",
|
||||
"desc": "Das Verbinden von Avataren ermöglicht das Teilen von Avataren zwischen allen Versionen. Sobald verbunden, wird diese Version von den geteilten Avataren profitieren.",
|
||||
"info": "Das Hinzufügen und Entfernen von Avataren wird ebenfalls geteilt.",
|
||||
"keep-models": {
|
||||
"label": "Avatare behalten",
|
||||
"title": "Das Behalten der Avatare wird die Avatare der aktuellen Version in den Ordner der geteilten Avatare verschieben. Andernfalls gehen sie verloren."
|
||||
},
|
||||
"valid-btn": "Avatare verbinden"
|
||||
},
|
||||
"saber": {
|
||||
"title": "Säbel verbinden",
|
||||
"desc": "Das Verbinden von Säbeln ermöglicht das Teilen von Säbeln zwischen allen Versionen. Sobald verbunden, wird diese Version von den geteilten Säbeln profitieren.",
|
||||
"info": "Das Hinzufügen und Entfernen von Säbeln wird ebenfalls geteilt.",
|
||||
"keep-models": {
|
||||
"label": "Säbel behalten",
|
||||
"title": "Das Behalten der Säbel wird die Säbel der aktuellen Version in den Ordner der geteilten Säbel verschieben. Andernfalls gehen sie verloren."
|
||||
},
|
||||
"valid-btn": "Säbel verbinden"
|
||||
},
|
||||
"platform": {
|
||||
"title": "Plattformen verbinden",
|
||||
"desc": "Das Verbinden von Plattformen ermöglicht das Teilen von Plattformen zwischen allen Versionen. Sobald verbunden, wird diese Version von den geteilten Plattformen profitieren.",
|
||||
"info": "Das Hinzufügen und Entfernen von Plattformen wird ebenfalls geteilt.",
|
||||
"keep-models": {
|
||||
"label": "Plattformen behalten",
|
||||
"title": "Das Behalten der Plattformen wird die Plattformen der aktuellen Version in den Ordner der geteilten Plattformen verschieben. Andernfalls gehen sie verloren."
|
||||
},
|
||||
"valid-btn": "Plattformen verbinden"
|
||||
},
|
||||
"bloq": {
|
||||
"title": "Bloqs verbinden",
|
||||
"desc": "Das Verbinden von Bloqs ermöglicht das Teilen von Bloqs zwischen allen Versionen. Sobald verbunden, wird diese Version von den geteilten Bloqs profitieren.",
|
||||
"info": "Das Hinzufügen und Entfernen von Bloqs wird ebenfalls geteilt.",
|
||||
"keep-models": {
|
||||
"label": "Bloqs behalten",
|
||||
"title": "Das Behalten der Bloqs wird die Bloqs der aktuellen Version in den Ordner der geteilten Bloqs verschieben. Andernfalls gehen sie verloren."
|
||||
},
|
||||
"valid-btn": "Bloqs verbinden"
|
||||
}
|
||||
},
|
||||
"unlink-models": {
|
||||
"avatar": {
|
||||
"title": "Avatare trennen",
|
||||
"desc": "Achtung, das Trennen der Avatare wird die Nutzung der geteilten Avatare für diese Version nicht mehr ermöglichen.",
|
||||
"keep-models": {
|
||||
"label": "Avatare behalten",
|
||||
"title": "Das Behalten der Avatare wird eine Kopie der geteilten Avatare für die aktuelle Version erstellen. Andernfalls wird kein Avatar für diese Version aufbewahrt."
|
||||
},
|
||||
"valid-btn": "Avatare trennen"
|
||||
},
|
||||
"saber": {
|
||||
"title": "Säbel trennen",
|
||||
"desc": "Achtung, das Trennen der Säbel wird die Nutzung der geteilten Säbel für diese Version nicht mehr ermöglichen.",
|
||||
"keep-models": {
|
||||
"label": "Säbel behalten",
|
||||
"title": "Das Behalten der Säbel wird eine Kopie der geteilten Säbel für die aktuelle Version erstellen. Andernfalls wird kein Säbel für diese Version aufbewahrt."
|
||||
},
|
||||
"valid-btn": "Säbel trennen"
|
||||
},
|
||||
"platform": {
|
||||
"title": "Plattformen trennen",
|
||||
"desc": "Achtung, das Trennen der Plattformen wird die Nutzung der geteilten Plattformen für diese Version nicht mehr ermöglichen.",
|
||||
"keep-models": {
|
||||
"label": "Plattformen behalten",
|
||||
"title": "Das Behalten der Plattformen wird eine Kopie der geteilten Plattformen für die aktuelle Version erstellen. Andernfalls wird keine Plattform für diese Version aufbewahrt."
|
||||
},
|
||||
"valid-btn": "Plattformen trennen"
|
||||
},
|
||||
"bloq": {
|
||||
"title": "Bloqs trennen",
|
||||
"desc": "Achtung, das Trennen der Bloqs wird die Nutzung der geteilten Bloqs für diese Version nicht mehr ermöglichen.",
|
||||
"keep-models": {
|
||||
"label": "Bloqs behalten",
|
||||
"title": "Das Behalten der Bloqs wird eine Kopie der geteilten Bloqs für die aktuelle Version erstellen. Andernfalls wird kein Bloq für diese Version aufbewahrt."
|
||||
},
|
||||
"valid-btn": "Bloqs trennen"
|
||||
}
|
||||
}
|
||||
},
|
||||
"notifications": {
|
||||
@@ -1164,14 +1139,6 @@
|
||||
"stop-download": "Download stoppen",
|
||||
"cancel-download": "Download abbrechen",
|
||||
"open-file": "Datei öffnen",
|
||||
"link-playlists": "Playlists verknüpfen",
|
||||
"link-playlist-desc": "Das Verknüpfen von Playlists ermöglicht das Teilen von Playlists zwischen allen Versionen. Nach der Verknüpfung profitiert diese Version von geteilten Playlists.",
|
||||
"link-playlist-info": "Das Hinzufügen und Löschen von Playlists wird ebenfalls geteilt.",
|
||||
"keep-playlists": "Playlists behalten",
|
||||
"keep-playlists-tip": "Wenn du Playlists behältst, werden die Playlists von der aktuellen Version in den Ordner für geteilte Playlists verschoben. Andernfalls gehen sie verloren.",
|
||||
"unlink-playlists": "Playlists trennen",
|
||||
"unlink-playlist-desc": "Achtung, das Trennen von Playlists verhindert die Nutzung geteilter Playlists für diese Version.",
|
||||
"unlink-keep-playlists-tip": "Wenn du Playlists behältst, wird eine Kopie der geteilten Playlists für die aktuelle Version erstellt. Andernfalls werden keine Playlists für diese Version behalten.",
|
||||
"delete-playlist-ask": "Playlist löschen?",
|
||||
"delete-playlists-ask": "Playlists löschen?",
|
||||
"delete-playlist-desc": "Bist du dir sicher, dass du die Playlist \"{playlistTitle}\" löschen möchtest?",
|
||||
|
||||
@@ -8,6 +8,10 @@
|
||||
"maps": "Maps",
|
||||
"playlists": "Playlists",
|
||||
"models": "Models",
|
||||
"avatars": "Avatars",
|
||||
"sabers": "Sabers",
|
||||
"platforms": "Platforms",
|
||||
"blocks": "Blocks",
|
||||
"cancel": "Cancel",
|
||||
"delete": "Delete",
|
||||
"accept": "Accept",
|
||||
@@ -19,7 +23,13 @@
|
||||
"choose-folder": "Choose folder",
|
||||
"unknown": "An unknown error occurred ¯\\_(ツ)_/¯",
|
||||
"warning": "Warning",
|
||||
"continue": "Continue"
|
||||
"continue": "Continue",
|
||||
"shared": "Shared",
|
||||
"pin": "Pin",
|
||||
"unpin": "Unpin"
|
||||
},
|
||||
"title-bar": {
|
||||
"outdated": "outdated"
|
||||
},
|
||||
"nav-bar": {
|
||||
"add-version": "Add a version",
|
||||
@@ -40,9 +50,15 @@
|
||||
"debug-description": "Enables the output log window for IPA. This will show the debug console that mods use.",
|
||||
"outdated-tippy": "This version is outdated, and some mods or features may no longer work as expected. Prefer using the recommended version ({recommendedVersion}) of Beat Saber to enjoy the latest features and bugfixes.",
|
||||
"advanced-launch": {
|
||||
"button": "Advanced launch",
|
||||
"button": "Launch options",
|
||||
"placeholder": "Additional arguments ex: --revert; --nowait"
|
||||
}
|
||||
},
|
||||
"skipsteam": "Skip Steam",
|
||||
"skipsteam-description": "Stops Steam from opening automatically with Beat Saber, enable if you are using a different VR runtime like WiVRn or Monado that SteamVR may interfere with.",
|
||||
"map-editor": "Map Editor",
|
||||
"map-editor-description": "Start the official Beat Saber map editor instead of the game.",
|
||||
"proton-logs": "Proton Logs",
|
||||
"proton-logs-description": "Enables the recording of proton logs for this Beat Saber install to \"{versionPath}\"."
|
||||
},
|
||||
"maps": {
|
||||
"search-bar": {
|
||||
@@ -106,6 +122,7 @@
|
||||
"latest": "Latest",
|
||||
"description": "Description",
|
||||
"dropdown": {
|
||||
"import-mods": "Import mods",
|
||||
"uninstall-all": "Uninstall all",
|
||||
"unselect-all": "Unselect all"
|
||||
}
|
||||
@@ -116,6 +133,10 @@
|
||||
"title": "Mods already installed",
|
||||
"description": "All selected mods are already installed"
|
||||
}
|
||||
},
|
||||
"drop-zone": {
|
||||
"text": "Import your mods",
|
||||
"subtext": "Drop your \"zip\" or \"dll\" files here to import them"
|
||||
}
|
||||
},
|
||||
"dropdown": {
|
||||
@@ -129,7 +150,7 @@
|
||||
}
|
||||
},
|
||||
"available-versions": {
|
||||
"title": "Select a version",
|
||||
"title": "Download a version",
|
||||
"steam-release": "Release page",
|
||||
"dropdown": {
|
||||
"refresh": "Refresh versions",
|
||||
@@ -244,7 +265,7 @@
|
||||
"confirm-btn": "Yes I'm sure"
|
||||
},
|
||||
"error-notification": {
|
||||
"message": "An error occur, unable to disable hardware acceleration."
|
||||
"message": "An error occurred, unable to disable hardware acceleration."
|
||||
}
|
||||
},
|
||||
"use-symlinks": {
|
||||
@@ -256,7 +277,14 @@
|
||||
"confirm-btn": "Yes I'm sure"
|
||||
},
|
||||
"error-notification": {
|
||||
"message": "An error occur, unable to change symlinks settings."
|
||||
"message": "An error occurred, unable to change symlinks settings."
|
||||
}
|
||||
},
|
||||
"use-system-proxy": {
|
||||
"title": "Use system proxy",
|
||||
"description": "BSManager will make network requests through your system proxy.",
|
||||
"error-notification": {
|
||||
"message": "An error occurred, unable to change system proxy settings."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -308,6 +336,8 @@
|
||||
"msg": {
|
||||
"401": "Steam doesn't seem to want to let us download Beat Saber 😢",
|
||||
"404": "Unable to contact Steam servers.",
|
||||
"ExeNotFoundWindows": "\"DepotDownloader.exe\" is missing. Please check if the executable is quarantined by your anti-virus.",
|
||||
"ExeNotFoundLinux": "\"DepotDownloader\" executable is missing.",
|
||||
"Password": "Password is invalid.",
|
||||
"InvalidCredentials": "Invalid login credentials, unapproved connection, or too many login attempts.",
|
||||
"NoManifest": "No manifest was found",
|
||||
@@ -341,8 +371,8 @@
|
||||
"UNABLE_TO_GET_MANIFEST": "Unable to obtain the manifest required for download.",
|
||||
"VERIFY_INTEGRITY_FAILED": "An error occurred during file verification.",
|
||||
"SOME_FILES_FAILED_TO_DOWNLOAD": "Some files failed to download.",
|
||||
"OCULUS_LOGIN_TIMED_OUT": "The login token took too long to retrieve.",
|
||||
"OCULUS_LOGIN_WINDOW_CLOSED_BY_USER": "The Meta login window was closed.",
|
||||
"META_LOGIN_TIMED_OUT": "The login token took too long to retrieve.",
|
||||
"META_LOGIN_WINDOW_CLOSED_BY_USER": "The Meta login window was closed.",
|
||||
"NO_META_AUTH_TOKEN": "Unable to retrieve the Meta login token required for download.",
|
||||
"UNKNOWN_ERROR": "An unknown error occurred."
|
||||
}
|
||||
@@ -429,11 +459,13 @@
|
||||
"success": {
|
||||
"titles": {
|
||||
"BS_LAUNCHING": "Launching...🚀",
|
||||
"STEAM_LAUNCHING": "Steam is launching!"
|
||||
"STEAM_LAUNCHING": "Steam is launching!",
|
||||
"SKIPPING_STEAM_LAUNCH": "Skipping Steam launch"
|
||||
},
|
||||
"msg": {
|
||||
"BS_LAUNCHING": "Don't forget to warm up 😉",
|
||||
"STEAM_LAUNCHING": "Beat Saber will automatically launch after Steam."
|
||||
"STEAM_LAUNCHING": "Beat Saber will automatically launch after Steam.",
|
||||
"SKIPPING_STEAM_LAUNCH": "I hope you know what you're doing :)"
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
@@ -526,6 +558,17 @@
|
||||
"no-mods": "No mods are installed in this version 😑"
|
||||
}
|
||||
}
|
||||
},
|
||||
"import-mod": {
|
||||
"titles": {
|
||||
"success": "Mods import completed",
|
||||
"error": "An error occurred during the mods import"
|
||||
},
|
||||
"msgs": {
|
||||
"success": "Mods successfully imported.",
|
||||
"some-success": "Some mods were successfully imported.",
|
||||
"no-dlls": "The file(s) do not contain any \"dll\" files."
|
||||
}
|
||||
}
|
||||
},
|
||||
"maps": {
|
||||
@@ -582,6 +625,10 @@
|
||||
"ENOSPC": "The disk is full, make some space and try again.",
|
||||
"UNKNOWN_ERROR": "An unknown error has occurred while linking the folder."
|
||||
}
|
||||
},
|
||||
"adding-error": {
|
||||
"title": "Adding shared folder failed",
|
||||
"msg": "You can't add \"{folder}\" to shared folders."
|
||||
}
|
||||
},
|
||||
"create-launch-shortcut": {
|
||||
@@ -736,32 +783,25 @@
|
||||
"multiple": "These maps will also be removed from versions using shared maps"
|
||||
}
|
||||
}
|
||||
},
|
||||
"link-maps": {
|
||||
"title": "Link maps",
|
||||
"desc": "Linking maps allows sharing maps between all versions with this feature enabled. Once linked, this version will benefit from shared maps.",
|
||||
"info": "Adding and deleting maps will also be shared",
|
||||
"keep-maps": {
|
||||
"label": "Keep maps",
|
||||
"title": "Keeping maps will copy all maps from the current version to the shared maps folder. Otherwise, no maps will be kept from this version and they will be lost."
|
||||
},
|
||||
"valid-btn": "Link maps"
|
||||
},
|
||||
"unlink-maps": {
|
||||
"title": "Unlink maps",
|
||||
"desc": "Be careful, unlinking maps will disable the use of shared maps for this version, until it is reenabled.",
|
||||
"keep-maps": {
|
||||
"label": "Keep maps",
|
||||
"title": "Keeping maps will copy all maps from the shared folder to the current version after unlinking. Maps will not be lost if this is disabled."
|
||||
},
|
||||
"valid-btn": "Unlink maps"
|
||||
},
|
||||
"delete-duplicate-maps": {
|
||||
"title": "Delete maps?",
|
||||
"desc": "Only the map \"{map}\" is a duplicate. Are you sure you want to delete it?",
|
||||
"desc-plural": "{nb} duplicate maps have been found. Are you sure you want to delete them?"
|
||||
}
|
||||
},
|
||||
"link-contents":{
|
||||
"title": "Link {contentType}",
|
||||
"p-1": "Linking {contentType} allows sharing {contentType} between all versions with this feature enabled. Once linked, this version will benefit from shared {contentType}.",
|
||||
"p-2": "In order to enjoy the linking feature, all your {itemsHtml} will be moved to the {sharedHtml} folder. After that, a symbolic link pointing to the shared {contentType} folder will replace your original {contentType} folder.",
|
||||
"warning": "Please note that any changes affecting {contentType} will also affect all versions with this feature enabled.",
|
||||
"what-is-a-symbolic-link": "What is a symbolic link?",
|
||||
"i-need-help": "I need help",
|
||||
"valid-btn": "Link {contentType}"
|
||||
},
|
||||
"unlink-contents":{
|
||||
"title": "Unlink {contentType}",
|
||||
"p-1": "Be careful, unlinking {contentType} will disable the use of shared {contentType} for this version, until it is reenabled.",
|
||||
"p-2": "In order to unlink your maps, the symbolic link pointing to the {sharedHtml} will be removed. After that, all maps in the shared folder will be copied to the {itemsHtml} folder of your Beat Saber version.",
|
||||
"do-not-copy-contents": "Do not copy {contentType}",
|
||||
"do-not-copy-contents-tip": "If enabled, {contentType} in the shared folder will not be copied to your Beat Saber version. As a result, no {contentType} will remain in your Beat Saber version after unlinking.",
|
||||
"valid-btn": "Unlink {contentType}"
|
||||
},
|
||||
"download-maps": {
|
||||
"search-btn": "Search",
|
||||
"loading-maps": "Loading maps...",
|
||||
@@ -783,9 +823,14 @@
|
||||
"add-folder": "Add Folder",
|
||||
"link-folder": "Link Folder",
|
||||
"unlink-folder": "Unlink Folder",
|
||||
"link-all": "Link all"
|
||||
"link-all": "Link all",
|
||||
"remove-from-the-list": "Remove from the list"
|
||||
}
|
||||
},
|
||||
"adding-shared-folder": {
|
||||
"title": "Adding Shared Folder",
|
||||
"description": "You might experience issues when linking the \"{folder}\" folder. Are you sure you want to add it?"
|
||||
},
|
||||
"create-launch-shortcut": {
|
||||
"title": "Create a shortcut",
|
||||
"desc": "Creating a shortcut will allow you to start Beat Saber with the chosen options without going through BSManager.",
|
||||
@@ -804,13 +849,12 @@
|
||||
"stay": "Remember me",
|
||||
"connect-to-meta": "Connect to Meta"
|
||||
},
|
||||
"original-version-backup-oculus": {
|
||||
"title": "Warning",
|
||||
"body": {
|
||||
"must-be-installed-once": "You must have Beat Saber installed from the Oculus Store on this device, otherwise Beat Saber might automatically close after launching.",
|
||||
"will-backup": "To launch this version, the original installation folder of Beat Saber located in your Oculus library will be renamed and will be automatically restored when Beat Saber is closed."
|
||||
},
|
||||
"not-remind-me": "Do not remind me",
|
||||
"enable-oculus-sideloaded-apps": {
|
||||
"title": "Enable Sideloading",
|
||||
"info-1": "In order to launch Beat Saber, the ability to run sideloaded apps must be enabled. BSManager will request administrator rights to enable this feature automatically.",
|
||||
"info-2": "The sideloaded apps feature allows launching games located outside your Oculus library folder.",
|
||||
"info-3": "After sideloading is activated, the feature will remain active, and you will no longer be prompted to enable it.",
|
||||
"i-want-to-do-it-myself": "I want to do it myself",
|
||||
"understood": "Understood"
|
||||
},
|
||||
"enter-meta-token": {
|
||||
@@ -865,7 +909,8 @@
|
||||
"tags": "tags",
|
||||
"specificities": "general",
|
||||
"requirements": "requirements",
|
||||
"exclude": "exclude"
|
||||
"exclude": "exclude",
|
||||
"leaderboard": "leaderboard"
|
||||
},
|
||||
"map-types": {
|
||||
"accuracy": "accuracy",
|
||||
@@ -916,11 +961,16 @@
|
||||
},
|
||||
"map-specificities": {
|
||||
"automapper": "AI",
|
||||
"ranked": "ranked",
|
||||
"curated": "curated",
|
||||
"verified": "verified",
|
||||
"fullSpread": "full spread"
|
||||
},
|
||||
"map-leaderboard": {
|
||||
"All": "All",
|
||||
"Ranked": "Ranked",
|
||||
"BeatLeader": "BeatLeader",
|
||||
"ScoreSaber": "ScoreSaber"
|
||||
},
|
||||
"map-excludes": {
|
||||
"installed": "installed"
|
||||
},
|
||||
@@ -1007,87 +1057,7 @@
|
||||
},
|
||||
"no-models": "No models found.",
|
||||
"no-internet": "No internet connection.",
|
||||
"error-occured": "An error occurred, try again later."
|
||||
},
|
||||
"link-models": {
|
||||
"avatar": {
|
||||
"title": "Link Avatars",
|
||||
"desc": "Linking avatars allows sharing avatars between all versions with this feature enabled. Once linked, this version will benefit from shared avatars.",
|
||||
"info": "Adding and deleting avatars will also be shared",
|
||||
"keep-models": {
|
||||
"label": "Keep Avatars",
|
||||
"title": "Keeping avatars will copy all avatars from the current version to the shared avatars folder. Otherwise, no avatars will be kept from this version and they will be lost."
|
||||
},
|
||||
"valid-btn": "Link Avatars"
|
||||
},
|
||||
"saber": {
|
||||
"title": "Link Sabers",
|
||||
"desc": "Linking sabers allows sharing sabers between all versions with this feature enabled. Once linked, this version will benefit from shared sabers.",
|
||||
"info": "Adding and deleting sabers will also be shared",
|
||||
"keep-models": {
|
||||
"label": "Keep Sabers",
|
||||
"title": "Keeping sabers will copy all sabers from the current version to the shared sabers folder. Otherwise, no sabers will be kept from this version and they will be lost."
|
||||
},
|
||||
"valid-btn": "Link Sabers"
|
||||
},
|
||||
"platform": {
|
||||
"title": "Link Platforms",
|
||||
"desc": "Linking platforms allows sharing platforms between all versions with this feature enabled. Once linked, this version will benefit from shared platforms.",
|
||||
"info": "Adding and deleting platforms will also be shared",
|
||||
"keep-models": {
|
||||
"label": "Keep Platforms",
|
||||
"title": "Keeping platforms will copy all platforms from the current version to the shared platforms folder. Otherwise, no platforms will be kept from this version and they will be lost."
|
||||
},
|
||||
"valid-btn": "Link Platforms"
|
||||
},
|
||||
"bloq": {
|
||||
"title": "Link Bloqs",
|
||||
"desc": "Linking bloqs allows sharing bloqs between all versions with this feature enabled. Once linked, this version will benefit from shared bloqs.",
|
||||
"info": "Adding and deleting bloqs will also be shared",
|
||||
"keep-models": {
|
||||
"label": "Keep Bloqs",
|
||||
"title": "Keeping bloqs will copy all bloqs from the current version to the shared bloqs folder. Otherwise, no bloqs will be kept from this version and they will be lost."
|
||||
},
|
||||
"valid-btn": "Link Bloqs"
|
||||
}
|
||||
},
|
||||
"unlink-models": {
|
||||
"avatar": {
|
||||
"title": "Unlink Avatars",
|
||||
"desc": "Be careful, unlinking avatars will disable the use of shared avatars for this version, until it is reenabled.",
|
||||
"keep-models": {
|
||||
"label": "Keep Avatars",
|
||||
"title": "Keeping avatars will copy all avatars from the shared folder to the current version after unlinking. Avatars will not be lost if this is disabled."
|
||||
},
|
||||
"valid-btn": "Unlink Avatars"
|
||||
},
|
||||
"saber": {
|
||||
"title": "Unlink Sabers",
|
||||
"desc": "Be careful, unlinking sabers will disable the use of shared sabers for this version, until it is reenabled.",
|
||||
"keep-models": {
|
||||
"label": "Keep Sabers",
|
||||
"title": "Keeping sabers will copy all sabers from the shared folder to the current version after unlinking. Sabers will not be lost if this is disabled."
|
||||
},
|
||||
"valid-btn": "Unlink Sabers"
|
||||
},
|
||||
"platform": {
|
||||
"title": "Unlink Platforms",
|
||||
"desc": "Be careful, unlinking platforms will disable the use of shared platforms for this version, until it is reenabled.",
|
||||
"keep-models": {
|
||||
"label": "Keep Platforms",
|
||||
"title": "Keeping platforms will copy all platforms from the shared folder to the current version after unlinking. Platforms will not be lost if this is disabled."
|
||||
},
|
||||
"valid-btn": "Unlink Platforms"
|
||||
},
|
||||
"bloq": {
|
||||
"title": "Unlink Bloqs",
|
||||
"desc": "Be careful, unlinking bloqs will disable the use of shared bloqs for this version, until it is reenabled.",
|
||||
"keep-models": {
|
||||
"label": "Keep Bloqs",
|
||||
"title": "Keeping bloqs will copy all bloqs from the shared folder to the current version after unlinking. Bloqs will not be lost if this is disabled."
|
||||
},
|
||||
"valid-btn": "Unlink Bloqs"
|
||||
}
|
||||
"error-occurred": "An error occurred, try again later."
|
||||
}
|
||||
},
|
||||
"notifications": {
|
||||
@@ -1164,14 +1134,6 @@
|
||||
"stop-download": "Stop download",
|
||||
"cancel-download": "Cancel download",
|
||||
"open-file": "Open file",
|
||||
"link-playlists": "Link playlists",
|
||||
"link-playlist-desc": "Linking playlists allows sharing playlists between all versions. Once linked, this version will benefit from shared playlists",
|
||||
"link-playlist-info": "Adding and deleting playlists will also be shared",
|
||||
"keep-playlists": "Keep playlists",
|
||||
"keep-playlists-tip": "Keeping playlists will move the playlists from the current version to the shared playlists folder. Otherwise, they will be lost",
|
||||
"unlink-playlists": "Unlink playlists",
|
||||
"unlink-playlist-desc": "Warning, unlinking playlists will no longer allow the use of shared playlists for this version.",
|
||||
"unlink-keep-playlists-tip": "Keeping playlists will create a copy of shared playlists for the current version. Otherwise, no playlists will be kept for this version.",
|
||||
"delete-playlist-ask": "Delete playlist?",
|
||||
"delete-playlists-ask": "Delete playlists?",
|
||||
"delete-playlist-desc": "Are you sure you want to delete the playlist \"{playlistTitle}\"?",
|
||||
|
||||
@@ -8,6 +8,10 @@
|
||||
"maps": "Mapas",
|
||||
"playlists": "Playlists",
|
||||
"models": "Modelos",
|
||||
"avatars": "Avatares",
|
||||
"sabers": "Sables",
|
||||
"platforms": "Plataformas",
|
||||
"blocks": "Bloques",
|
||||
"cancel": "Cancelar",
|
||||
"delete": "Borrar",
|
||||
"accept": "Aceptar",
|
||||
@@ -19,7 +23,13 @@
|
||||
"choose-folder": "Elige la carpeta",
|
||||
"unknown": "Se ha producido un error desconocido ¯\\_(ツ)_/¯",
|
||||
"warning": "Advertencia",
|
||||
"continue": "Continuar"
|
||||
"continue": "Continuar",
|
||||
"shared": "Compartido",
|
||||
"pin": "Fijar",
|
||||
"unpin": "Desfijar"
|
||||
},
|
||||
"title-bar": {
|
||||
"outdated": "obsoleto"
|
||||
},
|
||||
"nav-bar": {
|
||||
"add-version": "Agregar una versión",
|
||||
@@ -40,9 +50,15 @@
|
||||
"debug-description": "Habilita la ventana de registro de salida para IPA. Esto mostrará la consola de depuración que usan los mods.",
|
||||
"outdated-tippy": "Esta versión está desactualizada, y algunos mods o funciones pueden no funcionar como se espera. Es mejor usar la versión recomendada ({recommendedVersion}) de Beat Saber para disfrutar de las últimas características y correcciones.",
|
||||
"advanced-launch": {
|
||||
"button": "Opciones avanzadas",
|
||||
"button": "Opciones de lanzamiento",
|
||||
"placeholder": "Argumentos adicionales ej: --revert; --no-wait"
|
||||
}
|
||||
},
|
||||
"skipsteam": "Saltar Steam",
|
||||
"skipsteam-description": "Evita que Steam se abra automáticamente con Beat Saber, habilita esto si estás usando un runtime de VR diferente como WiVRn o Monado que SteamVR podría interferir.",
|
||||
"map-editor": "Editor de mapas",
|
||||
"map-editor-description": "Inicia el editor oficial de mapas de Beat Saber en lugar del juego.",
|
||||
"proton-logs": "Registros de Proton",
|
||||
"proton-logs-description": "Habilita el registro de los logs de Proton para esta instalación de Beat Saber en \"{versionPath}\"."
|
||||
},
|
||||
"maps": {
|
||||
"search-bar": {
|
||||
@@ -106,6 +122,7 @@
|
||||
"latest": "Último",
|
||||
"description": "Descripción",
|
||||
"dropdown": {
|
||||
"import-mods": "Importar mods",
|
||||
"uninstall-all": "Desinstalar todos",
|
||||
"unselect-all": "Deseleccionar todo"
|
||||
}
|
||||
@@ -116,6 +133,10 @@
|
||||
"title": "Mods ya instalados",
|
||||
"description": "Todos los mods seleccionados ya están instalados"
|
||||
}
|
||||
},
|
||||
"drop-zone": {
|
||||
"text": "Importa tus mods",
|
||||
"subtext": "Coloca tus archivos \"zip\" o \"dll\" aquí para importarlos"
|
||||
}
|
||||
},
|
||||
"dropdown": {
|
||||
@@ -129,7 +150,7 @@
|
||||
}
|
||||
},
|
||||
"available-versions": {
|
||||
"title": "Selecciona una versión",
|
||||
"title": "Descargar una versión",
|
||||
"steam-release": "Anuncio en Steam",
|
||||
"dropdown": {
|
||||
"refresh": "Actualizar las versiones",
|
||||
@@ -258,6 +279,13 @@
|
||||
"error-notification": {
|
||||
"message": "Ocurrió un error, no se pueden cambiar los ajustes de los enlaces simbólicos."
|
||||
}
|
||||
},
|
||||
"use-system-proxy": {
|
||||
"title": "Usar proxy del sistema",
|
||||
"description": "BSManager realizará solicitudes de red a través de su proxy del sistema.",
|
||||
"error-notification": {
|
||||
"message": "Ocurrió un error, no se pueden cambiar los ajustes del proxy del sistema."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -308,6 +336,8 @@
|
||||
"msg": {
|
||||
"401": "Parece que Steam no quiere dejarnos descargar Beat Saber 😢",
|
||||
"404": "No se puede contactar con los servidores de Steam",
|
||||
"ExeNotFoundWindows": "Falta \"DepotDownloader.exe\". Por favor, verifique si el ejecutable está en cuarentena por su antivirus.",
|
||||
"ExeNotFoundLinux": "Falta el ejecutable de \"DepotDownloader\".",
|
||||
"Password": "La contraseña es inválida.",
|
||||
"InvalidCredentials": "Credenciales de inicio de sesión inválidas, conexión no aprobada o demasiados intentos de inicio de sesión.",
|
||||
"NoManifest": "No se ha encontrado el manifiesto",
|
||||
@@ -341,8 +371,8 @@
|
||||
"UNABLE_TO_GET_MANIFEST": "Imposible obtener el manifiesto necesario para la descarga.",
|
||||
"VERIFY_INTEGRITY_FAILED": "Se produjo un error durante la verificación de archivos.",
|
||||
"SOME_FILES_FAILED_TO_DOWNLOAD": "Algunos archivos no pudieron ser descargados.",
|
||||
"OCULUS_LOGIN_TIMED_OUT": "El token de inicio de sesión tardó demasiado en recuperarse.",
|
||||
"OCULUS_LOGIN_WINDOW_CLOSED_BY_USER": "La ventana de inicio de sesión de Meta fue cerrada.",
|
||||
"META_LOGIN_TIMED_OUT": "El token de inicio de sesión tardó demasiado en recuperarse.",
|
||||
"META_LOGIN_WINDOW_CLOSED_BY_USER": "La ventana de inicio de sesión de Meta fue cerrada.",
|
||||
"NO_META_AUTH_TOKEN": "Imposible recuperar el token de inicio de sesión de Meta necesario para la descarga.",
|
||||
"UNKNOWN_ERROR": "Se produjo un error desconocido."
|
||||
}
|
||||
@@ -429,11 +459,13 @@
|
||||
"success": {
|
||||
"titles": {
|
||||
"BS_LAUNCHING": "Lanzamiento...🚀",
|
||||
"STEAM_LAUNCHING": "¡Steam se está iniciando!"
|
||||
"STEAM_LAUNCHING": "¡Steam se está iniciando!",
|
||||
"SKIPPING_STEAM_LAUNCH": "Saltando el lanzamiento de Steam"
|
||||
},
|
||||
"msg": {
|
||||
"BS_LAUNCHING": "No olvides calentar 😉",
|
||||
"STEAM_LAUNCHING": "Beat Saber se iniciará automáticamente después de Steam."
|
||||
"STEAM_LAUNCHING": "Beat Saber se iniciará automáticamente después de Steam.",
|
||||
"SKIPPING_STEAM_LAUNCH": "Espero que sepas lo que estás haciendo :)"
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
@@ -526,6 +558,17 @@
|
||||
"no-mods": "No hay mods instalados en esta versión 😑"
|
||||
}
|
||||
}
|
||||
},
|
||||
"import-mod": {
|
||||
"titles": {
|
||||
"success": "Importación de mods completada",
|
||||
"error": "Ocurrió un error durante la importación de mods"
|
||||
},
|
||||
"msgs": {
|
||||
"success": "Mods importados con éxito.",
|
||||
"some-success": "Algunos mods fueron importados con éxito.",
|
||||
"no-dlls": "El(los) archivo(s) no contienen archivos \"dll\"."
|
||||
}
|
||||
}
|
||||
},
|
||||
"maps": {
|
||||
@@ -582,6 +625,10 @@
|
||||
"ENOSPC": "El disco está lleno, libera espacio e inténtalo de nuevo.",
|
||||
"UNKNOWN_ERROR": "Se ha producido un error desconocido al enlazar la carpeta."
|
||||
}
|
||||
},
|
||||
"adding-error": {
|
||||
"title": "Error al agregar una carpeta compartida",
|
||||
"msg": "No puedes agregar \"{folder}\" a las carpetas compartidas."
|
||||
}
|
||||
},
|
||||
"create-launch-shortcut": {
|
||||
@@ -737,31 +784,29 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"link-maps": {
|
||||
"title": "Vincular mapas",
|
||||
"desc": "La vinculación de mapas permite compartir mapas entre todas las versiones. Una vez vinculada, esta versión se beneficiará de los mapas compartidos",
|
||||
"info": "También se compartirá la adición y eliminación de mapas.",
|
||||
"keep-maps": {
|
||||
"label": "Mantener mapas",
|
||||
"title": "Mantener mapas moverá los mapas de la versión actual a la carpeta de mapas compartidos. De lo contrario, se perderán"
|
||||
},
|
||||
"valid-btn": "Vincular mapas"
|
||||
},
|
||||
"unlink-maps": {
|
||||
"title": "Desvincular mapas",
|
||||
"desc": "Ten en cuenta que desvincular mapas no permitirá el uso de mapas compartidos para esta versión.",
|
||||
"keep-maps": {
|
||||
"label": "Mantener mapas",
|
||||
"title": "Mantener los mapas creará una copia de los mapas compartidos para la versión actual. De lo contrario, no se mantendrá ningún mapa para esta versión."
|
||||
},
|
||||
"valid-btn": "Desvincular mapas"
|
||||
},
|
||||
"delete-duplicate-maps": {
|
||||
"title": "¿Borrar mapas?",
|
||||
"desc": "Solo el mapa \"{map}\" está duplicado. ¿Estás seguro de que quieres eliminarlo?",
|
||||
"desc-plural": "Se han encontrado {nb} mapas duplicados. ¿Estás seguro de que quieres eliminarlos?"
|
||||
}
|
||||
},
|
||||
"link-contents": {
|
||||
"title": "Vincular los {contentType}",
|
||||
"p-1": "Vincular los {contentType} permite compartir los {contentType} entre todas las versiones que tengan esta función activada. Una vez vinculados, esta versión se beneficiará de los {contentType} compartidos.",
|
||||
"p-2": "Para disfrutar de la función de vinculación, todos tus {itemsHtml} se moverán a la carpeta {sharedHtml}. Después de eso, un enlace simbólico que apunta a la carpeta compartida de {contentType} reemplazará tu carpeta original de {contentType}.",
|
||||
"warning": "Ten en cuenta que cualquier cambio que afecte a los {contentType} también afectará a todas las versiones con esta función activada.",
|
||||
"what-is-a-symbolic-link": "¿Qué es un enlace simbólico?",
|
||||
"i-need-help": "Necesito ayuda",
|
||||
"valid-btn": "Vincular los {contentType}"
|
||||
},
|
||||
"unlink-contents": {
|
||||
"title": "Desvincular los {contentType}",
|
||||
"p-1": "Ten cuidado, desvincular los {contentType} desactivará el uso de los {contentType} compartidos para esta versión, hasta que se vuelva a activar.",
|
||||
"p-2": "Para desvincular tus {contentType}, se eliminará el enlace simbólico que apunta a la carpeta {sharedHtml}. Después de eso, todos los {contentType} en la carpeta compartida se copiarán a la carpeta {itemsHtml} de tu versión de Beat Saber.",
|
||||
"do-not-copy-contents": "No copiar los {contentType}",
|
||||
"do-not-copy-contents-tip": "Si se activa, los {contentType} en la carpeta compartida no se copiarán a tu versión de Beat Saber. Como resultado, ningún {contentType} permanecerá en tu versión de Beat Saber después de desvincular.",
|
||||
"valid-btn": "Desvincular los {contentType}"
|
||||
},
|
||||
"download-maps": {
|
||||
"search-btn": "Buscar",
|
||||
"loading-maps": "Cargando mapas...",
|
||||
@@ -783,9 +828,14 @@
|
||||
"add-folder": "Añadir Carpeta",
|
||||
"link-folder": "Enlazar Carpeta",
|
||||
"unlink-folder": "Desenlazar Carpeta",
|
||||
"link-all": "Enlazar todo"
|
||||
"link-all": "Enlazar todo",
|
||||
"remove-from-the-list": "Eliminar de la lista"
|
||||
}
|
||||
},
|
||||
"adding-shared-folder": {
|
||||
"title": "Agregar una carpeta compartida",
|
||||
"description": "Es posible que experimentes problemas al vincular la carpeta \"{folder}\". ¿Estás seguro de que deseas agregarla?"
|
||||
},
|
||||
"create-launch-shortcut": {
|
||||
"title": "Crear un atajo",
|
||||
"desc": "Crear un atajo te permitirá iniciar Beat Saber con las opciones seleccionadas sin pasar por BSManager.",
|
||||
@@ -804,13 +854,12 @@
|
||||
"stay": "Recuérdame",
|
||||
"connect-to-meta": "Conectarse a Meta"
|
||||
},
|
||||
"original-version-backup-oculus": {
|
||||
"title": "Atención",
|
||||
"body": {
|
||||
"must-be-installed-once": "Debes tener Beat Saber instalado desde la tienda de Oculus en este dispositivo, de lo contrario, Beat Saber se cerrará automáticamente después de iniciarse.",
|
||||
"will-backup": "Para lanzar esta versión, la carpeta de instalación original de Beat Saber ubicada en tu biblioteca de Oculus será renombrada y se restaurará automáticamente al cerrar Beat Saber."
|
||||
},
|
||||
"not-remind-me": "No volver a recordármelo",
|
||||
"enable-oculus-sideloaded-apps": {
|
||||
"title": "Habilitar Sideloading",
|
||||
"info-1": "Para poder iniciar Beat Saber, se debe habilitar la capacidad de ejecutar aplicaciones en sideloading. BSManager solicitará permisos de administrador para habilitar esta función automáticamente.",
|
||||
"info-2": "La función de sideloading permite iniciar juegos ubicados fuera de la carpeta de la biblioteca de Oculus.",
|
||||
"info-3": "Una vez que se active el sideloading, la función permanecerá activa y ya no se le pedirá que la habilite.",
|
||||
"i-want-to-do-it-myself": "Quiero hacerlo yo mismo",
|
||||
"understood": "Entendido"
|
||||
},
|
||||
"enter-meta-token": {
|
||||
@@ -865,7 +914,8 @@
|
||||
"tags": "tags",
|
||||
"specificities": "general",
|
||||
"requirements": "requisitos",
|
||||
"exclude": "excluir"
|
||||
"exclude": "excluir",
|
||||
"leaderboard": "tabla de clasificación"
|
||||
},
|
||||
"map-types": {
|
||||
"accuracy": "precisión",
|
||||
@@ -916,11 +966,16 @@
|
||||
},
|
||||
"map-specificities": {
|
||||
"automapper": "IA",
|
||||
"ranked": "rankado",
|
||||
"curated": "recomendado",
|
||||
"verified": "verificado",
|
||||
"fullSpread": "panel completo"
|
||||
},
|
||||
"map-leaderboard": {
|
||||
"All": "Todos",
|
||||
"Ranked": "Rankado",
|
||||
"BeatLeader": "BeatLeader",
|
||||
"ScoreSaber": "ScoreSaber"
|
||||
},
|
||||
"map-excludes": {
|
||||
"installed": "instalado"
|
||||
},
|
||||
@@ -1008,86 +1063,6 @@
|
||||
"no-models": "No se encontraron modelos.",
|
||||
"no-internet": "Sin conexión a Internet.",
|
||||
"error-occured": "Ocurrió un error, inténtalo de nuevo más tarde."
|
||||
},
|
||||
"link-models": {
|
||||
"avatar": {
|
||||
"title": "Vincular avatares",
|
||||
"desc": "La vinculación de avatares permite compartir avatares entre todas las versiones. Una vez vinculada, esta versión se beneficiará de los avatares compartidos",
|
||||
"info": "La adición y eliminación de avatares también se compartirá",
|
||||
"keep-models": {
|
||||
"label": "Conservar los avatares",
|
||||
"title": "Conservar los avatares moverá los avatares de la versión actual a la carpeta de avatares compartidos. De lo contrario, se perderán"
|
||||
},
|
||||
"valid-btn": "Vincular avatares"
|
||||
},
|
||||
"saber": {
|
||||
"title": "Vincular sables",
|
||||
"desc": "La vinculación de sables permite compartir sables entre todas las versiones. Una vez vinculada, esta versión se beneficiará de los sables compartidos",
|
||||
"info": "La adición y eliminación de sables también se compartirá",
|
||||
"keep-models": {
|
||||
"label": "Conservar los sables",
|
||||
"title": "Conservar los sables moverá los sables de la versión actual a la carpeta de sables compartidos. De lo contrario, se perderán"
|
||||
},
|
||||
"valid-btn": "Vincular sables"
|
||||
},
|
||||
"platform": {
|
||||
"title": "Vincular plataformas",
|
||||
"desc": "La vinculación de plataformas permite compartir plataformas entre todas las versiones. Una vez vinculada, esta versión se beneficiará de las plataformas compartidas",
|
||||
"info": "La adición y eliminación de plataformas también se compartirá",
|
||||
"keep-models": {
|
||||
"label": "Conservar las plataformas",
|
||||
"title": "Conservar las plataformas moverá las plataformas de la versión actual a la carpeta de plataformas compartidas. De lo contrario, se perderán"
|
||||
},
|
||||
"valid-btn": "Vincular plataformas"
|
||||
},
|
||||
"bloq": {
|
||||
"title": "Vincular bloques",
|
||||
"desc": "La vinculación de bloques permite compartir bloques entre todas las versiones. Una vez vinculada, esta versión se beneficiará de los bloques compartidos",
|
||||
"info": "La adición y eliminación de bloques también se compartirá",
|
||||
"keep-models": {
|
||||
"label": "Conservar los bloques",
|
||||
"title": "Conservar los bloques moverá los bloques de la versión actual a la carpeta de bloques compartidos. De lo contrario, se perderán"
|
||||
},
|
||||
"valid-btn": "Vincular bloques"
|
||||
}
|
||||
},
|
||||
"unlink-models": {
|
||||
"avatar": {
|
||||
"title": "Desvincular avatares",
|
||||
"desc": "Atención, desvincular los avatares ya no permitirá el uso de avatares compartidos para esta versión.",
|
||||
"keep-models": {
|
||||
"label": "Conservar los avatares",
|
||||
"title": "Conservar los avatares creará una copia de los avatares compartidos para la versión actual. En caso contrario, ningún avatar se conservará para esta versión."
|
||||
},
|
||||
"valid-btn": "Desvincular avatares"
|
||||
},
|
||||
"saber": {
|
||||
"title": "Desvincular sables",
|
||||
"desc": "Atención, desvincular los sables ya no permitirá el uso de sables compartidos para esta versión.",
|
||||
"keep-models": {
|
||||
"label": "Conservar los sables",
|
||||
"title": "Conservar los sables creará una copia de los sables compartidos para la versión actual. En caso contrario, ningún sable se conservará para esta versión."
|
||||
},
|
||||
"valid-btn": "Desvincular sables"
|
||||
},
|
||||
"platform": {
|
||||
"title": "Desvincular plataformas",
|
||||
"desc": "Atención, desvincular las plataformas ya no permitirá el uso de plataformas compartidas para esta versión.",
|
||||
"keep-models": {
|
||||
"label": "Conservar las plataformas",
|
||||
"title": "Conservar las plataformas creará una copia de las plataformas compartidas para la versión actual. En caso contrario, ninguna plataforma se conservará para esta versión."
|
||||
},
|
||||
"valid-btn": "Desvincular plataformas"
|
||||
},
|
||||
"bloq": {
|
||||
"title": "Desvincular bloques",
|
||||
"desc": "Atención, desvincular los bloques ya no permitirá el uso de bloques compartidos para esta versión.",
|
||||
"keep-models": {
|
||||
"label": "Conservar los bloques",
|
||||
"title": "Conservar los bloques creará una copia de los bloques compartidos para la versión actual. En caso contrario, ningún bloques se conservará para esta versión."
|
||||
},
|
||||
"valid-btn": "Desvincular bloques"
|
||||
}
|
||||
}
|
||||
},
|
||||
"notifications": {
|
||||
@@ -1164,14 +1139,6 @@
|
||||
"stop-download": "Detener descarga",
|
||||
"cancel-download": "Cancelar descarga",
|
||||
"open-file": "Abrir archivo",
|
||||
"link-playlists": "Vincular listas de reproducción",
|
||||
"link-playlist-desc": "Vincular listas de reproducción permite compartirlas entre todas las versiones. Una vez vinculada, esta versión se beneficiará de las listas de reproducción compartidas",
|
||||
"link-playlist-info": "Añadir y eliminar listas de reproducción también se compartirá",
|
||||
"keep-playlists": "Mantener listas de reproducción",
|
||||
"keep-playlists-tip": "Mantener las listas de reproducción moverá las listas de la versión actual a la carpeta de listas compartidas. De lo contrario, se perderán",
|
||||
"unlink-playlists": "Desvincular listas de reproducción",
|
||||
"unlink-playlist-desc": "Advertencia, desvincular las listas de reproducción ya no permitirá el uso de listas compartidas para esta versión.",
|
||||
"unlink-keep-playlists-tip": "Mantener las listas de reproducción creará una copia de las listas compartidas para la versión actual. De lo contrario, no se mantendrán listas para esta versión.",
|
||||
"delete-playlist-ask": "¿Eliminar lista de reproducción?",
|
||||
"delete-playlists-ask": "¿Eliminar listas de reproducción?",
|
||||
"delete-playlist-desc": "¿Estás seguro de que quieres eliminar la lista de reproducción \"{playlistTitle}\"?",
|
||||
|
||||
@@ -8,6 +8,10 @@
|
||||
"maps": "Maps",
|
||||
"playlists": "Playlists",
|
||||
"models": "Modèles",
|
||||
"avatars": "Avatars",
|
||||
"sabers": "Sabres",
|
||||
"platforms": "Plateformes",
|
||||
"blocks": "Blocs",
|
||||
"cancel": "Annuler",
|
||||
"delete": "Supprimer",
|
||||
"accept": "Accepter",
|
||||
@@ -19,7 +23,13 @@
|
||||
"choose-folder": "Choisir un dossier",
|
||||
"unknown": "Une erreur inconnue s'est produite ¯\\_(ツ)_/¯",
|
||||
"warning": "Attention",
|
||||
"continue": "Continuer"
|
||||
"continue": "Continuer",
|
||||
"shared": "Partagé",
|
||||
"pin": "Épingler",
|
||||
"unpin": "Désépingler"
|
||||
},
|
||||
"title-bar": {
|
||||
"outdated": "obsolète"
|
||||
},
|
||||
"nav-bar": {
|
||||
"add-version": "Ajouter une version",
|
||||
@@ -40,9 +50,15 @@
|
||||
"debug-description": "Active la fenêtre de log pour IPA. Cela affichera la console de débogage utilisée par les mods.",
|
||||
"outdated-tippy": "Cette version est obsolète et certains mods ou fonctionnalités peuvent ne plus fonctionner comme prévu. Préférez utiliser la version recommandée ({recommendedVersion}) de Beat Saber pour profiter des dernières fonctionnalités et correctifs.",
|
||||
"advanced-launch": {
|
||||
"button": "Lancement avancé",
|
||||
"button": "Options de lancement",
|
||||
"placeholder": "Arguments supplémentaires ex: --revert; --nowait"
|
||||
}
|
||||
},
|
||||
"skipsteam": "Ignorer Steam",
|
||||
"skipsteam-description": "Empêche Steam de s'ouvrir automatiquement avec Beat Saber, activez-le si vous utilisez un autre runtime VR comme WiVRn ou Monado avec lequel SteamVR pourrait interférer.",
|
||||
"map-editor": "Éditeur de map",
|
||||
"map-editor-description": "Lance l'éditeur officiel de map de Beat Saber au lieu du jeu.",
|
||||
"proton-logs": "Journaux de Proton",
|
||||
"proton-logs-description": "Active l'enregistrement des journaux Proton pour cette installation de Beat Saber sur \"{versionPath}\"."
|
||||
},
|
||||
"maps": {
|
||||
"search-bar": {
|
||||
@@ -106,6 +122,7 @@
|
||||
"latest": "Récent",
|
||||
"description": "Description",
|
||||
"dropdown": {
|
||||
"import-mods": "Importer des mods",
|
||||
"uninstall-all": "Tout désinstaller",
|
||||
"unselect-all": "Tout désélectionner"
|
||||
}
|
||||
@@ -116,6 +133,10 @@
|
||||
"title": "Mods déjà installées",
|
||||
"description": "Tous les mods séléctionnées sont déjà installées"
|
||||
}
|
||||
},
|
||||
"drop-zone": {
|
||||
"text": "Importez vos mods",
|
||||
"subtext": "Déposez vos fichiers \"zip\" ou \"dll\" ici pour les importer"
|
||||
}
|
||||
},
|
||||
"dropdown": {
|
||||
@@ -129,7 +150,7 @@
|
||||
}
|
||||
},
|
||||
"available-versions": {
|
||||
"title": "Choisis une version",
|
||||
"title": "Télécharger une version",
|
||||
"steam-release": "Notes de version",
|
||||
"dropdown": {
|
||||
"refresh": "Actualiser les versions",
|
||||
@@ -258,6 +279,13 @@
|
||||
"error-notification": {
|
||||
"message": "Une erreur s'est produite, impossible de changer les paramètres des liens symboliques."
|
||||
}
|
||||
},
|
||||
"use-system-proxy": {
|
||||
"title": "Utiliser le proxy du système",
|
||||
"description": "BSManager effectuera des requêtes réseau via votre proxy système.",
|
||||
"error-notification": {
|
||||
"message": "Une erreur est survenue, impossible de modifier les paramètres du proxy système."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -308,6 +336,8 @@
|
||||
"msg": {
|
||||
"401": "Steam ne semble pas vouloir nous laisser télécharger Beat Saber 😢",
|
||||
"404": "Impossible de contacter les serveurs de Steam.",
|
||||
"ExeNotFoundWindows": "\"DepotDownloader.exe\" est manquant. Veuillez vérifier si l'exécutable est mis en quarantaine par votre antivirus.",
|
||||
"ExeNotFoundLinux": "L'exécutable \"DepotDownloader\" est manquanto.",
|
||||
"Password": "Le mot de passe est invalide.",
|
||||
"InvalidCredentials": "Identifiants de connexion invalides, connexion non approuvée, ou trop de tentatives de connexion.",
|
||||
"NoManifest": "Aucun manifest n'a été trouvé.",
|
||||
@@ -341,8 +371,8 @@
|
||||
"UNABLE_TO_GET_MANIFEST": "Impossible d'obtenir le manifest nécessaire au téléchargement.",
|
||||
"VERIFY_INTEGRITY_FAILED": "Une erreur c'est produite lors de la vérification des fichiers.",
|
||||
"SOME_FILES_FAILED_TO_DOWNLOAD": "Certains fichiers n'ont pas pu être téléchargés.",
|
||||
"OCULUS_LOGIN_TIMED_OUT": "Le Token de connexion à mis trop de temps à être récupéré.",
|
||||
"OCULUS_LOGIN_WINDOW_CLOSED_BY_USER": "La fenêtre de connexion à Meta a été fermée.",
|
||||
"META_LOGIN_TIMED_OUT": "Le Token de connexion à mis trop de temps à être récupéré.",
|
||||
"META_LOGIN_WINDOW_CLOSED_BY_USER": "La fenêtre de connexion à Meta a été fermée.",
|
||||
"NO_META_AUTH_TOKEN": "Impossible de récupérer le token de connexion à Meta nécessaire au téléchargement.",
|
||||
"UNKNOWN_ERROR": "Une erreur inconnue s'est produite."
|
||||
}
|
||||
@@ -429,11 +459,13 @@
|
||||
"success": {
|
||||
"titles": {
|
||||
"BS_LAUNCHING": "Lancement...🚀",
|
||||
"STEAM_LAUNCHING": "Steam se lance !"
|
||||
"STEAM_LAUNCHING": "Steam se lance !",
|
||||
"SKIPPING_STEAM_LAUNCH": "Sauter le lancement de Steam"
|
||||
},
|
||||
"msg": {
|
||||
"BS_LAUNCHING": "N'oublie pas de t'échauffer 😉",
|
||||
"STEAM_LAUNCHING": "Beat Saber se lancera automatiquement après Steam."
|
||||
"STEAM_LAUNCHING": "Beat Saber se lancera automatiquement après Steam.",
|
||||
"SKIPPING_STEAM_LAUNCH": "J'espère que vous savez ce que vous faites :)"
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
@@ -526,6 +558,17 @@
|
||||
"no-mods": "Aucun mod n'est installé dans dans cette version 😑"
|
||||
}
|
||||
}
|
||||
},
|
||||
"import-mod": {
|
||||
"titles": {
|
||||
"success": "Importation des mods terminée",
|
||||
"error": "Une erreur est survenue lors de l'importation des mods"
|
||||
},
|
||||
"msgs": {
|
||||
"success": "Mods importés avec succès.",
|
||||
"some-success": "Certains mods ont été importés avec succès.",
|
||||
"no-dlls": "Le(s) fichier(s) ne contient(ent) aucun fichier \"dll\"."
|
||||
}
|
||||
}
|
||||
},
|
||||
"maps": {
|
||||
@@ -582,6 +625,10 @@
|
||||
"ENOSPC": "Le disque est plein, faites de la place et réessayez.",
|
||||
"UNKNOWN_ERROR": "Une erreur inconnue est survenue lors de la liaison du dossier."
|
||||
}
|
||||
},
|
||||
"adding-error": {
|
||||
"title": "L'ajout du dossier partagé a échoué",
|
||||
"msg": "Vous ne pouvez pas ajouter \"{folder}\" aux dossiers partagés."
|
||||
}
|
||||
|
||||
},
|
||||
@@ -738,31 +785,29 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"link-maps": {
|
||||
"title": "Lier les maps",
|
||||
"desc": "La liaison des maps permet de partager les maps entre toute les version. Une fois liée, cette version profitera des maps partagées",
|
||||
"info": "L'ajout et la suppression de maps sera également partagé",
|
||||
"keep-maps": {
|
||||
"label": "Conserver les maps",
|
||||
"title": "Conserver les maps déplacera les maps de la version actuelle dans le dossier des maps partagées. Dans le cas contraire elles seront perdues"
|
||||
},
|
||||
"valid-btn": "Lier les maps"
|
||||
},
|
||||
"unlink-maps": {
|
||||
"title": "Délier les maps",
|
||||
"desc": "Attention, délier les maps ne permettra plus l'utilisation des maps paratagées pour cette version.",
|
||||
"keep-maps": {
|
||||
"label": "Conserver les maps",
|
||||
"title": "Conserver les maps créera une copie des maps partagées pour la version actuelle. Dans le cas contraire, aucune map ne sera conservée pour cette version."
|
||||
},
|
||||
"valid-btn": "Délier les maps"
|
||||
},
|
||||
"delete-duplicate-maps": {
|
||||
"title": "Supprimer les maps ?",
|
||||
"desc": "Seule la map \"{map}\" est en double. Es-tu sûr de vouloir la supprimer ?",
|
||||
"desc-plural": "{nb} maps en double ont été trouvées. Es-tu sûr de vouloir les supprimer ?"
|
||||
}
|
||||
},
|
||||
"link-contents": {
|
||||
"title": "Lier les {contentType}",
|
||||
"p-1": "Lier les {contentType} permet de partager les {contentType} entre toutes les versions ayant cette fonctionnalité activée. Une fois liés, cette version bénéficiera des {contentType} partagés.",
|
||||
"p-2": "Pour profiter de la fonctionnalité de liaison, les {itemsHtml} seront déplacés dans le dossier {sharedHtml}. Ensuite, un lien symbolique pointant vers le dossier partagé des {contentType} remplacera votre dossier original des {contentType}.",
|
||||
"warning": "Veuillez noter que toute modification affectant les {contentType} affectera également toutes les versions ayant cette fonctionnalité activée.",
|
||||
"what-is-a-symbolic-link": "Qu'est-ce qu'un lien symbolique ?",
|
||||
"i-need-help": "J'ai besoin d'aide",
|
||||
"valid-btn": "Lier les {contentType}"
|
||||
},
|
||||
"unlink-contents": {
|
||||
"title": "Délier les {contentType}",
|
||||
"p-1": "Attention, délier les {contentType} désactivera l'utilisation des {contentType} partagés pour cette version, jusqu'à ce qu'elle soit réactivée.",
|
||||
"p-2": "Pour délier vos {contentType}, le lien symbolique pointant vers le dossier {sharedHtml} sera supprimé. Ensuite, les {contentType} dans le dossier partagé seront copiés dans le dossier {itemsHtml} de votre version de Beat Saber.",
|
||||
"do-not-copy-contents": "Ne pas copier les {contentType}",
|
||||
"do-not-copy-contents-tip": "Si activé, les {contentType} dans le dossier partagé ne seront pas copiés dans votre version de Beat Saber. En conséquence, aucun(e)s {contentType} ne restera dans votre version de Beat Saber après la suppression de la liaison.",
|
||||
"valid-btn": "Délier les {contentType}"
|
||||
},
|
||||
"download-maps": {
|
||||
"search-btn": "Rechercher",
|
||||
"loading-maps": "Chargement des maps...",
|
||||
@@ -784,9 +829,14 @@
|
||||
"add-folder": "Ajouter un dossier",
|
||||
"link-folder": "Lier le dossier",
|
||||
"unlink-folder": "Délier le dossier",
|
||||
"link-all": "Tout lier"
|
||||
"link-all": "Tout lier",
|
||||
"remove-from-the-list": "Retirer de la liste"
|
||||
}
|
||||
},
|
||||
"adding-shared-folder": {
|
||||
"title": "Ajout d'un dossier partagé",
|
||||
"description": "Vous pourriez rencontrer des problèmes lors de la liaison du dossier \"{folder}\". Êtes-vous sûr de vouloir l'ajouter ?"
|
||||
},
|
||||
"create-launch-shortcut": {
|
||||
"title": "Créer un raccourci",
|
||||
"desc": "Créer un raccourci te permettra de démarrer Beat Saber avec les options choisies sans passer par BSManager.",
|
||||
@@ -805,13 +855,12 @@
|
||||
"stay": "Se souvenir de moi",
|
||||
"connect-to-meta": "Se connecter à Meta"
|
||||
},
|
||||
"original-version-backup-oculus": {
|
||||
"title": "Attention",
|
||||
"body": {
|
||||
"must-be-installed-once": "Vous devez avoir Beat Saber installé depuis le Oculus Store sur cet appareil, sinon Beat Saber se fermera automatiquement après le lancement.",
|
||||
"will-backup": "Afin de lancer cette version, le dossier d'installation original de Beat Saber se trouvant dans votre bibliothèque Oculus va être renommé et sera automatiquement restauré à l'arrêt de Beat Saber."
|
||||
},
|
||||
"not-remind-me": "Ne plus me rappeler",
|
||||
"enable-oculus-sideloaded-apps": {
|
||||
"title": "Activer le sideloading",
|
||||
"info-1": "Pour lancer Beat Saber, la possibilité d'exécuter des applications en sideloading doit être activée. BSManager demandera les droits administrateur pour activer cette fonctionnalité automatiquement.",
|
||||
"info-2": "La fonctionnalité de sideloading permet de lancer des jeux situés en dehors de votre dossier de bibliothèque Oculus.",
|
||||
"info-3": "Une fois le sideloading activé, la fonctionnalité restera active et vous ne serez plus invité à l’activer.",
|
||||
"i-want-to-do-it-myself": "Je veux le faire moi-même",
|
||||
"understood": "J'ai compris"
|
||||
},
|
||||
"enter-meta-token": {
|
||||
@@ -866,7 +915,8 @@
|
||||
"tags": "tags",
|
||||
"specificities": "général",
|
||||
"requirements": "requis",
|
||||
"exclude": "exclure"
|
||||
"exclude": "exclure",
|
||||
"leaderboard": "tableau des leaders"
|
||||
},
|
||||
"map-types": {
|
||||
"accuracy": "précision",
|
||||
@@ -917,11 +967,16 @@
|
||||
},
|
||||
"map-specificities": {
|
||||
"automapper": "IA",
|
||||
"ranked": "classée",
|
||||
"curated": "recommandée",
|
||||
"verified": "vérifiée",
|
||||
"fullSpread": "panel complet"
|
||||
},
|
||||
"map-leaderboard": {
|
||||
"All": "Tout",
|
||||
"Ranked": "Classée",
|
||||
"BeatLeader": "BeatLeader",
|
||||
"ScoreSaber": "ScoreSaber"
|
||||
},
|
||||
"map-excludes": {
|
||||
"installed": "installé"
|
||||
},
|
||||
@@ -1009,86 +1064,6 @@
|
||||
"no-models": "Aucun modèles trouvés.",
|
||||
"no-internet": "Pas de connexion internet.",
|
||||
"error-occured": "Une erreur est survenue, réessaye plus tard."
|
||||
},
|
||||
"link-models": {
|
||||
"avatar": {
|
||||
"title": "Lier les avatars",
|
||||
"desc": "La liaison des avatars permet de partager les avatars entre toutes les version. Une fois liée, cette version profitera des avatars partagés",
|
||||
"info": "L'ajout et la suppression d'avatars seront également partagés",
|
||||
"keep-models": {
|
||||
"label": "Conserver les avatars",
|
||||
"title": "Conserver les avatars déplacera les avatars de la version actuelle dans le dossier des avatars partagés. Dans le cas contraire ils seront perdus"
|
||||
},
|
||||
"valid-btn": "Lier les avatars"
|
||||
},
|
||||
"saber": {
|
||||
"title": "Lier les sabres",
|
||||
"desc": "La liaison des sabres permet de partager les sabres entre toutes les version. Une fois liée, cette version profitera des sabres partagés",
|
||||
"info": "L'ajout et la suppression de sabres seront également partagés",
|
||||
"keep-models": {
|
||||
"label": "Conserver les sabres",
|
||||
"title": "Conserver les sabres déplacera les sabres de la version actuelle dans le dossier des sabres partagés. Dans le cas contraire ils seront perdus"
|
||||
},
|
||||
"valid-btn": "Lier les sabres"
|
||||
},
|
||||
"platform": {
|
||||
"title": "Lier les plateformes",
|
||||
"desc": "La liaison des plateformes permet de partager les plateformes entre toutes les version. Une fois liée, cette version profitera des plateformes partagées",
|
||||
"info": "L'ajout et la suppression de plateformes seront également partagés",
|
||||
"keep-models": {
|
||||
"label": "Conserver les plateformes",
|
||||
"title": "Conserver les plateformes déplacera les plateformes de la version actuelle dans le dossier des plateformes partagées. Dans le cas contraire ils seront perdus"
|
||||
},
|
||||
"valid-btn": "Lier les plateformes"
|
||||
},
|
||||
"bloq": {
|
||||
"title": "Lier les bloqs",
|
||||
"desc": "La liaison des bloqs permet de partager les bloqs entre toutes les version. Une fois liée, cette version profitera des bloqs partagés",
|
||||
"info": "L'ajout et la suppression de bloqs seront également partagés",
|
||||
"keep-models": {
|
||||
"label": "Conserver les bloqs",
|
||||
"title": "Conserver les bloqs déplacera les bloqs de la version actuelle dans le dossier des bloqs partagés. Dans le cas contraire ils seront perdus"
|
||||
},
|
||||
"valid-btn": "Lier les bloqs"
|
||||
}
|
||||
},
|
||||
"unlink-models": {
|
||||
"avatar": {
|
||||
"title": "Délier les avatars",
|
||||
"desc": "Attention, délier les avatars ne permettra plus l'utilisation des avatars paratagés pour cette version.",
|
||||
"keep-models": {
|
||||
"label": "Conserver les avatars",
|
||||
"title": "Conserver les avatars créera une copie des avatars partagés pour la version actuelle. Dans le cas contraire, aucun avatar ne sera conservé pour cette version."
|
||||
},
|
||||
"valid-btn": "Délier les avatars"
|
||||
},
|
||||
"saber": {
|
||||
"title": "Délier les sabres",
|
||||
"desc": "Attention, délier les sabres ne permettra plus l'utilisation des sabres paratagés pour cette version.",
|
||||
"keep-models": {
|
||||
"label": "Conserver les sabres",
|
||||
"title": "Conserver les sabres créera une copie des sabres partagés pour la version actuelle. Dans le cas contraire, aucun sabre ne sera conservé pour cette version."
|
||||
},
|
||||
"valid-btn": "Délier les sabres"
|
||||
},
|
||||
"platform": {
|
||||
"title": "Délier les plateformes",
|
||||
"desc": "Attention, délier les plateformes ne permettra plus l'utilisation des plateformes paratagées pour cette version.",
|
||||
"keep-models": {
|
||||
"label": "Conserver les plateformes",
|
||||
"title": "Conserver les plateformes créera une copie des plateformes partagées pour la version actuelle. Dans le cas contraire, aucune plateforme ne sera conservée pour cette version."
|
||||
},
|
||||
"valid-btn": "Délier les plateformes"
|
||||
},
|
||||
"bloq": {
|
||||
"title": "Délier les bloqs",
|
||||
"desc": "Attention, délier les bloqs ne permettra plus l'utilisation des bloqs paratagés pour cette version.",
|
||||
"keep-models": {
|
||||
"label": "Conserver les bloqs",
|
||||
"title": "Conserver les bloqs créera une copie des bloqs partagés pour la version actuelle. Dans le cas contraire, aucun bloq ne sera conservé pour cette version."
|
||||
},
|
||||
"valid-btn": "Délier les bloqs"
|
||||
}
|
||||
}
|
||||
},
|
||||
"notifications": {
|
||||
@@ -1165,14 +1140,6 @@
|
||||
"stop-download": "Arrêter le téléchargement",
|
||||
"cancel-download": "Annuler le téléchargement",
|
||||
"open-file": "Ouvrir le fichier",
|
||||
"link-playlists": "Lier les playlists",
|
||||
"link-playlist-desc": "La liaison des playlists permet de partager les playlists entre toute les version. Une fois liée, cette version profitera des playlists partagées",
|
||||
"link-playlist-info": "L'ajout et la suppression de playlists sera également partagé",
|
||||
"keep-playlists": "Conserver les playlists",
|
||||
"keep-playlists-tip": "Conserver les playlists déplacera les playlists de la version actuelle dans le dossier des playlists partagées. Dans le cas contraire elles seront perdues",
|
||||
"unlink-playlists": "Délier les playlists",
|
||||
"unlink-playlist-desc": "Attention, délier les playlists ne permettra plus l'utilisation des playlists paratagées pour cette version.",
|
||||
"unlink-keep-playlists-tip": "Conserver les playlists créera une copie des playlists partagées pour la version actuelle. Dans le cas contraire, aucune playlist ne sera conservée pour cette version.",
|
||||
"delete-playlist-ask": "Supprimer la playlist ?",
|
||||
"delete-playlists-ask": "Supprimer les playlists ?",
|
||||
"delete-playlist-desc": "Es-tu sur de vouloir supprimer la playlist \"{playlistTitle}\" ?",
|
||||
|
||||
@@ -8,6 +8,10 @@
|
||||
"maps": "マップ",
|
||||
"playlists": "プレイリスト",
|
||||
"models": "モデル",
|
||||
"avatars": "アバター",
|
||||
"sabers": "セイバー",
|
||||
"platforms": "プラットフォーム",
|
||||
"blocks": "ブロック",
|
||||
"cancel": "キャンセル",
|
||||
"delete": "削除",
|
||||
"accept": "同意する",
|
||||
@@ -19,7 +23,13 @@
|
||||
"choose-folder": "フォルダーを選択",
|
||||
"unknown": "不明なエラーが発生しました (´・ω・`)",
|
||||
"warning": "警告",
|
||||
"continue": "続ける"
|
||||
"continue": "続ける",
|
||||
"shared": "共有",
|
||||
"pin": "固定",
|
||||
"unpin": "固定解除"
|
||||
},
|
||||
"title-bar": {
|
||||
"outdated": "時代遅れ"
|
||||
},
|
||||
"nav-bar": {
|
||||
"add-version": "バージョンを追加",
|
||||
@@ -40,9 +50,15 @@
|
||||
"debug-description": "IPAの出力ログウィンドウを有効にします。これはMODのデバッグコンソールを表示します。",
|
||||
"outdated-tippy": "このバージョンは古いため、一部のMODや機能が期待通りに動作しない可能性があります。最新の機能やバグ修正を楽しむには、推奨バージョン ({recommendedVersion}) のBeat Saberを使用することをお勧めします。",
|
||||
"advanced-launch": {
|
||||
"button": "高度な起動",
|
||||
"button": "起動オプション",
|
||||
"placeholder": "追加引数 例:--revert; --nowait"
|
||||
}
|
||||
},
|
||||
"skipsteam": "Steamをスキップ",
|
||||
"skipsteam-description": "Beat Saberと一緒にSteamが自動的に開くのを防ぎます。SteamVRが干渉する可能性のあるWiVRnやMonadoなど、別のVRランタイムを使用している場合は有効にしてください。",
|
||||
"map-editor": "マップエディタ",
|
||||
"map-editor-description": "ゲームの代わりに公式のBeat Saberマップエディタを起動する。",
|
||||
"proton-logs": "プロトンログ",
|
||||
"proton-logs-description": "このBeat Saberインストールのプロトンログの記録を「{versionPath}」に有効にします。"
|
||||
},
|
||||
"maps": {
|
||||
"search-bar": {
|
||||
@@ -106,6 +122,7 @@
|
||||
"latest": "最新",
|
||||
"description": "說明",
|
||||
"dropdown": {
|
||||
"import-mods": "モッドをインポート",
|
||||
"uninstall-all": "全てアンインストールする",
|
||||
"unselect-all": "すべて選択解除"
|
||||
}
|
||||
@@ -116,6 +133,10 @@
|
||||
"title": "すでにインストール済みのMOD",
|
||||
"description": "選択したすべてのMODはすでにインストールされています"
|
||||
}
|
||||
},
|
||||
"drop-zone": {
|
||||
"text": "モッドをインポートする",
|
||||
"subtext": "「zip」または「dll」ファイルをここにドロップしてインポートする"
|
||||
}
|
||||
},
|
||||
"dropdown": {
|
||||
@@ -129,7 +150,7 @@
|
||||
}
|
||||
},
|
||||
"available-versions": {
|
||||
"title": "バージョンを選択",
|
||||
"title": "バージョンをダウンロード",
|
||||
"steam-release": "リリースページ",
|
||||
"dropdown": {
|
||||
"refresh": "バージョンの更新",
|
||||
@@ -258,6 +279,13 @@
|
||||
"error-notification": {
|
||||
"message": "エラーが発生しました。シンボリックリンク設定を変更できません。"
|
||||
}
|
||||
},
|
||||
"use-system-proxy": {
|
||||
"title": "システムプロキシを使用",
|
||||
"description": "BSManagerはシステムプロキシを通じてネットワークリクエストを行います。",
|
||||
"error-notification": {
|
||||
"message": "エラーが発生しました。システムプロキシ設定を変更できません。"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -308,6 +336,8 @@
|
||||
"msg": {
|
||||
"401": "SteamがBeat Saberのダウンロードを許可してくれないようだ。😢",
|
||||
"404": "Steamサーバーに接続できません。",
|
||||
"ExeNotFoundWindows": "「DepotDownloader.exe」が見つかりません。実行ファイルがアンチウイルスソフトによって隔離されていないか確認してください。",
|
||||
"ExeNotFoundLinux": "「DepotDownloader」の実行ファイルが見つかりません。",
|
||||
"Password": "パスワードが不正です。",
|
||||
"InvalidCredentials": "無効なログイン認証情報、許可されていない接続、またはログイン試行回数が多すぎます。",
|
||||
"NoManifest": "マニフェストは見つかりませんでした",
|
||||
@@ -341,8 +371,8 @@
|
||||
"UNABLE_TO_GET_MANIFEST": "ダウンロードに必要なマニフェストを取得できません。",
|
||||
"VERIFY_INTEGRITY_FAILED": "ファイルの検証中にエラーが発生しました。",
|
||||
"SOME_FILES_FAILED_TO_DOWNLOAD": "一部のファイルのダウンロードに失敗しました。",
|
||||
"OCULUS_LOGIN_TIMED_OUT": "ログイントークンの取得に時間がかかりすぎました。",
|
||||
"OCULUS_LOGIN_WINDOW_CLOSED_BY_USER": "メタ・ログイン・ウィンドウが閉じられた。",
|
||||
"META_LOGIN_TIMED_OUT": "ログイントークンの取得に時間がかかりすぎました。",
|
||||
"META_LOGIN_WINDOW_CLOSED_BY_USER": "メタ・ログイン・ウィンドウが閉じられた。",
|
||||
"NO_META_AUTH_TOKEN": "ダウンロードに必要な Meta ログイン トークンを取得できません。",
|
||||
"UNKNOWN_ERROR": "不明なエラーが発生しました。"
|
||||
}
|
||||
@@ -429,11 +459,13 @@
|
||||
"success": {
|
||||
"titles": {
|
||||
"BS_LAUNCHING": "起動中...🚀",
|
||||
"STEAM_LAUNCHING": "Steamの起動を確認しました!"
|
||||
"STEAM_LAUNCHING": "Steamの起動を確認しました!",
|
||||
"SKIPPING_STEAM_LAUNCH": "Steamの起動をスキップ"
|
||||
},
|
||||
"msg": {
|
||||
"BS_LAUNCHING": "ウォームアップを忘れずに 😉",
|
||||
"STEAM_LAUNCHING": "Beat SaberはSteamの起動後に自動的に起動します。"
|
||||
"STEAM_LAUNCHING": "Beat SaberはSteamの起動後に自動的に起動します。",
|
||||
"SKIPPING_STEAM_LAUNCH": "あなたが何をしているのか分かっていることを願っています :)"
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
@@ -526,6 +558,17 @@
|
||||
"no-mods": "このバージョンのModはインストールされていません 😑"
|
||||
}
|
||||
}
|
||||
},
|
||||
"import-mod": {
|
||||
"titles": {
|
||||
"success": "モッドのインポートが完了しました",
|
||||
"error": "モッドのインポート中にエラーが発生しました"
|
||||
},
|
||||
"msgs": {
|
||||
"success": "モッドが正常にインポートされました。",
|
||||
"some-success": "一部のモッドが正常にインポートされました。",
|
||||
"no-dlls": "ファイルに \"dll\" ファイルが含まれていません。"
|
||||
}
|
||||
}
|
||||
},
|
||||
"maps": {
|
||||
@@ -582,6 +625,10 @@
|
||||
"ENOSPC": "ディスクがいっぱいです。空き容量を作ってもう一度試してください。",
|
||||
"UNKNOWN_ERROR": "フォルダをリンク中に不明なエラーが発生しました。"
|
||||
}
|
||||
},
|
||||
"adding-error": {
|
||||
"title": "共有フォルダの追加に失敗しました",
|
||||
"msg": "「{folder}」を共有フォルダに追加することはできません。"
|
||||
}
|
||||
},
|
||||
"create-launch-shortcut": {
|
||||
@@ -737,31 +784,29 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"link-maps": {
|
||||
"title": "マップをリンク",
|
||||
"desc": "マップをリンクすると、この機能が有効になっているすべてのバージョン間でマップを共有できます。リンクされると、このバージョンは共有マップの恩恵を受けることができます。",
|
||||
"info": "マップの追加と削除も共有されます。",
|
||||
"keep-maps": {
|
||||
"label": "マップを保持",
|
||||
"title": "マップを保持すると現在のバージョンのすべてのマップを共有マップフォルダーにコピーします。有効していない場合はこのバージョンのマップは保持されず、失われます。"
|
||||
},
|
||||
"valid-btn": "マップをリンク"
|
||||
},
|
||||
"unlink-maps": {
|
||||
"title": "マップのリンクを解除",
|
||||
"desc": "マップのリンクを解除すると、このバージョンでは共有マップを使用できなくなります。",
|
||||
"keep-maps": {
|
||||
"label": "マップを保持",
|
||||
"title": "マップを保持は、リンクを解除した後、共有フォルダから現在のバージョンにすべてのマップをコピーします。これを無効をしても共有マップは失われません。"
|
||||
},
|
||||
"valid-btn": "マップのリンクを解除"
|
||||
},
|
||||
"delete-duplicate-maps": {
|
||||
"title": "マップを削除しますか?",
|
||||
"desc": "マップ「{map}」のみが重複しています。削除してもよろしいですか?",
|
||||
"desc-plural": "{nb}個の重複したマップが見つかりました。削除してもよろしいですか?"
|
||||
}
|
||||
},
|
||||
"link-contents": {
|
||||
"title": "{contentType}をリンクする",
|
||||
"p-1": "{contentType}をリンクすると、この機能が有効なすべてのバージョン間で{contentType}を共有できます。リンクされると、このバージョンは共有された{contentType}を利用できます。",
|
||||
"p-2": "リンク機能を利用するには、すべての{itemsHtml}を{sharedHtml}フォルダに移動します。その後、共有された{contentType}フォルダを指すシンボリックリンクが元の{contentType}フォルダの代わりになります。",
|
||||
"warning": "ご注意ください:{contentType}に影響を与える変更は、この機能が有効なすべてのバージョンにも影響を与えます。",
|
||||
"what-is-a-symbolic-link": "シンボリックリンクとは何ですか?",
|
||||
"i-need-help": "助けが必要です",
|
||||
"valid-btn": "{contentType}をリンクする"
|
||||
},
|
||||
"unlink-contents": {
|
||||
"title": "{contentType}のリンクを解除する",
|
||||
"p-1": "注意してください:{contentType}のリンクを解除すると、この機能が再度有効化されるまで、このバージョンで共有された{contentType}を使用できなくなります。",
|
||||
"p-2": "{contentType}のリンクを解除するには、{sharedHtml}を指すシンボリックリンクが削除されます。その後、共有フォルダ内のすべての{contentType}がBeat Saberのバージョン内の{itemsHtml}フォルダにコピーされます。",
|
||||
"do-not-copy-contents": "{contentType}をコピーしない",
|
||||
"do-not-copy-contents-tip": "有効にすると、共有フォルダ内の{contentType}はBeat Saberのバージョンにコピーされません。その結果、リンク解除後にBeat Saberバージョン内に{contentType}は残りません。",
|
||||
"valid-btn": "{contentType}のリンクを解除する"
|
||||
},
|
||||
"download-maps": {
|
||||
"search-btn": "検索 ",
|
||||
"loading-maps": "マップを読込中...",
|
||||
@@ -783,9 +828,14 @@
|
||||
"add-folder": "フォルダーを追加",
|
||||
"link-folder": "フォルダーをリンク",
|
||||
"unlink-folder": "フォルダーのリンクを解除",
|
||||
"link-all": "すべてリンク"
|
||||
"link-all": "すべてリンク",
|
||||
"remove-from-the-list": "リストから削除"
|
||||
}
|
||||
},
|
||||
"adding-shared-folder": {
|
||||
"title": "共有フォルダの追加",
|
||||
"description": "「{folder}」フォルダをリンクするときに問題が発生する可能性があります。追加してもよろしいですか?"
|
||||
},
|
||||
"create-launch-shortcut": {
|
||||
"title": "ショートカットを作成",
|
||||
"desc": "ショートカットを作成すると、BSManagerを起動せずに、直接Beat Saberを起動できます。",
|
||||
@@ -804,14 +854,13 @@
|
||||
"stay": "記憶する",
|
||||
"connect-to-meta": "Metaに接続する"
|
||||
},
|
||||
"original-version-backup-oculus": {
|
||||
"title": "注意",
|
||||
"body": {
|
||||
"must-be-installed-once": "このデバイスにOculusストアからBeat Saberをインストールしておく必要があります。そうしないと、Beat Saberは起動後に自動的に閉じます。",
|
||||
"will-backup": "このバージョンを起動するために、OculusライブラリにあるBeat Saberの元のインストールフォルダは名前が変更され、Beat Saberの終了時に自動的に復元されます。"
|
||||
},
|
||||
"not-remind-me": "二度と表示しないでください",
|
||||
"understood": "分かった"
|
||||
"enable-oculus-sideloaded-apps": {
|
||||
"title": "サイドローディングを有効化",
|
||||
"info-1": "Beat Saberを起動するには、サイドローディングアプリを実行する機能を有効にする必要があります。BSManagerは、この機能を自動的に有効にするために管理者権限を要求します。",
|
||||
"info-2": "サイドローディング機能により、Oculusライブラリフォルダ外にあるゲームを起動できます。",
|
||||
"info-3": "サイドローディングを有効化すると、この機能はアクティブなままとなり、再度有効化を求められることはありません。",
|
||||
"i-want-to-do-it-myself": "自分でやりたい",
|
||||
"understood": "了解しました"
|
||||
},
|
||||
"enter-meta-token": {
|
||||
"title": "Oculusトークン",
|
||||
@@ -865,7 +914,8 @@
|
||||
"tags": "タグ",
|
||||
"specificities": "一般",
|
||||
"requirements": "要Mod",
|
||||
"exclude": "除外する"
|
||||
"exclude": "除外する",
|
||||
"leaderboard": "ランキング"
|
||||
},
|
||||
"map-types": {
|
||||
"accuracy": "正確",
|
||||
@@ -916,11 +966,16 @@
|
||||
},
|
||||
"map-specificities": {
|
||||
"automapper": "AI",
|
||||
"ranked": "ランク入賞",
|
||||
"curated": "厳選済み",
|
||||
"verified": "認証済み",
|
||||
"fullSpread": "フルスプレッド"
|
||||
},
|
||||
"map-leaderboard": {
|
||||
"All": "すべて",
|
||||
"Ranked": "ランク入賞",
|
||||
"BeatLeader": "BeatLeader",
|
||||
"ScoreSaber": "ScoreSaber"
|
||||
},
|
||||
"map-excludes": {
|
||||
"installed": "インストール済み"
|
||||
},
|
||||
@@ -1008,86 +1063,6 @@
|
||||
"no-models": "モデルは見つかりませんでした。",
|
||||
"no-internet": "インターネットに接続できませんでした。",
|
||||
"error-occured": "エラーが発生しました。もう一度お試しください."
|
||||
},
|
||||
"link-models": {
|
||||
"avatar": {
|
||||
"title": "アバターをリンク",
|
||||
"desc": "アバターをリンクすると、この機能が有効になっているすべてのバージョン間でアバターを共有できます。また、リンクすることでこのバージョンは共有アバターの恩恵を受けることができます。",
|
||||
"info": "アバターの追加と削除も共有されます",
|
||||
"keep-models": {
|
||||
"label": "アバターを保持",
|
||||
"title": "アバターを保持すると、現在のバージョンからすべてのアバターが共有アバターフォルダーにコピーされます。有効していない場合はこのバージョンのアバターは保持されず、それらは失われます。"
|
||||
},
|
||||
"valid-btn": "アバターをリンク"
|
||||
},
|
||||
"saber": {
|
||||
"title": "セーバーをリンク",
|
||||
"desc": "セーバーをリンクすると、この機能が有効になっているすべてのバージョン間でセーバーを共有できます。また、リンクすることでこのバージョンは共有セーバーの恩恵を受けることができます。",
|
||||
"info": "セーバーの追加と削除も共有されます",
|
||||
"keep-models": {
|
||||
"label": "セーバーを保持",
|
||||
"title": "セーバーを保持すると、現在のバージョンからすべてのセーバーが共有vフォルダーにコピーされます。有効していない場合はこのバージョンのセーバーは保持されず、それらは失われます。"
|
||||
},
|
||||
"valid-btn": "セーバーをリンク"
|
||||
},
|
||||
"platform": {
|
||||
"title": "プラットフォームをリンク",
|
||||
"desc": "プラットフォームをリンクすると、この機能が有効になっているすべてのバージョン間でプラットフォームを共有できます。また、リンクすることでこのバージョンは共有プラットフォームの恩恵を受けることができます。",
|
||||
"info": "プラットフォームの追加と削除も共有されます",
|
||||
"keep-models": {
|
||||
"label": "プラットフォームを保持",
|
||||
"title": "プラットフォームを保持すると、現在のバージョンからすべてのプラットフォームが共有プラットフォームフォルダーにコピーされます。有効していない場合はこのバージョンのプラットフォームは保持されず、それらは失われます。"
|
||||
},
|
||||
"valid-btn": "プラットフォームをリンク"
|
||||
},
|
||||
"bloq": {
|
||||
"title": "ノーツをリンク",
|
||||
"desc": "ノーツをリンクすると、この機能が有効になっているすべてのバージョン間でノーツを共有できます。また、リンクすることでこのバージョンは共有ノーツの恩恵を受けることができます。",
|
||||
"info": "ノーツの追加と削除も共有されます",
|
||||
"keep-models": {
|
||||
"label": "ノーツを保持",
|
||||
"title": "ノーツを保持すると、現在のバージョンからすべてのノーツが共有ノーツフォルダーにコピーされます。有効していない場合はこのバージョンのノーツは保持されず、それらは失われます。"
|
||||
},
|
||||
"valid-btn": "ノーツをリンク"
|
||||
}
|
||||
},
|
||||
"unlink-models": {
|
||||
"avatar": {
|
||||
"title": "アバターのリンクを解除",
|
||||
"desc": "アバターのリンクを解除すると、このバージョンでは共有アバターが使用できなくなります。",
|
||||
"keep-models": {
|
||||
"label": "アバターを保持する",
|
||||
"title": "アバターを保持すると、リンクを解除した後、共有フォルダにあるすべてのアバターが現在のバージョンにコピーされます。これが無効にしても共有アバターは失われません。"
|
||||
},
|
||||
"valid-btn": "アバターのリンクを解除"
|
||||
},
|
||||
"saber": {
|
||||
"title": "セーバーのリンクを解除",
|
||||
"desc": "セーバーのリンクを解除すると、このバージョンでは共有セーバーが使用できなくなります。",
|
||||
"keep-models": {
|
||||
"label": "セーバーを保持する",
|
||||
"title": "セーバーを保持すると、リンクを解除した後、共有フォルダにあるすべてのセーバーが現在のバージョンにコピーされます。これが無効にしても共有セーバーは失われません。"
|
||||
},
|
||||
"valid-btn": "セーバーのリンクを解除"
|
||||
},
|
||||
"platform": {
|
||||
"title": "プラットフォームのリンクを解除",
|
||||
"desc": "プラットフォームのリンクを解除すると、このバージョンでは共有プラットフォームが使用できなくなります。",
|
||||
"keep-models": {
|
||||
"label": "プラットフォームを保持する",
|
||||
"title": "プラットフォームを保持すると、リンクを解除した後、共有フォルダにあるすべてのプラットフォームが現在のバージョンにコピーされます。これが無効にしても共有プラットフォームは失われません。"
|
||||
},
|
||||
"valid-btn": "プラットフォームのリンクを解除"
|
||||
},
|
||||
"bloq": {
|
||||
"title": "ノーツのリンクを解除",
|
||||
"desc": "ノーツのリンクを解除すると、このバージョンでは共有ノーツが使用できなくなります。",
|
||||
"keep-models": {
|
||||
"label": "ノーツを保持する",
|
||||
"title": "ノーツを保持すると、リンクを解除した後、共有フォルダにあるすべてのノーツが現在のバージョンにコピーされます。これが無効にしても共有ノーツは失われません。"
|
||||
},
|
||||
"valid-btn": "ノーツのリンクを解除"
|
||||
}
|
||||
}
|
||||
},
|
||||
"notifications": {
|
||||
@@ -1164,14 +1139,6 @@
|
||||
"stop-download": "ダウンロードを停止",
|
||||
"cancel-download": "ダウンロードをキャンセル",
|
||||
"open-file": "ファイルを開く",
|
||||
"link-playlists": "プレイリストをリンク",
|
||||
"link-playlist-desc": "プレイリストをリンクすると、すべてのバージョン間でプレイリストを共有できます。リンクすると、このバージョンは共有プレイリストの恩恵を受けます",
|
||||
"link-playlist-info": "プレイリストの追加と削除も共有されます",
|
||||
"keep-playlists": "プレイリストを保持",
|
||||
"keep-playlists-tip": "プレイリストを保持すると、現在のバージョンのプレイリストが共有プレイリストフォルダに移動されます。そうしない場合、それらは失われます",
|
||||
"unlink-playlists": "プレイリストのリンクを解除",
|
||||
"unlink-playlist-desc": "警告:プレイリストのリンクを解除すると、このバージョンでは共有プレイリストを使用できなくなります。",
|
||||
"unlink-keep-playlists-tip": "プレイリストを保持すると、現在のバージョン用に共有プレイリストのコピーが作成されます。そうしない場合、このバージョンのプレイリストは保持されません。",
|
||||
"delete-playlist-ask": "プレイリストを削除しますか?",
|
||||
"delete-playlists-ask": "プレイリストを削除しますか?",
|
||||
"delete-playlist-desc": "プレイリスト「{playlistTitle}」を削除してもよろしいですか?",
|
||||
|
||||
@@ -8,6 +8,10 @@
|
||||
"maps": "맵",
|
||||
"playlists": "재생 목록",
|
||||
"models": "모델",
|
||||
"avatars": "아바타",
|
||||
"sabers": "세이버",
|
||||
"platforms": "플랫폼",
|
||||
"blocks": "블록",
|
||||
"cancel": "취소",
|
||||
"delete": "삭제",
|
||||
"accept": "동의",
|
||||
@@ -19,7 +23,13 @@
|
||||
"choose-folder": "폴더를 선택하세요",
|
||||
"unknown": "알 수 없는 오류가 발생했습니다 (´・ω・`)",
|
||||
"warning": "경고",
|
||||
"continue": "계속"
|
||||
"continue": "계속",
|
||||
"shared": "공유",
|
||||
"pin": "고정",
|
||||
"unpin": "고정 해제"
|
||||
},
|
||||
"title-bar": {
|
||||
"outdated": "구식"
|
||||
},
|
||||
"nav-bar": {
|
||||
"add-version": "버전 추가",
|
||||
@@ -40,9 +50,15 @@
|
||||
"debug-description": "IPA 출력 로그 창을 활성화하여 MOD의 디버그 콘솔을 표시합니다.",
|
||||
"outdated-tippy": "이 버전은 오래되어 일부 모드나 기능이 예상대로 작동하지 않을 수 있습니다. 최신 기능과 버그 수정을 사용하려면 권장 버전({recommendedVersion})의 Beat Saber를 사용하는 것이 좋습니다.",
|
||||
"advanced-launch": {
|
||||
"button": "고급 실행",
|
||||
"button": "실행 옵션",
|
||||
"placeholder": "추가 인수 예: --revert; --nowait"
|
||||
}
|
||||
},
|
||||
"skipsteam": "Steam 실행 건너뛰기",
|
||||
"skipsteam-description": "Beat Saber와 함께 Steam이 자동으로 열리는 것을 방지합니다. SteamVR이 간섭할 수 있는 WiVRn 또는 Monado와 같은 다른 VR 런타임을 사용하는 경우 활성화하세요.",
|
||||
"map-editor": "맵 편집기",
|
||||
"map-editor-description": "게임 대신 공식 Beat Saber 맵 편집기를 실행해.",
|
||||
"proton-logs": "프로톤 로그",
|
||||
"proton-logs-description": "이 Beat Saber 설치에 대한 프로톤 로그 기록을 「{versionPath}」로 활성화합니다."
|
||||
},
|
||||
"maps": {
|
||||
"search-bar": {
|
||||
@@ -106,6 +122,7 @@
|
||||
"latest": "최신",
|
||||
"description": "설명",
|
||||
"dropdown": {
|
||||
"import-mods": "모드 가져오기",
|
||||
"uninstall-all": "모두 제거",
|
||||
"unselect-all": "모두 선택 해제"
|
||||
}
|
||||
@@ -116,6 +133,10 @@
|
||||
"title": "이미 설치된 모드",
|
||||
"description": "선택한 모든 모드가 이미 설치되었습니다."
|
||||
}
|
||||
},
|
||||
"drop-zone": {
|
||||
"text": "모드를 가져오기",
|
||||
"subtext": "\"zip\" 또는 \"dll\" 파일을 여기에 드롭하여 가져오기"
|
||||
}
|
||||
},
|
||||
"dropdown": {
|
||||
@@ -258,6 +279,13 @@
|
||||
"error-notification": {
|
||||
"message": "오류가 발생했습니다. 심볼릭 링크 설정을 변경할 수 없습니다."
|
||||
}
|
||||
},
|
||||
"use-system-proxy": {
|
||||
"title": "시스템 프록시 사용",
|
||||
"description": "BSManager는 시스템 프록시를 통해 네트워크 요청을 수행합니다.",
|
||||
"error-notification": {
|
||||
"message": "오류가 발생했습니다. 시스템 프록시 설정을 변경할 수 없습니다."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -308,6 +336,8 @@
|
||||
"msg": {
|
||||
"401": "Steam이 Beat Saber 다운로드를 허용하지 않는 것 같습니다 😢",
|
||||
"404": "Steam 서버에 연결할 수 없습니다.",
|
||||
"ExeNotFoundWindows": "「DepotDownloader.exe」가 없습니다. 실행 파일이 바이러스 백신 프로그램에 의해 격리되었는지 확인하십시오.",
|
||||
"ExeNotFoundLinux": "「DepotDownloader」 실행 파일이 없습니다.",
|
||||
"Password": "비밀번호가 올바르지 않습니다.",
|
||||
"InvalidCredentials": "유효하지 않은 로그인 자격 증명, 허가되지 않은 연결 또는 너무 많은 로그인 시도입니다.",
|
||||
"NoManifest": "매니페스트를 찾을 수 없습니다",
|
||||
@@ -341,8 +371,8 @@
|
||||
"UNABLE_TO_GET_MANIFEST": "다운로드에 필요한 매니페스트를 가져올 수 없습니다.",
|
||||
"VERIFY_INTEGRITY_FAILED": "파일 검증 중 오류가 발생했습니다.",
|
||||
"SOME_FILES_FAILED_TO_DOWNLOAD": "일부 파일을 다운로드하지 못했습니다.",
|
||||
"OCULUS_LOGIN_TIMED_OUT": "로그인 토큰을 얻는 데 시간이 너무 오래 걸렸습니다.",
|
||||
"OCULUS_LOGIN_WINDOW_CLOSED_BY_USER": "Meta 로그인 창이 닫혔습니다.",
|
||||
"META_LOGIN_TIMED_OUT": "로그인 토큰을 얻는 데 시간이 너무 오래 걸렸습니다.",
|
||||
"META_LOGIN_WINDOW_CLOSED_BY_USER": "Meta 로그인 창이 닫혔습니다.",
|
||||
"NO_META_AUTH_TOKEN": "다운로드에 필요한 Meta 로그인 토큰을 얻을 수 없습니다.",
|
||||
"UNKNOWN_ERROR": "알 수 없는 오류가 발생했습니다."
|
||||
}
|
||||
@@ -429,11 +459,13 @@
|
||||
"success": {
|
||||
"titles": {
|
||||
"BS_LAUNCHING": "실행 중...🚀",
|
||||
"STEAM_LAUNCHING": "Steam 실행 확인 완료!"
|
||||
"STEAM_LAUNCHING": "Steam 실행 확인 완료!",
|
||||
"SKIPPING_STEAM_LAUNCH": "Steam 실행 건너뛰기"
|
||||
},
|
||||
"msg": {
|
||||
"BS_LAUNCHING": "워밍업을 잊지 마세요 😉",
|
||||
"STEAM_LAUNCHING": "Beat Saber는 Steam 실행 후 자동으로 시작됩니다."
|
||||
"STEAM_LAUNCHING": "Beat Saber는 Steam 실행 후 자동으로 시작됩니다.",
|
||||
"SKIPPING_STEAM_LAUNCH": "신이 무엇을 하고 있는지 알고 있기를 바랍니다 :)"
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
@@ -526,6 +558,17 @@
|
||||
"no-mods": "이 버전에 설치된 Mod가 없습니다 😑"
|
||||
}
|
||||
}
|
||||
},
|
||||
"import-mod": {
|
||||
"titles": {
|
||||
"success": "모드 임포트 완료",
|
||||
"error": "모드 임포트 중 오류가 발생했습니다"
|
||||
},
|
||||
"msgs": {
|
||||
"success": "모드가 성공적으로 임포트되었습니다.",
|
||||
"some-success": "일부 모드가 성공적으로 임포트되었습니다.",
|
||||
"no-dlls": "파일에 \"dll\" 파일이 포함되어 있지 않습니다."
|
||||
}
|
||||
}
|
||||
},
|
||||
"maps": {
|
||||
@@ -582,6 +625,10 @@
|
||||
"ENOSPC": "디스크가 가득 찼습니다. 공간을 확보하고 다시 시도하세요.",
|
||||
"UNKNOWN_ERROR": "폴더를 연결하는 중 알 수 없는 오류가 발생했습니다."
|
||||
}
|
||||
},
|
||||
"adding-error": {
|
||||
"title": "공유 폴더 추가에 실패했습니다.",
|
||||
"msg": "공유 폴더에 「{folder}」을 추가할 수 없습니다."
|
||||
}
|
||||
},
|
||||
"create-launch-shortcut": {
|
||||
@@ -737,31 +784,29 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"link-maps": {
|
||||
"title": "맵 연결",
|
||||
"desc": "맵을 연결하면 이 기능이 활성화된 모든 버전 간에 맵을 공유할 수 있습니다. 연결되면 이 버전은 공유 맵을 활용할 수 있습니다.",
|
||||
"info": "맵 추가 및 삭제도 공유됩니다.",
|
||||
"keep-maps": {
|
||||
"label": "맵 유지",
|
||||
"title": "맵 유지를 선택하면 현재 버전의 모든 맵이 공유 맵 폴더에 복사됩니다. 활성화하지 않으면 이 버전의 맵은 유지되지 않고 손실됩니다."
|
||||
},
|
||||
"valid-btn": "맵 연결"
|
||||
},
|
||||
"unlink-maps": {
|
||||
"title": "맵 연결 해제",
|
||||
"desc": "맵 연결을 해제하면 이 버전에서 공유 맵을 사용할 수 없습니다.",
|
||||
"keep-maps": {
|
||||
"label": "맵 유지",
|
||||
"title": "맵 유지를 선택하면 연결을 해제한 후 공유 폴더에서 현재 버전으로 모든 맵이 복사됩니다. 이 옵션을 비활성화해도 공유 맵은 손실되지 않습니다."
|
||||
},
|
||||
"valid-btn": "맵 연결 해제"
|
||||
},
|
||||
"delete-duplicate-maps": {
|
||||
"title": "맵을 삭제하시겠습니까?",
|
||||
"desc": "맵 {map}만 중복되었습니다. 삭제하시겠습니까?",
|
||||
"desc-plural": "{nb}개의 중복된 맵이 발견되었습니다. 삭제하시겠습니까?"
|
||||
}
|
||||
},
|
||||
"link-contents": {
|
||||
"title": "{contentType} 링크",
|
||||
"p-1": "{contentType}를 링크하면 이 기능이 활성화된 모든 버전 간에 {contentType}를 공유할 수 있습니다. 링크된 후에는 이 버전이 공유된 {contentType}를 사용할 수 있게 됩니다.",
|
||||
"p-2": "링크 기능을 사용하려면 모든 {itemsHtml}를 {sharedHtml} 폴더로 이동해야 합니다. 그런 다음, 공유된 {contentType} 폴더를 가리키는 심볼릭 링크가 기존의 {contentType} 폴더를 대체합니다.",
|
||||
"warning": "주의하세요: {contentType}에 영향을 미치는 모든 변경 사항은 이 기능이 활성화된 모든 버전에 영향을 미칩니다.",
|
||||
"what-is-a-symbolic-link": "심볼릭 링크란 무엇인가요?",
|
||||
"i-need-help": "도움이 필요합니다",
|
||||
"valid-btn": "{contentType} 링크"
|
||||
},
|
||||
"unlink-contents": {
|
||||
"title": "{contentType} 링크 해제",
|
||||
"p-1": "주의하세요: {contentType} 링크를 해제하면 이 기능이 다시 활성화될 때까지 이 버전에서 공유된 {contentType}를 사용할 수 없게 됩니다.",
|
||||
"p-2": "{contentType} 링크를 해제하려면 {sharedHtml}을 가리키는 심볼릭 링크가 제거됩니다. 그 후, 공유 폴더에 있는 모든 {contentType}가 Beat Saber 버전의 {itemsHtml} 폴더로 복사됩니다.",
|
||||
"do-not-copy-contents": "{contentType}를 복사하지 않기",
|
||||
"do-not-copy-contents-tip": "활성화되면, 공유 폴더에 있는 {contentType}가 Beat Saber 버전으로 복사되지 않습니다. 그 결과, 링크 해제 후 Beat Saber 버전에 {contentType}가 남지 않게 됩니다.",
|
||||
"valid-btn": "{contentType} 링크 해제"
|
||||
},
|
||||
"download-maps": {
|
||||
"search-btn": "검색",
|
||||
"loading-maps": "맵을 로딩 중...",
|
||||
@@ -783,9 +828,14 @@
|
||||
"add-folder": "폴더 추가",
|
||||
"link-folder": "폴더 연결",
|
||||
"unlink-folder": "폴더 연결 해제",
|
||||
"link-all": "모두 연결"
|
||||
"link-all": "모두 연결",
|
||||
"remove-from-the-list": "목록에서 제거"
|
||||
}
|
||||
},
|
||||
"adding-shared-folder": {
|
||||
"title": "공유 폴더 추가",
|
||||
"description": "「{folder}」폴더를 연결할 때 문제가 발생할 수 있습니다. 정말 추가하시겠습니까?"
|
||||
},
|
||||
"create-launcher-shortcut": {
|
||||
"title": "런처 바로가기 만들기",
|
||||
"description": "바탕화면에 실행 파일의 바로가기를 만들어 BSManager를 손쉽게 실행하세요.",
|
||||
@@ -804,13 +854,12 @@
|
||||
"stay": "기억하기",
|
||||
"connect-to-meta": "Meta에 연결하기"
|
||||
},
|
||||
"original-version-backup-oculus": {
|
||||
"title": "주의",
|
||||
"body": {
|
||||
"must-be-installed-once": "이 장치에 Oculus 스토어에서 Beat Saber를 설치해야 합니다. 그렇지 않으면 Beat Saber가 실행 후 자동으로 종료됩니다..",
|
||||
"will-backup": "이 버전을 실행하기 위해 Oculus 라이브러리에 있는 Beat Saber의 원래 설치 폴더의 이름이 변경되며, Beat Saber가 종료되면 자동으로 복원됩니다."
|
||||
},
|
||||
"not-remind-me": "다시 표시하지 않기",
|
||||
"enable-oculus-sideloaded-apps": {
|
||||
"title": "사이드로딩 활성화",
|
||||
"info-1": "Beat Saber를 실행하려면 사이드로딩 앱을 실행할 수 있는 기능을 활성화해야 합니다. BSManager는 이 기능을 자동으로 활성화하기 위해 관리자 권한을 요청할 것입니다.",
|
||||
"info-2": "사이드로딩 기능을 사용하면 Oculus 라이브러리 폴더 외부에 있는 게임을 실행할 수 있습니다.",
|
||||
"info-3": "사이드로딩(sideloading)이 활성화되면 이 기능은 계속 활성 상태를 유지하며, 더 이상 활성화를 요청받지 않습니다.",
|
||||
"i-want-to-do-it-myself": "스스로 하고 싶습니다",
|
||||
"understood": "알겠습니다"
|
||||
},
|
||||
"enter-meta-token": {
|
||||
@@ -1008,86 +1057,6 @@
|
||||
"no-models": "모델을 찾을 수 없습니다.",
|
||||
"no-internet": "인터넷에 연결할 수 없습니다.",
|
||||
"error-occured": "오류가 발생했습니다. 다시 시도해 주세요."
|
||||
},
|
||||
"link-models": {
|
||||
"avatar": {
|
||||
"title": "아바타 링크",
|
||||
"desc": "아바타를 링크하면 이 기능이 활성화된 모든 버전 간에 아바타를 공유할 수 있습니다. 또한 링크하면 이 버전이 공유 아바타의 혜택을 받을 수 있습니다.",
|
||||
"info": "아바타의 추가 및 삭제도 공유됩니다.",
|
||||
"keep-models": {
|
||||
"label": "아바타 유지",
|
||||
"title": "아바타를 유지하면 현재 버전에서 모든 아바타가 공유 아바타 폴더에 복사됩니다. 활성화하지 않으면 이 버전의 아바타가 유지되지 않으며, 그들은 사라집니다."
|
||||
},
|
||||
"valid-btn": "아바타 링크"
|
||||
},
|
||||
"saber": {
|
||||
"title": "세이버 링크",
|
||||
"desc": "세이버를 링크하면 이 기능이 활성화된 모든 버전 간에 세이버를 공유할 수 있습니다. 또한 링크하면 이 버전이 공유 세이버의 혜택을 받을 수 있습니다.",
|
||||
"info": "세이버의 추가 및 삭제도 공유됩니다.",
|
||||
"keep-models": {
|
||||
"label": "세이버 유지",
|
||||
"title": "세이버를 유지하면 현재 버전에서 모든 세이버가 공유 세이버 폴더에 복사됩니다. 활성화하지 않으면 이 버전의 세이버가 유지되지 않으며, 그들은 사라집니다."
|
||||
},
|
||||
"valid-btn": "세이버 링크"
|
||||
},
|
||||
"platform": {
|
||||
"title": "플랫폼 링크",
|
||||
"desc": "플랫폼을 링크하면 이 기능이 활성화된 모든 버전 간에 플랫폼을 공유할 수 있습니다. 또한 링크하면 이 버전이 공유 플랫폼의 혜택을 받을 수 있습니다.",
|
||||
"info": "플랫폼의 추가 및 삭제도 공유됩니다.",
|
||||
"keep-models": {
|
||||
"label": "플랫폼 유지",
|
||||
"title": "플랫폼을 유지하면 현재 버전에서 모든 플랫폼이 공유 플랫폼 폴더에 복사됩니다. 활성화하지 않으면 이 버전의 플랫폼이 유지되지 않으며, 그들은 사라집니다."
|
||||
},
|
||||
"valid-btn": "플랫폼 링크"
|
||||
},
|
||||
"bloq": {
|
||||
"title": "노트 링크",
|
||||
"desc": "노트를 링크하면 이 기능이 활성화된 모든 버전 간에 노트를 공유할 수 있습니다. 또한 링크하면 이 버전이 공유 노트의 혜택을 받을 수 있습니다.",
|
||||
"info": "노트의 추가 및 삭제도 공유됩니다.",
|
||||
"keep-models": {
|
||||
"label": "노트 유지",
|
||||
"title": "노트를 유지하면 현재 버전에서 모든 노트가 공유 노트 폴더에 복사됩니다. 활성화하지 않으면 이 버전의 노트가 유지되지 않으며, 그들은 사라집니다."
|
||||
},
|
||||
"valid-btn": "노트 링크"
|
||||
}
|
||||
},
|
||||
"unlink-models": {
|
||||
"avatar": {
|
||||
"title": "아바타 링크 해제",
|
||||
"desc": "아바타의 링크를 해제하면 이 버전에서는 공유 아바타를 사용할 수 없습니다.",
|
||||
"keep-models": {
|
||||
"label": "아바타 유지",
|
||||
"title": "아바타를 유지하면 링크를 해제한 후 공유 폴더에 있는 모든 아바타가 현재 버전에 복사됩니다. 이것을 비활성화하더라도 공유 아바타는 사라지지 않습니다."
|
||||
},
|
||||
"valid-btn": "아바타 링크 해제"
|
||||
},
|
||||
"saber": {
|
||||
"title": "세이버 링크 해제",
|
||||
"desc": "세이버의 링크를 해제하면 이 버전에서는 공유 세이버를 사용할 수 없습니다.",
|
||||
"keep-models": {
|
||||
"label": "세이버 유지",
|
||||
"title": "세이버를 유지하면 링크를 해제한 후 공유 폴더에 있는 모든 세이버가 현재 버전에 복사됩니다. 이것을 비활성화하더라도 공유 세이버는 사라지지 않습니다."
|
||||
},
|
||||
"valid-btn": "세이버 링크 해제"
|
||||
},
|
||||
"platform": {
|
||||
"title": "플랫폼 링크 해제",
|
||||
"desc": "플랫폼의 링크를 해제하면 이 버전에서는 공유 플랫폼을 사용할 수 없습니다.",
|
||||
"keep-models": {
|
||||
"label": "플랫폼 유지",
|
||||
"title": "플랫폼을 유지하면 링크를 해제한 후 공유 폴더에 있는 모든 플랫폼이 현재 버전에 복사됩니다. 이것을 비활성화하더라도 공유 플랫폼은 사라지지 않습니다."
|
||||
},
|
||||
"valid-btn": "플랫폼 링크 해제"
|
||||
},
|
||||
"bloq": {
|
||||
"title": "노트 링크 해제",
|
||||
"desc": "노트의 링크를 해제하면 이 버전에서는 공유 노트를 사용할 수 없습니다.",
|
||||
"keep-models": {
|
||||
"label": "노트 유지",
|
||||
"title": "노트를 유지하면 링크를 해제한 후 공유 폴더에 있는 모든 노트가 현재 버전에 복사됩니다. 이것을 비활성화하더라도 공유 노트는 사라지지 않습니다."
|
||||
},
|
||||
"valid-btn": "노트 링크 해제"
|
||||
}
|
||||
}
|
||||
},
|
||||
"notifications": {
|
||||
@@ -1164,14 +1133,6 @@
|
||||
"stop-download": "다운로드 중지",
|
||||
"cancel-download": "다운로드 취소",
|
||||
"open-file": "파일 열기",
|
||||
"link-playlists": "재생목록 링크",
|
||||
"link-playlist-desc": "재생목록을 링크하면 모든 버전 간에 재생목록을 공유할 수 있습니다. 링크를 클릭하면 이 버전이 공유 재생목록의 혜택을 받습니다.",
|
||||
"link-playlist-info": "재생목록의 추가 및 삭제도 공유됩니다.",
|
||||
"keep-playlists": "재생목록 유지",
|
||||
"keep-playlists-tip": "재생목록을 유지하면 현재 버전의 재생목록이 공유 재생목록 폴더로 이동됩니다. 그렇지 않으면 잃어버리게 됩니다.",
|
||||
"unlink-playlists": "재생목록 링크 해제",
|
||||
"unlink-playlist-desc": "경고: 재생목록 링크를 해제하면 이 버전에서는 공유 재생목록을 사용할 수 없습니다.",
|
||||
"unlink-keep-playlists-tip": "재생목록을 유지하면 현재 버전에 대해 공유 재생목록의 복사본이 생성됩니다. 그렇지 않으면 이 버전의 재생목록이 유지되지 않습니다.",
|
||||
"delete-playlist-ask": "재생목록을 삭제하시겠습니까?",
|
||||
"delete-playlists-ask": "재생목록을 삭제하시겠습니까?",
|
||||
"delete-playlist-desc": "재생목록「{playlistTitle}」을(를) 삭제해도 괜찮습니까?",
|
||||
|
||||
@@ -8,6 +8,10 @@
|
||||
"maps": "Карты",
|
||||
"playlists": "Плейлисты",
|
||||
"models": "Модели",
|
||||
"avatars": "Аватары",
|
||||
"sabers": "Сабли",
|
||||
"platforms": "Платформы",
|
||||
"blocks": "Блоки",
|
||||
"cancel": "Отмена",
|
||||
"delete": "Удалить",
|
||||
"accept": "Принять",
|
||||
@@ -19,7 +23,13 @@
|
||||
"choose-folder": "Изменить папку",
|
||||
"unknown": "Неизвестная ошибка ¯\\_(ツ)_/¯",
|
||||
"warning": "Внимание",
|
||||
"continue": "Продолжить"
|
||||
"continue": "Продолжить",
|
||||
"shared": "Общий",
|
||||
"pin": "Закрепить",
|
||||
"unpin": "Открепить"
|
||||
},
|
||||
"title-bar": {
|
||||
"outdated": "устаревший"
|
||||
},
|
||||
"nav-bar": {
|
||||
"add-version": "Добавить версию игры",
|
||||
@@ -40,9 +50,15 @@
|
||||
"debug-description": "Включает окно логов для IPA. В окне будут отображаться логи модов.",
|
||||
"outdated-tippy": "Эта версия устарела, и некоторые моды или функции могут работать не так, как ожидалось. Рекомендуется использовать рекомендуемую версию ({recommendedVersion}) Beat Saber, чтобы воспользоваться последними функциями и исправлениями ошибок.",
|
||||
"advanced-launch": {
|
||||
"button": "Расширенный запуск",
|
||||
"button": "Параметры запуска",
|
||||
"placeholder": "Параметры запуска, например: --revert; --nowait"
|
||||
}
|
||||
},
|
||||
"skipsteam": "Пропустить Steam",
|
||||
"skipsteam-description": "Предотвращает автоматическое открытие Steam с Beat Saber, включите, если вы используете другую VR-среду, такую как WiVRn или Monado, с которой SteamVR может мешать.",
|
||||
"map-editor": "Редактор карт",
|
||||
"map-editor-description": "Запусти официальный редактор карт Beat Saber вместо игры.",
|
||||
"proton-logs": "Протонные логи",
|
||||
"proton-logs-description": "Включает запись протонных логов для этой установки Beat Saber в \"{versionPath}\"."
|
||||
},
|
||||
"maps": {
|
||||
"search-bar": {
|
||||
@@ -106,6 +122,7 @@
|
||||
"latest": "Latest",
|
||||
"description": "Описание",
|
||||
"dropdown": {
|
||||
"import-mods": "Импортировать моды",
|
||||
"uninstall-all": "Удалить всё",
|
||||
"unselect-all": "Снять все выделения"
|
||||
}
|
||||
@@ -116,6 +133,10 @@
|
||||
"title": "Моды уже установлены",
|
||||
"description": "Все выбранные моды уже установлены"
|
||||
}
|
||||
},
|
||||
"drop-zone": {
|
||||
"text": "Импортируйте свои моды",
|
||||
"subtext": "Перетащите ваши файлы \"zip\" или \"dll\" сюда для импорта"
|
||||
}
|
||||
},
|
||||
"dropdown": {
|
||||
@@ -129,7 +150,7 @@
|
||||
}
|
||||
},
|
||||
"available-versions": {
|
||||
"title": "Выберите версию",
|
||||
"title": "Скачать версию",
|
||||
"steam-release": "Список изменений",
|
||||
"dropdown": {
|
||||
"refresh": "Обновить версии",
|
||||
@@ -258,6 +279,13 @@
|
||||
"error-notification": {
|
||||
"message": "Произошла ошибка, невозможно изменить настройки символических ссылок."
|
||||
}
|
||||
},
|
||||
"use-system-proxy": {
|
||||
"title": "Использовать системный прокси",
|
||||
"description": "BSManager будет делать сетевые запросы через ваш системный прокси.",
|
||||
"error-notification": {
|
||||
"message": "Произошла ошибка, не удалось изменить настройки системного прокси."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -308,6 +336,8 @@
|
||||
"msg": {
|
||||
"401": "Похоже Steam не хочет, чтобы мы скачали Beat Saber 😢",
|
||||
"404": "Нет связи с серверами Steam.",
|
||||
"ExeNotFoundWindows": "Отсутствует \"DepotDownloader.exe\". Пожалуйста, проверьте, не находится ли исполняемый файл в карантине вашего антивируса.",
|
||||
"ExeNotFoundLinux": "Отсутствует исполняемый файл \"DepotDownloader\".",
|
||||
"Password": "Неверный пароль.",
|
||||
"InvalidCredentials": "Неверные учетные данные для входа, неразрешенное соединение или слишком много попыток входа.",
|
||||
"NoManifest": "Файл манифеста не найден",
|
||||
@@ -341,8 +371,8 @@
|
||||
"UNABLE_TO_GET_MANIFEST": "Невозможно получить манифест, необходимый для загрузки.",
|
||||
"VERIFY_INTEGRITY_FAILED": "При проверке файлов произошла ошибка.",
|
||||
"SOME_FILES_FAILED_TO_DOWNLOAD": "Некоторые файлы не удалось загрузить.",
|
||||
"OCULUS_LOGIN_TIMED_OUT": "Получение токена входа заняло слишком много времени.",
|
||||
"OCULUS_LOGIN_WINDOW_CLOSED_BY_USER": "Окно входа в Meta было закрыто.",
|
||||
"META_LOGIN_TIMED_OUT": "Получение токена входа заняло слишком много времени.",
|
||||
"META_LOGIN_WINDOW_CLOSED_BY_USER": "Окно входа в Meta было закрыто.",
|
||||
"NO_META_AUTH_TOKEN": "Невозможно получить токен входа в Meta, необходимый для загрузки.",
|
||||
"UNKNOWN_ERROR": "Произошла неизвестная ошибка."
|
||||
}
|
||||
@@ -429,11 +459,13 @@
|
||||
"success": {
|
||||
"titles": {
|
||||
"BS_LAUNCHING": "Запуск...🚀",
|
||||
"STEAM_LAUNCHING": "Steam запускается!"
|
||||
"STEAM_LAUNCHING": "Steam запускается!",
|
||||
"SKIPPING_STEAM_LAUNCH": "Пропуск запуска Steam"
|
||||
},
|
||||
"msg": {
|
||||
"BS_LAUNCHING": "Не забудьте разогреться 😉",
|
||||
"STEAM_LAUNCHING": "Beat Saber запустится сам после Steam."
|
||||
"STEAM_LAUNCHING": "Beat Saber запустится сам после Steam.",
|
||||
"SKIPPING_STEAM_LAUNCH": "Надеюсь, вы знаете, что делаете :)"
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
@@ -526,6 +558,17 @@
|
||||
"no-mods": "Для этой версии не было установлено модов 😑"
|
||||
}
|
||||
}
|
||||
},
|
||||
"import-mod": {
|
||||
"titles": {
|
||||
"success": "Импорт модов завершен",
|
||||
"error": "Произошла ошибка при импорте модов"
|
||||
},
|
||||
"msgs": {
|
||||
"success": "Моды успешно импортированы.",
|
||||
"some-success": "Некоторые моды были успешно импортированы.",
|
||||
"no-dlls": "Файл(ы) не содержат файлов \"dll\"."
|
||||
}
|
||||
}
|
||||
},
|
||||
"maps": {
|
||||
@@ -582,6 +625,10 @@
|
||||
"ENOSPC": "Диск заполнен, освободите место и попробуйте снова.",
|
||||
"UNKNOWN_ERROR": "Произошла неизвестная ошибка при связывании папки."
|
||||
}
|
||||
},
|
||||
"adding-error": {
|
||||
"title": "Не удалось добавить общую папку.",
|
||||
"msg": "Вы не можете добавлять \"{folder}\" в общие папки."
|
||||
}
|
||||
},
|
||||
"create-launch-shortcut": {
|
||||
@@ -737,31 +784,29 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"link-maps": {
|
||||
"title": "Связать карты",
|
||||
"desc": "Общие карты доступны во всех версиях игры с этой функцией. Пока связь есть, эта версия будет иметь общие карты.",
|
||||
"info": "Добавление и удаление карт так же будут общими",
|
||||
"keep-maps": {
|
||||
"label": "Оставить карты",
|
||||
"title": "Все карты из этой версии станут общими. Если не включить, то карты этой версии будут потеряны."
|
||||
},
|
||||
"valid-btn": "Связать карты"
|
||||
},
|
||||
"unlink-maps": {
|
||||
"title": "Отвязать карты",
|
||||
"desc": "Осторожно, отвязка карт сделает недоступными общие карты для этой версии, пока вы не свяжете их снова.",
|
||||
"keep-maps": {
|
||||
"label": "Оставить карты",
|
||||
"title": "Общие карты будут скопированы в папку карт этой версии. Карты не будут потеряны, если это не выбрано."
|
||||
},
|
||||
"valid-btn": "Отвязать карты"
|
||||
},
|
||||
"delete-duplicate-maps": {
|
||||
"title": "Удалить карту?",
|
||||
"desc": "Только карта \"{map}\" является дубликатом. Вы уверены, что хотите ее удалить?",
|
||||
"desc-plural": "Найдено {nb} дубликатов карт. Вы уверены, что хотите их удалить?"
|
||||
}
|
||||
},
|
||||
"link-contents": {
|
||||
"title": "Связать {contentType}",
|
||||
"p-1": "Связывание {contentType} позволяет делиться {contentType} между всеми версиями с этой функцией. После связывания эта версия будет использовать общие {contentType}.",
|
||||
"p-2": "Для использования функции связывания все ваши {itemsHtml} будут перемещены в папку {sharedHtml}. После этого символическая ссылка, указывающая на общую папку {contentType}, заменит вашу оригинальную папку {contentType}.",
|
||||
"warning": "Обратите внимание, что любые изменения, затрагивающие {contentType}, также затронут все версии с этой функцией.",
|
||||
"what-is-a-symbolic-link": "Что такое символическая ссылка?",
|
||||
"i-need-help": "Мне нужна помощь",
|
||||
"valid-btn": "Связать {contentType}"
|
||||
},
|
||||
"unlink-contents": {
|
||||
"title": "Разорвать связь {contentType}",
|
||||
"p-1": "Будьте осторожны, разрыв связи {contentType} отключит использование общих {contentType} для этой версии, пока они снова не будут связаны.",
|
||||
"p-2": "Чтобы разорвать связь ваших {contentType}, символическая ссылка, указывающая на {sharedHtml}, будет удалена. После этого все {contentType} из общей папки будут скопированы в папку {itemsHtml} вашей версии Beat Saber.",
|
||||
"do-not-copy-contents": "Не копировать {contentType}",
|
||||
"do-not-copy-contents-tip": "Если включено, {contentType} из общей папки не будут скопированы в вашу версию Beat Saber. В результате, никакие {contentType} не останутся в вашей версии Beat Saber после разрыва связи.",
|
||||
"valid-btn": "Разорвать связь {contentType}"
|
||||
},
|
||||
"download-maps": {
|
||||
"search-btn": "Поиск",
|
||||
"loading-maps": "Загрузка карт...",
|
||||
@@ -783,9 +828,14 @@
|
||||
"add-folder": "Добавить папку",
|
||||
"link-folder": "Связать папку",
|
||||
"unlink-folder": "Отвязать папку",
|
||||
"link-all": "Связать всё"
|
||||
"link-all": "Связать всё",
|
||||
"remove-from-the-list": "Удалить из списка"
|
||||
}
|
||||
},
|
||||
"adding-shared-folder": {
|
||||
"title": "Добавление общей папки",
|
||||
"description": "У вас могут возникнуть проблемы при связывании папки \"{folder}\". Вы уверены, что хотите ее добавить?"
|
||||
},
|
||||
"create-launch-shortcut": {
|
||||
"title": "Создать ярлык",
|
||||
"desc": "Ярлык позволит вам запустить Beat Saber без помощи BSManager.",
|
||||
@@ -804,14 +854,13 @@
|
||||
"stay": "Запомнить меня",
|
||||
"connect-to-meta": "Подключиться к Meta"
|
||||
},
|
||||
"original-version-backup-oculus": {
|
||||
"title": "Внимание",
|
||||
"body": {
|
||||
"must-be-installed-once": "Вы должны установить Beat Saber из магазина Oculus на этом устройстве, в противном случае Beat Saber автоматически закроется после запуска.",
|
||||
"will-backup": "Для запуска этой версии исходная папка установки Beat Saber, находящаяся в вашей библиотеке Oculus, будет переименована и автоматически восстановлена при выходе из Beat Saber."
|
||||
},
|
||||
"not-remind-me": "Больше не напоминать",
|
||||
"understood": "Понял"
|
||||
"enable-oculus-sideloaded-apps": {
|
||||
"title": "Включить Sideloading",
|
||||
"info-1": "Чтобы запустить Beat Saber, необходимо включить возможность запуска приложений через sideloading. BSManager запросит права администратора для автоматического включения этой функции.",
|
||||
"info-2": "Функция sideloading позволяет запускать игры, расположенные за пределами папки библиотеки Oculus.",
|
||||
"info-3": "После активации сайдлоадинга функция останется активной, и вам больше не будет предложено её включить.",
|
||||
"i-want-to-do-it-myself": "Я хочу сделать это сам",
|
||||
"understood": "Понято"
|
||||
},
|
||||
"enter-meta-token": {
|
||||
"title": "Токен Oculus",
|
||||
@@ -864,7 +913,8 @@
|
||||
"tags": "тэги",
|
||||
"specificities": "основное",
|
||||
"requirements": "требуемые моды",
|
||||
"exclude": "исключить"
|
||||
"exclude": "исключить",
|
||||
"leaderboard": "Таблица лидеров"
|
||||
},
|
||||
"map-types": {
|
||||
"accuracy": "точность",
|
||||
@@ -915,11 +965,16 @@
|
||||
},
|
||||
"map-specificities": {
|
||||
"automapper": "ИИ",
|
||||
"ranked": "с рейтингом",
|
||||
"curated": "оценённые куратором",
|
||||
"verified": "проверенные авторы",
|
||||
"fullSpread": "полный набор"
|
||||
},
|
||||
"map-leaderboard": {
|
||||
"All": "все",
|
||||
"Ranked": "с рейтингом",
|
||||
"BeatLeader": "BeatLeader",
|
||||
"ScoreSaber": "ScoreSaber"
|
||||
},
|
||||
"map-excludes": {
|
||||
"installed": "установленный"
|
||||
},
|
||||
@@ -1007,86 +1062,6 @@
|
||||
"no-models": "Модели не найдены.",
|
||||
"no-internet": "Нет интернета.",
|
||||
"error-occured": "Ошибка, повторите попытку позже."
|
||||
},
|
||||
"link-models": {
|
||||
"avatar": {
|
||||
"title": "Связать аватары",
|
||||
"desc": "Общие аватары доступны во всех версиях игры с этой функцией. Пока связь есть, эта версия будет иметь общие аватары.",
|
||||
"info": "Добавление и удаление аватаров так же будут общими",
|
||||
"keep-models": {
|
||||
"label": "Оставить аватары",
|
||||
"title": "Все аватары из этой версии станут общими. Если не включить, то аватары этой версии будут потеряны."
|
||||
},
|
||||
"valid-btn": "Связать аватары"
|
||||
},
|
||||
"saber": {
|
||||
"title": "Связать мечи",
|
||||
"desc": "Общие мечи доступны во всех версиях игры с этой функцией. Пока связь есть, эта версия будет иметь общие мечи.",
|
||||
"info": "Добавление и удаление мечей так же будут общими",
|
||||
"keep-models": {
|
||||
"label": "Оставить мечи",
|
||||
"title": "Все мечи из этой версии станут общими. Если не включить, то мечи этой версии будут потеряны."
|
||||
},
|
||||
"valid-btn": "Связать мечи"
|
||||
},
|
||||
"platform": {
|
||||
"title": "Связать платформы",
|
||||
"desc": "Общие платформы доступны во всех версиях игры с этой функцией. Пока связь есть, эта версия будет иметь общие платформы.",
|
||||
"info": "Добавление и удаление платформ так же будут общими",
|
||||
"keep-models": {
|
||||
"label": "Оставить платформы",
|
||||
"title": "Все платформы из этой версии станут общими. Если не включить, то платформы этой версии будут потеряны."
|
||||
},
|
||||
"valid-btn": "Связать платформы"
|
||||
},
|
||||
"bloq": {
|
||||
"title": "Связать кубы",
|
||||
"desc": "Общие кубы доступны во всех версиях игры с этой функцией. Пока связь есть, эта версия будет иметь общие кубы.",
|
||||
"info": "Adding and deleting bloqs will also be shared",
|
||||
"keep-models": {
|
||||
"label": "Оставить кубы",
|
||||
"title": "Все кубы из этой версии станут общими. Если не включить, то кубы этой версии будут потеряны."
|
||||
},
|
||||
"valid-btn": "Связать кубы"
|
||||
}
|
||||
},
|
||||
"unlink-models": {
|
||||
"avatar": {
|
||||
"title": "Отвязать аватары",
|
||||
"desc": "Осторожно, отвязка аватаров сделает недоступными общие аватары для этой версии, пока вы не свяжете их снова.",
|
||||
"keep-models": {
|
||||
"label": "Оставить аватары",
|
||||
"title": "Общие аватары будут скопированы в папку аватаров этой версии. Аватары не будут потеряны, если это не выбрано."
|
||||
},
|
||||
"valid-btn": "Отвязать аватары"
|
||||
},
|
||||
"saber": {
|
||||
"title": "Отвязать мечи",
|
||||
"desc": "Осторожно, отвязка мечей сделает недоступными общие мечи для этой версии, пока вы не свяжете их снова.",
|
||||
"keep-models": {
|
||||
"label": "Оставить мечи",
|
||||
"title": "Общие мечи будут скопированы в папку мечей этой версии. Мечи не будут потеряны, если это не выбрано."
|
||||
},
|
||||
"valid-btn": "Отвязать мечи"
|
||||
},
|
||||
"platform": {
|
||||
"title": "Отвязать платформы",
|
||||
"desc": "Осторожно, отвязка платформ сделает недоступными общие платформы для этой версии, пока вы не свяжете их снова.",
|
||||
"keep-models": {
|
||||
"label": "Оставить платформы",
|
||||
"title": "Общие платформы будут скопированы в папку платформ этой версии. Платформы не будут потеряны, если это не выбрано."
|
||||
},
|
||||
"valid-btn": "Отвязать платформы"
|
||||
},
|
||||
"bloq": {
|
||||
"title": "Отвязать кубы",
|
||||
"desc": "Осторожно, отвязка кубов сделает недоступными общие кубы для этой версии, пока вы не свяжете их снова.",
|
||||
"keep-models": {
|
||||
"label": "Оставить кубы",
|
||||
"title": "Общие кубы будут скопированы в папку кубов этой версии. Кубы не будут потеряны, если это не выбрано."
|
||||
},
|
||||
"valid-btn": "Отвязать кубы"
|
||||
}
|
||||
}
|
||||
},
|
||||
"notifications": {
|
||||
@@ -1163,14 +1138,6 @@
|
||||
"stop-download": "Остановить загрузку",
|
||||
"cancel-download": "Отменить загрузку",
|
||||
"open-file": "Открыть файл",
|
||||
"link-playlists": "Связать плейлисты",
|
||||
"link-playlist-desc": "Связывание плейлистов позволяет делиться плейлистами между всеми версиями. После связывания эта версия будет иметь доступ к общим плейлистам",
|
||||
"link-playlist-info": "Добавление и удаление плейлистов также будет общим",
|
||||
"keep-playlists": "Сохранить плейлисты",
|
||||
"keep-playlists-tip": "Сохранение плейлистов переместит плейлисты из текущей версии в папку общих плейлистов. В противном случае они будут потеряны",
|
||||
"unlink-playlists": "Отвязать плейлисты",
|
||||
"unlink-playlist-desc": "Внимание, отвязка плейлистов больше не позволит использовать общие плейлисты для этой версии.",
|
||||
"unlink-keep-playlists-tip": "Сохранение плейлистов создаст копию общих плейлистов для текущей версии. В противном случае для этой версии не будут сохранены плейлисты.",
|
||||
"delete-playlist-ask": "Удалить плейлист?",
|
||||
"delete-playlists-ask": "Удалить плейлисты?",
|
||||
"delete-playlist-desc": "Вы уверены, что хотите удалить плейлист \"{playlistTitle}\"?",
|
||||
|
||||
@@ -8,6 +8,10 @@
|
||||
"maps": "譜面",
|
||||
"playlists": "歌單",
|
||||
"models": "模型",
|
||||
"avatars": "頭像",
|
||||
"sabers": "光劍",
|
||||
"platforms": "平台",
|
||||
"blocks": "方塊",
|
||||
"cancel": "取消",
|
||||
"delete": "刪除",
|
||||
"accept": "接受",
|
||||
@@ -19,7 +23,13 @@
|
||||
"choose-folder": "選擇文件夾",
|
||||
"unknown": "發生了一個錯誤 ¯\\_(ツ)_/¯",
|
||||
"warning": "警告",
|
||||
"continue": "繼續"
|
||||
"continue": "繼續",
|
||||
"shared": "共享",
|
||||
"pin": "固定",
|
||||
"unpin": "取消固定"
|
||||
},
|
||||
"title-bar": {
|
||||
"outdated": "過時"
|
||||
},
|
||||
"nav-bar": {
|
||||
"add-version": "新增版本",
|
||||
@@ -40,9 +50,15 @@
|
||||
"debug-description": "為 IPA 啟用輸出日誌窗口。 會顯示所用 Mod 的 debug 輸出",
|
||||
"outdated-tippy": "此版本已過時,某些模組或功能可能無法按預期運作。建議使用推薦版本的 Beat Saber ({recommendedVersion}),以享受最新功能和修復。",
|
||||
"advanced-launch": {
|
||||
"button": "高級啟動",
|
||||
"button": "啟動選項",
|
||||
"placeholder": "額外啟動參數,例如: --revert; --nowait"
|
||||
}
|
||||
},
|
||||
"skipsteam": "跳過 Steam",
|
||||
"skipsteam-description": "防止 Steam 與 Beat Saber 自動打開,如果您使用的是其他 VR 執行時,如 WiVRn 或 Monado,SteamVR 可能會干擾,請啟用此選項。",
|
||||
"map-editor": "地圖編輯器",
|
||||
"map-editor-description": "啟動官方的 Beat Saber 地圖編輯器,而不是遊戲。",
|
||||
"proton-logs": "Proton紀錄",
|
||||
"proton-logs-description": "啟用此 Beat Saber 安裝的質子紀錄,路徑為\"{versionPath}\"。"
|
||||
},
|
||||
"maps": {
|
||||
"search-bar": {
|
||||
@@ -106,6 +122,7 @@
|
||||
"latest": "最新",
|
||||
"description": "描述",
|
||||
"dropdown": {
|
||||
"import-mods": "導入模組",
|
||||
"uninstall-all": "全部移除",
|
||||
"unselect-all": "取消全選"
|
||||
}
|
||||
@@ -116,6 +133,10 @@
|
||||
"title": "模組已安裝",
|
||||
"description": "所有選中的模組已經安裝"
|
||||
}
|
||||
},
|
||||
"drop-zone": {
|
||||
"text": "匯入你的模組",
|
||||
"subtext": "將你的 \"zip\" 或 \"dll\" 檔案拖放到這裡以進行匯入"
|
||||
}
|
||||
},
|
||||
"dropdown": {
|
||||
@@ -129,7 +150,7 @@
|
||||
}
|
||||
},
|
||||
"available-versions": {
|
||||
"title": "選擇版本",
|
||||
"title": "下載一個版本",
|
||||
"steam-release": "發布頁面",
|
||||
"dropdown": {
|
||||
"refresh": "刷新版本",
|
||||
@@ -258,6 +279,13 @@
|
||||
"error-notification": {
|
||||
"message": "發生錯誤,無法更改符號鏈接設置。"
|
||||
}
|
||||
},
|
||||
"use-system-proxy": {
|
||||
"title": "使用系統代理",
|
||||
"description": "BSManager將通過您的系統代理發送網絡請求。",
|
||||
"error-notification": {
|
||||
"message": "發生錯誤,無法更改系統代理設置。"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -308,6 +336,8 @@
|
||||
"msg": {
|
||||
"401": "Steam 似乎不想讓我們下載 Beat Saber😢",
|
||||
"404": "無法聯繫 Steam 伺服器。",
|
||||
"ExeNotFoundWindows": "\"DepotDownloader.exe\" 缺失。請檢查執行檔是否被您的防毒軟體隔離。",
|
||||
"ExeNotFoundLinux": "\"DepotDownloader\" 的可執行檔缺失。",
|
||||
"Password": "密碼無效",
|
||||
"InvalidCredentials": "登錄憑據無效、連接未經批准或登錄嘗試次數過多。",
|
||||
"NoManifest": "未找到清單",
|
||||
@@ -341,8 +371,8 @@
|
||||
"UNABLE_TO_GET_MANIFEST": "無法獲取下載所需的清單。",
|
||||
"VERIFY_INTEGRITY_FAILED": "文件驗證過程中發生錯誤。",
|
||||
"SOME_FILES_FAILED_TO_DOWNLOAD": "一些文件下載失敗。",
|
||||
"OCULUS_LOGIN_TIMED_OUT": "獲取登錄令牌用時過長。",
|
||||
"OCULUS_LOGIN_WINDOW_CLOSED_BY_USER": "Meta 登錄窗口被關閉。",
|
||||
"META_LOGIN_TIMED_OUT": "獲取登錄令牌用時過長。",
|
||||
"META_LOGIN_WINDOW_CLOSED_BY_USER": "Meta 登錄窗口被關閉。",
|
||||
"NO_META_AUTH_TOKEN": "無法獲取下載所需的 Meta 登錄令牌。",
|
||||
"UNKNOWN_ERROR": "發生未知錯誤。"
|
||||
}
|
||||
@@ -429,11 +459,13 @@
|
||||
"success": {
|
||||
"titles": {
|
||||
"BS_LAUNCHING": "啟動中...🚀",
|
||||
"STEAM_LAUNCHING": "Steam 正在啟動!"
|
||||
"STEAM_LAUNCHING": "Steam 正在啟動!",
|
||||
"SKIPPING_STEAM_LAUNCH": "跳過 Steam 啟動"
|
||||
},
|
||||
"msg": {
|
||||
"BS_LAUNCHING": "別忘了熱身哦 😉",
|
||||
"STEAM_LAUNCHING": "Beat Saber 會在 Steam 啟動後自動啟動。"
|
||||
"STEAM_LAUNCHING": "Beat Saber 會在 Steam 啟動後自動啟動。",
|
||||
"SKIPPING_STEAM_LAUNCH": "希望你知道自己在做什麼 :)"
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
@@ -526,6 +558,17 @@
|
||||
"no-mods": "該版本中沒有安裝 Mod 😑"
|
||||
}
|
||||
}
|
||||
},
|
||||
"import-mod": {
|
||||
"titles": {
|
||||
"success": "模組匯入完成",
|
||||
"error": "模組匯入過程中發生錯誤"
|
||||
},
|
||||
"msgs": {
|
||||
"success": "模組成功匯入。",
|
||||
"some-success": "一些模組已成功匯入。",
|
||||
"no-dlls": "檔案中不包含任何 \"dll\" 檔案。"
|
||||
}
|
||||
}
|
||||
},
|
||||
"maps": {
|
||||
@@ -582,6 +625,10 @@
|
||||
"ENOSPC": "磁碟已滿,請騰出空間後再試。",
|
||||
"UNKNOWN_ERROR": "連結文件夾時發生未知錯誤。"
|
||||
}
|
||||
},
|
||||
"adding-error": {
|
||||
"title": "添加共享文件夹失败",
|
||||
"msg": "您无法将\"{folder}\"添加到共享文件夹。"
|
||||
}
|
||||
},
|
||||
"create-launch-shortcut": {
|
||||
@@ -737,31 +784,29 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"link-maps": {
|
||||
"title": "關聯譜面",
|
||||
"desc": "關聯譜面可以在所有啟用此功能的版本之間共享譜面。一旦關聯,該版本將受益於共享譜面。",
|
||||
"info": "新增和刪除的譜面也將被共享",
|
||||
"keep-maps": {
|
||||
"label": "保留譜面",
|
||||
"title": "保留譜面將會把當前版本的所有譜面複製到共享譜面文件夾。否則,該版本的譜面將不會被保留,它們將遺失。"
|
||||
},
|
||||
"valid-btn": "關聯譜面"
|
||||
},
|
||||
"unlink-maps": {
|
||||
"title": "取消關聯譜面",
|
||||
"desc": "請注意,取消關聯譜面後該版本將無法使用共享譜面,直到重新啟用。",
|
||||
"keep-maps": {
|
||||
"label": "保留譜面",
|
||||
"title": "保留譜面將會在取消關聯後把共享文件夾的所有譜面複製到當前版本。如果此項被禁用,譜面也不會遺失。"
|
||||
},
|
||||
"valid-btn": "取消關聯譜面"
|
||||
},
|
||||
"delete-duplicate-maps": {
|
||||
"title": "刪除地圖",
|
||||
"desc": "只有地圖「{map}」是重複的。你確定要刪除它嗎?",
|
||||
"desc-plural": "發現了 {nb} 個重複的地圖。你確定要刪除它們嗎?"
|
||||
}
|
||||
},
|
||||
"link-contents": {
|
||||
"title": "連結{contentType}",
|
||||
"p-1": "連結{contentType}允許在啟用此功能的所有版本之間共享{contentType}。連結後,該版本將受益於共享的{contentType}。",
|
||||
"p-2": "為了使用連結功能,您的所有{itemsHtml}將被移動到{sharedHtml}資料夾。之後,一個指向共享{contentType}資料夾的符號連結將取代您原本的{contentType}資料夾。",
|
||||
"warning": "請注意,任何影響{contentType}的更改也會影響所有啟用此功能的版本。",
|
||||
"what-is-a-symbolic-link": "什麼是符號連結?",
|
||||
"i-need-help": "我需要幫助",
|
||||
"valid-btn": "連結{contentType}"
|
||||
},
|
||||
"unlink-contents": {
|
||||
"title": "取消連結{contentType}",
|
||||
"p-1": "注意:取消連結{contentType}將會停用此版本中共享的{contentType}功能,直到該功能再次啟用。",
|
||||
"p-2": "為了取消連結您的{contentType},指向{sharedHtml}的符號連結將被移除。之後,共享資料夾中的所有{contentType}將被複製到您Beat Saber版本的{itemsHtml}資料夾中。",
|
||||
"do-not-copy-contents": "不要複製{contentType}",
|
||||
"do-not-copy-contents-tip": "如果啟用,共享資料夾中的{contentType}將不會被複製到您的Beat Saber版本中。結果是,在取消連結後,您的Beat Saber版本中將不再保留任何{contentType}。",
|
||||
"valid-btn": "取消連結{contentType}"
|
||||
},
|
||||
"download-maps": {
|
||||
"search-btn": "搜尋",
|
||||
"loading-maps": "譜面載入中...",
|
||||
@@ -783,9 +828,14 @@
|
||||
"add-folder": "新增文件夾",
|
||||
"link-folder": "關聯文件夾",
|
||||
"unlink-folder": "取消關聯文件夾",
|
||||
"link-all": "關聯所有"
|
||||
"link-all": "關聯所有",
|
||||
"remove-from-the-list": "從列表中移除"
|
||||
}
|
||||
},
|
||||
"adding-shared-folder": {
|
||||
"title": "添加共享文件夹",
|
||||
"description": "链接\"{folder}\"文件夹时可能会遇到问题。您确定要添加它吗?"
|
||||
},
|
||||
"create-launch-shortcut": {
|
||||
"title": "創建捷徑",
|
||||
"desc": "創建捷徑使得你可以在不通過 BSManager 的情況下以特定選項啟動 BeatSaber",
|
||||
@@ -804,14 +854,13 @@
|
||||
"stay": "記住我",
|
||||
"connect-to-meta": "連接到 Meta"
|
||||
},
|
||||
"original-version-backup-oculus": {
|
||||
"title": "警告",
|
||||
"body": {
|
||||
"must-be-installed-once": "您必須在此設備上從Oculus商店安裝Beat Saber,否則Beat Saber將在啟動後自動關閉。",
|
||||
"will-backup": "為了啟動這個版本,位於您的Oculus庫中的Beat Saber的原始安裝文件夾將被重新命名,並且在Beat Saber關閉時會自動恢復。"
|
||||
},
|
||||
"not-remind-me": "不再提醒我",
|
||||
"understood": "明白了"
|
||||
"enable-oculus-sideloaded-apps": {
|
||||
"title": "啟用旁載",
|
||||
"info-1": "為了啟動 Beat Saber,必須啟用執行旁載應用程式的功能。BSManager 將請求管理員權限以自動啟用此功能。",
|
||||
"info-2": "旁載功能允許啟動位於 Oculus 資料庫資料夾之外的遊戲。",
|
||||
"info-3": "啟用 sideloading 後,此功能將保持啟用狀態,您將不再被提示啟用它。",
|
||||
"i-want-to-do-it-myself": "我想自己完成",
|
||||
"understood": "了解"
|
||||
},
|
||||
"enter-meta-token": {
|
||||
"title": "Oculus令牌",
|
||||
@@ -865,7 +914,8 @@
|
||||
"tags": "標籤",
|
||||
"specificities": "general",
|
||||
"requirements": "要求",
|
||||
"exclude": "排除"
|
||||
"exclude": "排除",
|
||||
"leaderboard": "排行榜"
|
||||
},
|
||||
"map-types": {
|
||||
"accuracy": "精確度",
|
||||
@@ -916,11 +966,16 @@
|
||||
},
|
||||
"map-specificities": {
|
||||
"automapper": "AI",
|
||||
"ranked": "Ranked",
|
||||
"curated": "Curated",
|
||||
"verified": "Verified",
|
||||
"fullSpread": "Full spread"
|
||||
},
|
||||
"map-leaderboard": {
|
||||
"All": "所有",
|
||||
"Ranked": "排名的",
|
||||
"BeatLeader": "BeatLeader",
|
||||
"ScoreSaber": "ScoreSaber"
|
||||
},
|
||||
"map-excludes": {
|
||||
"installed": "已安裝"
|
||||
},
|
||||
@@ -1008,86 +1063,6 @@
|
||||
"no-models": "未找到模型",
|
||||
"no-internet": "無網路連接",
|
||||
"error-occured": "發生了一個錯誤,稍後重試。"
|
||||
},
|
||||
"link-models": {
|
||||
"avatar": {
|
||||
"title": "關聯 Avatar",
|
||||
"desc": "關聯 Avatar 允許在所有啟用此功能的版本之間共享 Avatar。一旦關聯,該版本將受益於共享 Avatar。",
|
||||
"info": "對 Avatar 的新增和刪除也將被共享",
|
||||
"keep-models": {
|
||||
"label": "保留 Avatar",
|
||||
"title": "保留 Avatar 將會把當前版本的所有 Avatar 複製到共享 Avatar文件夾。否則,該版本的 Avatar將不會被保留,它們將遺失。"
|
||||
},
|
||||
"valid-btn": "關聯 Avatar "
|
||||
},
|
||||
"saber": {
|
||||
"title": "關聯光劍",
|
||||
"desc": "關聯光劍允許在所有啟用此功能的版本之間共享光劍。一旦關聯,該版本將受益於共享光劍。",
|
||||
"info": "對光劍的新增和刪除也將被共享",
|
||||
"keep-models": {
|
||||
"label": "保留光劍",
|
||||
"title": "保留光劍將會把當前版本的所有光劍複製到共享光劍文件夾。否則,該版本的光劍將不會被保留,它們將遺失。"
|
||||
},
|
||||
"valid-btn": "關聯光劍"
|
||||
},
|
||||
"platform": {
|
||||
"title": "關聯平台",
|
||||
"desc": "關聯平台允許在所有啟用此功能的版本之間共享平台。一旦關聯,該版本將受益於共享平台。",
|
||||
"info": "對平台的新增和刪除也將被共享",
|
||||
"keep-models": {
|
||||
"label": "保留平台",
|
||||
"title": "保留平台將會把當前版本的所有平台複製到共享平台文件夾。否則,該版本的平台將不會被保留,它們將遺失。"
|
||||
},
|
||||
"valid-btn": "關聯平台"
|
||||
},
|
||||
"bloq": {
|
||||
"title": "關聯方塊",
|
||||
"desc": "關聯方塊允許在所有啟用此功能的版本之間共享方塊。一旦關聯,該版本將受益於共享方塊。",
|
||||
"info": "對方塊的新增和刪除也將被共享",
|
||||
"keep-models": {
|
||||
"label": "保留方塊",
|
||||
"title": "保留方塊將會把當前版本的所有方塊複製到共享方塊文件夾。否則,該版本的方塊將不會被保留,它們將遺失。"
|
||||
},
|
||||
"valid-btn": "關聯方塊"
|
||||
}
|
||||
},
|
||||
"unlink-models": {
|
||||
"avatar": {
|
||||
"title": "取消關聯 Avatar",
|
||||
"desc": "請注意,取消關聯 Avatar 將禁用該版本的共享 Avatar 使用,直到重新啟用。",
|
||||
"keep-models": {
|
||||
"label": "保留 Avatar",
|
||||
"title": "保留 Avatar 將會在取消關聯後把共享文件夾的所有 Avatar 複製到當前版本。如果此項被禁用,Avatar 將不會遺失。"
|
||||
},
|
||||
"valid-btn": "取消關聯 Avatar"
|
||||
},
|
||||
"saber": {
|
||||
"title": "取消關聯光劍",
|
||||
"desc": "請注意,取消關聯光劍將禁用該版本的共享光劍使用,直到重新啟用。",
|
||||
"keep-models": {
|
||||
"label": "保留光劍",
|
||||
"title": "保留光劍將會在取消關聯後把共享文件夾的所有光劍複製到當前版本。如果此項被禁用,光劍將不會遺失。"
|
||||
},
|
||||
"valid-btn": "取消關聯光劍"
|
||||
},
|
||||
"platform": {
|
||||
"title": "取消關聯平台",
|
||||
"desc": "請注意,取消關聯平台將禁用該版本的共享平台使用,直到重新啟用。",
|
||||
"keep-models": {
|
||||
"label": "保留平台",
|
||||
"title": "保留平台將會在取消關聯後把共享文件夾的所有平台複製到當前版本。如果此項被禁用,平台將不會遺失。"
|
||||
},
|
||||
"valid-btn": "取消關聯平台"
|
||||
},
|
||||
"bloq": {
|
||||
"title": "取消關聯方塊",
|
||||
"desc": "請注意,取消關聯方塊將禁用該版本的共享方塊使用,直到重新啟用。",
|
||||
"keep-models": {
|
||||
"label": "保留方塊",
|
||||
"title": "保留方塊將會在取消關聯後把共享文件夾的所有方塊複製到當前版本。如果此項被禁用,方塊將不會遺失。"
|
||||
},
|
||||
"valid-btn": "取消關聯方塊"
|
||||
}
|
||||
}
|
||||
},
|
||||
"notifications": {
|
||||
@@ -1164,14 +1139,6 @@
|
||||
"stop-download": "停止下載",
|
||||
"cancel-download": "取消下載",
|
||||
"open-file": "開啟檔案",
|
||||
"link-playlists": "連結播放清單",
|
||||
"link-playlist-desc": "連結播放清單允許在所有版本之間共享播放清單。一旦連結,此版本將受益於共享播放清單",
|
||||
"link-playlist-info": "新增和刪除播放清單也將被共享",
|
||||
"keep-playlists": "保留播放清單",
|
||||
"keep-playlists-tip": "保留播放清單將把當前版本的播放清單移動到共享播放清單資料夾。否則,它們將遺失",
|
||||
"unlink-playlists": "取消連結播放清單",
|
||||
"unlink-playlist-desc": "警告,取消連結播放清單將不再允許此版本使用共享播放清單。",
|
||||
"unlink-keep-playlists-tip": "保留播放清單將為當前版本建立共享播放清單的副本。否則,此版本將不保留任何播放清單。",
|
||||
"delete-playlist-ask": "刪除播放清單?",
|
||||
"delete-playlists-ask": "刪除播放清單?",
|
||||
"delete-playlist-desc": "您確定要刪除播放清單 \"{playlistTitle}\" 嗎?",
|
||||
|
||||
@@ -8,6 +8,10 @@
|
||||
"maps": "谱面",
|
||||
"playlists": "歌单",
|
||||
"models": "模型",
|
||||
"avatars": "头像",
|
||||
"sabers": "光剑",
|
||||
"platforms": "平台",
|
||||
"blocks": "方块",
|
||||
"cancel": "取消",
|
||||
"delete": "删除",
|
||||
"accept": "接受",
|
||||
@@ -19,7 +23,13 @@
|
||||
"choose-folder": "选择文件夹",
|
||||
"Unknown": "发生了一个错误 ¯\\_(ツ)_/¯",
|
||||
"warning": "警告",
|
||||
"continue": "继续"
|
||||
"continue": "继续",
|
||||
"shared": "共享",
|
||||
"pin": "固定",
|
||||
"unpin": "取消固定"
|
||||
},
|
||||
"title-bar": {
|
||||
"outdated": "过时"
|
||||
},
|
||||
"nav-bar": {
|
||||
"add-version": "添加版本",
|
||||
@@ -40,9 +50,15 @@
|
||||
"debug-description": "为 IPA 启用输出日志窗口。 会显示所用 Mod 的 debug 输出",
|
||||
"outdated-tippy": "此版本已过时,某些模组或功能可能无法按预期运行。建议使用推荐版本的 Beat Saber ({recommendedVersion}),以享受最新功能和修复。",
|
||||
"advanced-launch": {
|
||||
"button": "高级启动",
|
||||
"button": "启动选项",
|
||||
"placeholder": "额外启动参数,例如: --revert; --nowait"
|
||||
}
|
||||
},
|
||||
"skipsteam": "跳过 Steam",
|
||||
"skipsteam-description": "防止 Steam 与 Beat Saber 自动打开,如果您使用的是其他 VR 运行时,如 WiVRn 或 Monado,SteamVR 可能会干扰,请启用此选项。",
|
||||
"map-editor": "地图编辑器",
|
||||
"map-editor-description": "启动官方的 Beat Saber 地图编辑器,而不是游戏。",
|
||||
"proton-logs": "Proton日志",
|
||||
"proton-logs-description": "启用此 Beat Saber 安装的质子日志,路径为\"{versionPath}\"。"
|
||||
},
|
||||
"maps": {
|
||||
"search-bar": {
|
||||
@@ -106,6 +122,7 @@
|
||||
"latest": "最新",
|
||||
"description": "描述",
|
||||
"dropdown": {
|
||||
"import-mods": "导入模组",
|
||||
"uninstall-all": "全部卸载",
|
||||
"unselect-all": "取消全选"
|
||||
}
|
||||
@@ -116,6 +133,10 @@
|
||||
"title": "模组已安装",
|
||||
"description": "所有选中的模组已经安装"
|
||||
}
|
||||
},
|
||||
"drop-zone": {
|
||||
"text": "导入你的模组",
|
||||
"subtext": "将你的 \"zip\" 或 \"dll\" 文件拖放到这里进行导入"
|
||||
}
|
||||
},
|
||||
"dropdown": {
|
||||
@@ -129,7 +150,7 @@
|
||||
}
|
||||
},
|
||||
"available-versions": {
|
||||
"title": "选择版本",
|
||||
"title": "下载一个版本",
|
||||
"steam-release": "发布页面",
|
||||
"dropdown": {
|
||||
"refresh": "刷新版本",
|
||||
@@ -258,6 +279,13 @@
|
||||
"error-notification": {
|
||||
"message": "发生错误,无法更改符号链接设置。"
|
||||
}
|
||||
},
|
||||
"use-system-proxy": {
|
||||
"title": "使用系统代理",
|
||||
"description": "BSMansger将通过系统代理发起网络请求。",
|
||||
"error-notification": {
|
||||
"message": "发生错误,无法启用系统代理。"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -308,6 +336,8 @@
|
||||
"msg": {
|
||||
"401": "Steam 似乎不想让我们下载 Beat Saber😢",
|
||||
"404": "无法联系 Steam 服务器。",
|
||||
"ExeNotFoundWindows": "\"DepotDownloader.exe\" 缺失。请检查执行文件是否被您的防病毒软件隔离。",
|
||||
"ExeNotFoundLinux": "\"DepotDownloader\" 的可执行文件缺失。",
|
||||
"Password": "密码无效",
|
||||
"InvalidCredentials": "登录凭据无效、连接未经批准或登录尝试次数过多。",
|
||||
"NoManifest": "未找到清单",
|
||||
@@ -341,8 +371,8 @@
|
||||
"UNABLE_TO_GET_MANIFEST": "无法获取下载所需的清单。",
|
||||
"VERIFY_INTEGRITY_FAILED": "文件验证过程中发生错误。",
|
||||
"SOME_FILES_FAILED_TO_DOWNLOAD": "一些文件下载失败。",
|
||||
"OCULUS_LOGIN_TIMED_OUT": "获取登录令牌用时过长。",
|
||||
"OCULUS_LOGIN_WINDOW_CLOSED_BY_USER": "Meta 登录窗口被关闭。",
|
||||
"META_LOGIN_TIMED_OUT": "获取登录令牌用时过长。",
|
||||
"META_LOGIN_WINDOW_CLOSED_BY_USER": "Meta 登录窗口被关闭。",
|
||||
"NO_META_AUTH_TOKEN": "无法获取下载所需的 Meta 登录令牌。",
|
||||
"UNKNOWN_ERROR": "发生未知错误。"
|
||||
}
|
||||
@@ -429,11 +459,13 @@
|
||||
"success": {
|
||||
"titles": {
|
||||
"BS_LAUNCHING": "启动中...🚀",
|
||||
"STEAM_LAUNCHING": "Steam 正在启动!"
|
||||
"STEAM_LAUNCHING": "Steam 正在启动!",
|
||||
"SKIPPING_STEAM_LAUNCH": "跳过 Steam 启动"
|
||||
},
|
||||
"msg": {
|
||||
"BS_LAUNCHING": "别忘了热身哦 😉",
|
||||
"STEAM_LAUNCHING": "Beat Saber 会在 Steam 启动后自动启动。"
|
||||
"STEAM_LAUNCHING": "Beat Saber 会在 Steam 启动后自动启动。",
|
||||
"SKIPPING_STEAM_LAUNCH": "希望你知道自己在做什么 :)"
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
@@ -526,6 +558,17 @@
|
||||
"no-mods": "该版本中没有安装 Mod 😑"
|
||||
}
|
||||
}
|
||||
},
|
||||
"import-mod": {
|
||||
"titles": {
|
||||
"success": "模组导入完成",
|
||||
"error": "模组导入过程中发生错误"
|
||||
},
|
||||
"msgs": {
|
||||
"success": "模组成功导入。",
|
||||
"some-success": "一些模组已成功导入。",
|
||||
"no-dlls": "文件中不包含任何 \"dll\" 文件。"
|
||||
}
|
||||
}
|
||||
},
|
||||
"maps": {
|
||||
@@ -582,6 +625,10 @@
|
||||
"ENOSPC": "磁盘已满,请腾出空间后再试。",
|
||||
"UNKNOWN_ERROR": "链接文件夹时发生未知错误。"
|
||||
}
|
||||
},
|
||||
"adding-error": {
|
||||
"title": "添加共享文件夹失败",
|
||||
"msg": "您无法将\"{folder}\"添加到共享文件夹。"
|
||||
}
|
||||
},
|
||||
"create-launch-shortcut": {
|
||||
@@ -737,31 +784,29 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"link-maps": {
|
||||
"title": "关联谱面",
|
||||
"desc": "关联谱面可以在所有启用此功能的版本之间共享谱面。一旦关联,该版本将受益于共享谱面。",
|
||||
"info": "添加和删除的谱面也将被共享",
|
||||
"keep-maps": {
|
||||
"label": "保留谱面",
|
||||
"title": "保留谱面将会把当前版本的所有谱面复制到共享谱面文件夹。否则,该版本的谱面将不会被保留,它们将丢失。"
|
||||
},
|
||||
"valid-btn": "关联谱面"
|
||||
},
|
||||
"unlink-maps": {
|
||||
"title": "取消关联谱面",
|
||||
"desc": "请注意,取消关联谱面后该版本将无法使用共享谱面,直到重新启用。",
|
||||
"keep-maps": {
|
||||
"label": "保留谱面",
|
||||
"title": "保留谱面将会在取消关联后把共享文件夹的所有谱面复制到当前版本。如果此项被禁用,谱面也不会丢失。"
|
||||
},
|
||||
"valid-btn": "取消关联谱面"
|
||||
},
|
||||
"delete-duplicate-maps": {
|
||||
"title": "删除地图",
|
||||
"desc": "只有地图 \"{map}\" 是重复的。你确定要删除它吗?",
|
||||
"desc-plural": "发现了 {nb} 个重复的地图。你确定要删除它们吗?"
|
||||
}
|
||||
},
|
||||
"link-contents": {
|
||||
"title": "链接{contentType}",
|
||||
"p-1": "链接{contentType}允许在启用此功能的所有版本之间共享{contentType}。链接后,该版本将受益于共享的{contentType}。",
|
||||
"p-2": "为了使用链接功能,所有的{itemsHtml}将被移动到{sharedHtml}文件夹中。之后,一个指向共享{contentType}文件夹的符号链接将取代您原来的{contentType}文件夹。",
|
||||
"warning": "请注意,任何影响{contentType}的更改也会影响所有启用此功能的版本。",
|
||||
"what-is-a-symbolic-link": "什么是符号链接?",
|
||||
"i-need-help": "我需要帮助",
|
||||
"valid-btn": "链接{contentType}"
|
||||
},
|
||||
"unlink-contents": {
|
||||
"title": "取消链接{contentType}",
|
||||
"p-1": "注意:取消链接{contentType}将禁用此版本中共享的{contentType}功能,直到该功能再次启用。",
|
||||
"p-2": "为了取消链接您的{contentType},指向{sharedHtml}的符号链接将被删除。之后,共享文件夹中的所有{contentType}将被复制到您Beat Saber版本的{itemsHtml}文件夹中。",
|
||||
"do-not-copy-contents": "不复制{contentType}",
|
||||
"do-not-copy-contents-tip": "如果启用,共享文件夹中的{contentType}不会被复制到您的Beat Saber版本中。结果是,取消链接后,您的Beat Saber版本中将不再保留任何{contentType}。",
|
||||
"valid-btn": "取消链接{contentType}"
|
||||
},
|
||||
"download-maps": {
|
||||
"search-btn": "搜索",
|
||||
"loading-maps": "谱面加载中...",
|
||||
@@ -783,9 +828,14 @@
|
||||
"add-folder": "添加文件夹",
|
||||
"link-folder": "关联文件夹",
|
||||
"unlink-folder": "取消关联文件夹",
|
||||
"link-all": "关联所有"
|
||||
"link-all": "关联所有",
|
||||
"remove-from-the-list": "从列表中移除"
|
||||
}
|
||||
},
|
||||
"adding-shared-folder": {
|
||||
"title": "添加共享文件夹",
|
||||
"description": "链接\"{folder}\"文件夹时可能会遇到问题。您确定要添加它吗?"
|
||||
},
|
||||
"create-launch-shortcut": {
|
||||
"title": "创建快捷方式",
|
||||
"desc": "创建快捷方式使得你可以在不通过 BSManager 的情况下以特定选项启动 BeatSaber",
|
||||
@@ -804,13 +854,12 @@
|
||||
"stay": "记住我",
|
||||
"connect-to-meta": "连接到 Meta"
|
||||
},
|
||||
"original-version-backup-oculus": {
|
||||
"title": "警告",
|
||||
"body": {
|
||||
"must-be-installed-once": "您必须在此设备上从Oculus商店安装Beat Saber,否则Beat Saber将在启动后自动关闭。",
|
||||
"will-backup": "为了启动这个版本,位于您的Oculus库中的Beat Saber的原始安装文件夹将被重命名,并且在Beat Saber关闭时会自动恢复。"
|
||||
},
|
||||
"not-remind-me": "不再提醒我",
|
||||
"enable-oculus-sideloaded-apps": {
|
||||
"title": "启用旁加载",
|
||||
"info-1": "为了启动 Beat Saber,必须启用运行旁加载应用程序的功能。BSManager 将请求管理员权限以自动启用此功能。",
|
||||
"info-2": "旁加载功能允许启动位于 Oculus 库文件夹之外的游戏。",
|
||||
"info-3": "激活 sideloading 后,该功能将保持激活状态,您将不再被提示启用它。",
|
||||
"i-want-to-do-it-myself": "我想自己完成",
|
||||
"understood": "明白了"
|
||||
},
|
||||
"enter-meta-token": {
|
||||
@@ -865,7 +914,8 @@
|
||||
"tags": "标签",
|
||||
"specificities": "general",
|
||||
"requirements": "要求",
|
||||
"exclude": "排除"
|
||||
"exclude": "排除",
|
||||
"leaderboard": "排行榜"
|
||||
},
|
||||
"map-types": {
|
||||
"accuracy": "精确度",
|
||||
@@ -916,11 +966,16 @@
|
||||
},
|
||||
"map-specificities": {
|
||||
"automapper": "AI",
|
||||
"ranked": "ranked",
|
||||
"curated": "curated",
|
||||
"verified": "verified",
|
||||
"fullSpread": "full spread"
|
||||
},
|
||||
"map-leaderboard": {
|
||||
"All": "所有",
|
||||
"Ranked": "排名的",
|
||||
"BeatLeader": "BeatLeader",
|
||||
"ScoreSaber": "ScoreSaber"
|
||||
},
|
||||
"map-excludes": {
|
||||
"installed": "已安装"
|
||||
},
|
||||
@@ -1008,86 +1063,6 @@
|
||||
"no-models": "未找到模型",
|
||||
"no-internet": "无网络连接",
|
||||
"error-occured": "发生了一个错误,稍后重试。"
|
||||
},
|
||||
"link-models": {
|
||||
"avatar": {
|
||||
"title": "关联 Avatar",
|
||||
"desc": "关联 Avatar 允许在所有启用此功能的版本之间共享 Avatar。一旦关联,该版本将受益于共享 Avatar。",
|
||||
"info": "对 Avatar 的添加和删除也将被共享",
|
||||
"keep-models": {
|
||||
"label": "保留 Avatar",
|
||||
"title": "保留 Avatar 将会把当前版本的所有 Avatar复制到共享 Avatar文件夹。否则,该版本的 Avatar将不会被保留,它们将丢失。"
|
||||
},
|
||||
"valid-btn": "关联 Avatar "
|
||||
},
|
||||
"saber": {
|
||||
"title": "关联光剑",
|
||||
"desc": "关联光剑允许在所有启用此功能的版本之间共享光剑。一旦关联,该版本将受益于共享光剑。",
|
||||
"info": "对光剑的添加和删除也将被共享",
|
||||
"keep-models": {
|
||||
"label": "保留光剑",
|
||||
"title": "保留光剑将会把当前版本的所有光剑复制到共享光剑文件夹。否则,该版本的光剑将不会被保留,它们将丢失。"
|
||||
},
|
||||
"valid-btn": "关联光剑"
|
||||
},
|
||||
"platform": {
|
||||
"title": "关联平台",
|
||||
"desc": "关联平台允许在所有启用此功能的版本之间共享平台。一旦关联,该版本将受益于共享平台。",
|
||||
"info": "对平台的添加和删除也将被共享",
|
||||
"keep-models": {
|
||||
"label": "保留平台",
|
||||
"title": "保留平台将会把当前版本的所有平台复制到共享平台文件夹。否则,该版本的平台将不会被保留,它们将丢失。"
|
||||
},
|
||||
"valid-btn": "关联平台"
|
||||
},
|
||||
"bloq": {
|
||||
"title": "关联方块",
|
||||
"desc": "关联方块允许在所有启用此功能的版本之间共享方块。一旦关联,该版本将受益于共享方块。",
|
||||
"info": "对方块的添加和删除也将被共享",
|
||||
"keep-models": {
|
||||
"label": "保留方块",
|
||||
"title": "保留方块将会把当前版本的所有方块复制到共享方块文件夹。否则,该版本的方块将不会被保留,它们将丢失。"
|
||||
},
|
||||
"valid-btn": "关联方块"
|
||||
}
|
||||
},
|
||||
"unlink-models": {
|
||||
"avatar": {
|
||||
"title": "取消关联 Avatar",
|
||||
"desc": "请注意,取消关联 Avatar 将禁用该版本的共享 Avatar 使用,直到重新启用。",
|
||||
"keep-models": {
|
||||
"label": "保留 Avatar",
|
||||
"title": "保留 Avatar 将会在取消关联后把共享文件夹的所有 Avatar 复制到当前版本。如果此项被禁用,Avatar 将不会丢失。"
|
||||
},
|
||||
"valid-btn": "取消关联 Avatar"
|
||||
},
|
||||
"saber": {
|
||||
"title": "取消关联光剑",
|
||||
"desc": "请注意,取消关联光剑将禁用该版本的共享光剑使用,直到重新启用。",
|
||||
"keep-models": {
|
||||
"label": "保留光剑",
|
||||
"title": "保留光剑将会在取消关联后把共享文件夹的所有光剑复制到当前版本。如果此项被禁用,光剑将不会丢失。"
|
||||
},
|
||||
"valid-btn": "取消关联光剑"
|
||||
},
|
||||
"platform": {
|
||||
"title": "取消关联平台",
|
||||
"desc": "请注意,取消关联平台将禁用该版本的共享平台使用,直到重新启用。",
|
||||
"keep-models": {
|
||||
"label": "保留平台",
|
||||
"title": "保留平台将会在取消关联后把共享文件夹的所有平台复制到当前版本。如果此项被禁用,平台将不会丢失。"
|
||||
},
|
||||
"valid-btn": "取消关联平台"
|
||||
},
|
||||
"bloq": {
|
||||
"title": "取消关联方块",
|
||||
"desc": "请注意,取消关联方块将禁用该版本的共享方块使用,直到重新启用。",
|
||||
"keep-models": {
|
||||
"label": "保留方块",
|
||||
"title": "保留方块将会在取消关联后把共享文件夹的所有方块复制到当前版本。如果此项被禁用,方块将不会丢失。"
|
||||
},
|
||||
"valid-btn": "取消关联方块"
|
||||
}
|
||||
}
|
||||
},
|
||||
"notifications": {
|
||||
@@ -1164,14 +1139,6 @@
|
||||
"stop-download": "停止下载",
|
||||
"cancel-download": "取消下载",
|
||||
"open-file": "打开文件",
|
||||
"link-playlists": "链接播放列表",
|
||||
"link-playlist-desc": "链接播放列表允许在所有版本之间共享播放列表。一旦链接,此版本将受益于共享播放列表",
|
||||
"link-playlist-info": "添加和删除播放列表也将被共享",
|
||||
"keep-playlists": "保留播放列表",
|
||||
"keep-playlists-tip": "保留播放列表将把当前版本的播放列表移动到共享播放列表文件夹。否则,它们将丢失",
|
||||
"unlink-playlists": "取消链接播放列表",
|
||||
"unlink-playlist-desc": "警告,取消链接播放列表将不再允许此版本使用共享播放列表。",
|
||||
"unlink-keep-playlists-tip": "保留播放列表将为当前版本创建共享播放列表的副本。否则,此版本将不保留任何播放列表。",
|
||||
"delete-playlist-ask": "删除播放列表?",
|
||||
"delete-playlists-ask": "删除播放列表?",
|
||||
"delete-playlist-desc": "您确定要删除播放列表 \"{playlistTitle}\" 吗?",
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 31 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 782 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 687 KiB |
@@ -0,0 +1,11 @@
|
||||
Enabling Oculus sideloading allows games located outside if your Oculus library to be played on your Oculus Quest.
|
||||
|
||||
- **Step 1:** Start the regedit application by pressing `Win + R` and typing `regedit` in the dialog box.
|
||||
- **Step 2:** Navigate to `HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Oculus VR, LLC\Oculus`.
|
||||
- **Step 3:** Right-click on the right panel and select `New > DWORD (32-bit) Value`.
|
||||
- **Step 4:** Name the new value `AllowDevSideloaded` and set the value to `1`.
|
||||
|
||||
You should end up with something like this:
|
||||

|
||||
|
||||
After completing these steps, you should be able to start Beat Saber from BSManager and play it on your Oculus Quest. If you are still having issues, please join our [Discord](https://discord.gg/uSqbHVpKdV) server for further assistance.
|
||||
@@ -17,14 +17,14 @@ However, you might wonder why it is necessary to provide this token to BSManager
|
||||
|
||||
## Step 3 - Copy your Token
|
||||
In the developer tools :
|
||||
- Open the `Network` tab ***(1)***
|
||||
- Filter for `graph` ***(2)***
|
||||
- Click on the first request ***(3)***
|
||||
- Open the `Headers` tab ***(4)***
|
||||
- Scroll to the bottom to locate your token, it should start with `FRL` ***(5)***
|
||||
- Open the `Network` tab
|
||||
- Filter for `graph`
|
||||
- Click on the first request
|
||||
- Open the `Payload` tab
|
||||
- Locate your token, it should start with `FRL`
|
||||
- Select the token using your mouse and press <kbd>Ctrl</kbd> + <kbd>c</kbd> to copy it
|
||||
|
||||

|
||||

|
||||
|
||||
# Known bugs
|
||||
- [Nothing opens when I press `Ctrl`+`Shift`+`i`.](https://github.com/Zagrios/bs-manager/wiki/Nothing-opens-when-I-press-%60Ctrl%60%E2%80%90%60Shift%60%E2%80%90%60i%60.)
|
||||
- [Nothing opens when I press `Ctrl`+`Shift`+`i`.](https://github.com/Zagrios/bs-manager/wiki/Nothing-opens-when-I-press-%60Ctrl%60%E2%80%90%60Shift%60%E2%80%90%60i%60.)
|
||||
|
||||
+13
-1
@@ -2,7 +2,7 @@
|
||||
|
||||
## Installation
|
||||
|
||||
Go to [Releases](https://github.com/Zagrios/bs-manager/releases) page and go to the latest release. Download the necessary build installer for your distro (see below).
|
||||
Go to [Releases](https://github.com/Zagrios/bs-manager/releases) page and go to the latest alpha release (version 1.5.0-alpha.5 or higher). Download the necessary build installer for your distro (see below).
|
||||
|
||||
### Ubuntu, Debian (deb)
|
||||
|
||||
@@ -47,6 +47,18 @@ Once Proton is installed, when you open your BSManager application for the first
|
||||
|
||||
# Troubleshooting
|
||||
|
||||
## Missing Icons in game
|
||||
|
||||
<p align="center">
|
||||
<img src="../assets/linux-missing-icons.png" alt="Linux Missing Icons" width="300"/>
|
||||
<br>
|
||||
NOTE: "?" are missing unicode icons
|
||||
</p>
|
||||
|
||||
This is due to [BSML](https://github.com/monkeymanboy/BeatSaberMarkupLanguage) using a missing font (`Segoe UI Symbols`) for unicode emojis.
|
||||
|
||||
To fix the issue, you need to get a copy of `seguisym.ttf` from the fonts folder an official copy Windows. Once you have a copy, add the font file to `~/.steam/steam/steamapps/compatdata/620980/pfx/drive_c/windows/Fonts`.
|
||||
|
||||
## Permission denied on "bs-versions.json"
|
||||
|
||||
<pre>
|
||||
|
||||
@@ -63,7 +63,7 @@ const config = {
|
||||
// Read/write home directory access
|
||||
"--filesystem=~/BSManager:create", // Default BSManager installation folder
|
||||
"--filesystem=~/.steam/steam/steamapps:ro", // for the libraryfolders.vdf
|
||||
"--filesystem=~/.steam/steam/steamapps/common:ro", // Steam game folder
|
||||
"--filesystem=~/.steam/steam/steamapps/common:create", // Steam game folder
|
||||
"--filesystem=~/.steam/steam/steamapps/common/Beat Saber:create", // For installing mods/maps to original Beat Saber version
|
||||
// Allow communication with network
|
||||
"--share=network",
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
[target.x86_64-pc-windows-msvc]
|
||||
rustflags = ["-C", "target-feature=+crt-static"]
|
||||
+166
@@ -0,0 +1,166 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "oculus-allow-dev-sideloaded"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"winreg",
|
||||
"winres",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.92"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.38"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.217"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.217"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.93"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c786062daee0d6db1132800e623df74274a0a87322d8e183338e01b3d98d058"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.5.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
||||
|
||||
[[package]]
|
||||
name = "winreg"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winres"
|
||||
version = "0.1.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b68db261ef59e9e52806f688020631e987592bd83619edccda9c47d42cde4f6c"
|
||||
dependencies = [
|
||||
"toml",
|
||||
]
|
||||
Vendored
+7
-9
@@ -1,22 +1,20 @@
|
||||
[package]
|
||||
name = "oculus_symlink_cleaner"
|
||||
name = "oculus-allow-dev-sideloaded"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[build-dependencies]
|
||||
winres = "0.1.12"
|
||||
|
||||
[dependencies]
|
||||
sysinfo = "0.30.6"
|
||||
winreg = "0.52.0"
|
||||
|
||||
[package.metadata.winres]
|
||||
FileDescription = "Enable Sideloaded Apps"
|
||||
LegalCopyright = "Copyright © 2024 Zagrios"
|
||||
CompanyName = "Zagrios"
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
lto = true
|
||||
opt-level = "z"
|
||||
|
||||
[package.metadata.winres]
|
||||
FileDescription = "Clean Oculus Symlink after Beat Saber ends"
|
||||
LegalCopyright = "Copyright © 2024 Zagrios"
|
||||
CompanyName = "Zagrios"
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
extern crate winres;
|
||||
|
||||
fn main() {
|
||||
if cfg!(target_os = "windows") {
|
||||
let mut res = winres::WindowsResource::new();
|
||||
res.set_manifest(r#"
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<security>
|
||||
<requestedPrivileges>
|
||||
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
|
||||
</requestedPrivileges>
|
||||
</security>
|
||||
</trustInfo>
|
||||
</assembly>
|
||||
"#);
|
||||
res.set_icon("./icon.ico");
|
||||
res.compile().unwrap();
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 67 KiB |
@@ -0,0 +1,22 @@
|
||||
use winreg::enums::HKEY_LOCAL_MACHINE;
|
||||
use winreg::RegKey;
|
||||
|
||||
const PATH: &str = "SOFTWARE\\Wow6432Node\\Oculus VR, LLC\\Oculus";
|
||||
|
||||
fn main() {
|
||||
let hklm = RegKey::predef(HKEY_LOCAL_MACHINE);
|
||||
|
||||
// Create (or open if it already exists) the subkey
|
||||
let (key, _) = match hklm.create_subkey(PATH) {
|
||||
Ok(res) => res,
|
||||
Err(err) => return println!("{}", err.to_string()),
|
||||
};
|
||||
|
||||
let res = key.set_value("AllowDevSideloaded", &1u32);
|
||||
|
||||
if let Err(err) = res {
|
||||
return println!("{}", err.to_string());
|
||||
}
|
||||
|
||||
println!("AllowDevSideloaded = 1 has been successfully set in {PATH}");
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
[target.x86_64-pc-windows-msvc]
|
||||
rustflags = ["-C", "target-feature=+crt-static"]
|
||||
-281
@@ -1,281 +0,0 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-deque"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d"
|
||||
dependencies = [
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-epoch"
|
||||
version = "0.9.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345"
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.153"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
|
||||
|
||||
[[package]]
|
||||
name = "ntapi"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "oculus_symlink_cleaner"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"sysinfo",
|
||||
"winres",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.78"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon"
|
||||
version = "1.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e4963ed1bc86e4f3ee217022bd855b297cef07fb9eac5dfa1f788b220b49b3bd"
|
||||
dependencies = [
|
||||
"either",
|
||||
"rayon-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon-core"
|
||||
version = "1.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
|
||||
dependencies = [
|
||||
"crossbeam-deque",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.197"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.197"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.52"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sysinfo"
|
||||
version = "0.30.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6746919caf9f2a85bff759535664c060109f21975c5ac2e8652e60102bd4d196"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
"ntapi",
|
||||
"once_cell",
|
||||
"rayon",
|
||||
"windows",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.5.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be"
|
||||
dependencies = [
|
||||
"windows-core",
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8"
|
||||
|
||||
[[package]]
|
||||
name = "winres"
|
||||
version = "0.1.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b68db261ef59e9e52806f688020631e987592bd83619edccda9c47d42cde4f6c"
|
||||
dependencies = [
|
||||
"toml",
|
||||
]
|
||||
-9
@@ -1,9 +0,0 @@
|
||||
extern crate winres;
|
||||
|
||||
fn main() {
|
||||
if cfg!(target_os = "windows") {
|
||||
let mut res = winres::WindowsResource::new();
|
||||
res.set_icon("./icon.ico");
|
||||
res.compile().unwrap();
|
||||
}
|
||||
}
|
||||
-78
@@ -1,78 +0,0 @@
|
||||
use sysinfo::{Pid, ProcessRefreshKind, System};
|
||||
use std::{env, thread, time::Duration, fs};
|
||||
use std::ffi::OsStr;
|
||||
use std::path::Path;
|
||||
use std::str::FromStr;
|
||||
|
||||
const BEAT_SABER_OCULUS_FOLDER_NAME: &str = "hyperbolic-magnetism-beat-saber";
|
||||
|
||||
fn main() {
|
||||
let args: Vec<String> = env::args().collect();
|
||||
if args.len() != 3 {
|
||||
eprintln!("Usage: {} <pid> <path_to_directory>", args[0]);
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
let pid: Pid = Pid::from_str(&args[1]).expect("Invalid pid");
|
||||
let directory_path = Path::new(&args[2]);
|
||||
|
||||
if directory_path.file_name() != Some(OsStr::new(BEAT_SABER_OCULUS_FOLDER_NAME)) {
|
||||
eprintln!("Directory name is not {}", BEAT_SABER_OCULUS_FOLDER_NAME);
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
// we want to monitor only processes
|
||||
let mut system = System::new_with_specifics(sysinfo::RefreshKind::new().with_processes(ProcessRefreshKind::everything()));
|
||||
|
||||
loop {
|
||||
system.refresh_processes();
|
||||
let process = system.process(pid);
|
||||
|
||||
match process {
|
||||
Some(_) => {
|
||||
println!("Process {} is still running.", pid);
|
||||
thread::sleep(Duration::from_secs(2));
|
||||
},
|
||||
None => {
|
||||
println!("Process {} has stopped, deleting directory {:?}", pid, directory_path);
|
||||
match delete_dir_if_is_symlink(directory_path) {
|
||||
Ok(_) => {
|
||||
println!("Directory {:?} deleted successfully.", directory_path);
|
||||
if let Err(e) = rename_specific_backup_directory(directory_path.parent().unwrap()) {
|
||||
eprintln!("Failed to rename backup directory: {}", e);
|
||||
}
|
||||
},
|
||||
Err(e) => eprintln!("Failed to delete directory {:?}: {}", directory_path, e),
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn delete_dir_if_is_symlink(path: &Path) -> Result<(), Box<dyn std::error::Error>>{
|
||||
match path.symlink_metadata()?.file_type().is_symlink() {
|
||||
true => {
|
||||
fs::remove_dir_all(path)?;
|
||||
Ok(())
|
||||
},
|
||||
false => {
|
||||
eprintln!("Path {:?} is not a symlink.", path);
|
||||
Err("Path is not a symlink.".into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn rename_specific_backup_directory(parent_path: &Path) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let backup_dir_name = format!("{}.bsmbak", BEAT_SABER_OCULUS_FOLDER_NAME);
|
||||
let backup_path = parent_path.join(&backup_dir_name);
|
||||
if backup_path.exists() && backup_path.is_dir() {
|
||||
let new_path = parent_path.join(BEAT_SABER_OCULUS_FOLDER_NAME);
|
||||
fs::rename(&backup_path, &new_path)?;
|
||||
println!("Renamed {:?} to {:?}", backup_path, new_path);
|
||||
Ok(())
|
||||
} else {
|
||||
Err(format!("Backup directory {:?} does not exist or is not a directory.", backup_path).into())
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+11
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "bs-manager",
|
||||
"version": "1.5.0-alpha.5",
|
||||
"version": "1.5.0-alpha.6",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "bs-manager",
|
||||
"version": "1.5.0-alpha.5",
|
||||
"version": "1.5.0-alpha.6",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -29,6 +29,7 @@
|
||||
"format-duration": "^3.0.2",
|
||||
"framer-motion": "^11.2.6",
|
||||
"fs-extra": "^11.2.0",
|
||||
"global-agent": "^3.0.0",
|
||||
"got": "^14.4.4",
|
||||
"history": "^5.3.0",
|
||||
"is-elevated": "^4.0.0",
|
||||
@@ -74,6 +75,7 @@
|
||||
"@types/color": "^3.0.3",
|
||||
"@types/crypto-js": "^4.2.1",
|
||||
"@types/dateformat": "^5.0.0",
|
||||
"@types/global-agent": "^2.1.3",
|
||||
"@types/got": "^9.6.12",
|
||||
"@types/jest": "^29.5.11",
|
||||
"@types/node": "22.8.6",
|
||||
@@ -6158,6 +6160,13 @@
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/global-agent": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/global-agent/-/global-agent-2.1.3.tgz",
|
||||
"integrity": "sha512-rGtZZcgZcKWuKNTkGBGsqyOQ7Nn2MjXh4+xeZbf+5b5KMUx8H1rTqLRackxos7pUlreszbYjQcop5JvqCnZlLw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/got": {
|
||||
"version": "9.6.12",
|
||||
"resolved": "https://registry.npmjs.org/@types/got/-/got-9.6.12.tgz",
|
||||
|
||||
+3
-1
@@ -2,7 +2,7 @@
|
||||
"name": "bs-manager",
|
||||
"description": "Manage maps, mods and more for Beat Saber",
|
||||
"main": "./.erb/dll/main.bundle.dev.js",
|
||||
"version": "1.5.0-alpha.5",
|
||||
"version": "1.5.0-alpha.6",
|
||||
"scripts": {
|
||||
"build-rust-scripts": "ts-node ./.erb/scripts/build-rust-scripts.js",
|
||||
"build": "concurrently \"npm run build:main\" \"npm run build:renderer\"",
|
||||
@@ -77,6 +77,7 @@
|
||||
"@types/color": "^3.0.3",
|
||||
"@types/crypto-js": "^4.2.1",
|
||||
"@types/dateformat": "^5.0.0",
|
||||
"@types/global-agent": "^2.1.3",
|
||||
"@types/got": "^9.6.12",
|
||||
"@types/jest": "^29.5.11",
|
||||
"@types/node": "22.8.6",
|
||||
@@ -170,6 +171,7 @@
|
||||
"format-duration": "^3.0.2",
|
||||
"framer-motion": "^11.2.6",
|
||||
"fs-extra": "^11.2.0",
|
||||
"global-agent": "^3.0.0",
|
||||
"got": "^14.4.4",
|
||||
"history": "^5.3.0",
|
||||
"is-elevated": "^4.0.0",
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "bs-manager",
|
||||
"version": "1.5.0-alpha.5",
|
||||
"version": "1.5.0-alpha.6",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "bs-manager",
|
||||
"version": "1.5.0-alpha.5",
|
||||
"version": "1.5.0-alpha.6",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "bs-manager",
|
||||
"version": "1.5.0-alpha.5",
|
||||
"version": "1.5.0-alpha.6",
|
||||
"description": "BSManager",
|
||||
"main": "./dist/main/main.js",
|
||||
"author": {
|
||||
|
||||
@@ -196,8 +196,14 @@ ifDescribe(IS_LINUX)("Test os.helpers isProcessRunning", () => {
|
||||
const running = await isProcessRunning(`bs-manager-${crypto.randomUUID()}`);
|
||||
expect(running).toBe(false);
|
||||
|
||||
// No errors received
|
||||
expect(logSpy).toHaveBeenCalledTimes(0);
|
||||
// Throws because grep couldn't find any process with that name
|
||||
expect(logSpy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("Empty process name", async () => {
|
||||
const running = await isProcessRunning("");
|
||||
expect(running).toBe(false);
|
||||
expect(logSpy).toHaveBeenCalledTimes(0);
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
@@ -3,9 +3,6 @@ import log from "electron-log";
|
||||
import psList from "ps-list";
|
||||
import { IS_FLATPAK } from "main/constants";
|
||||
|
||||
// There are 2 erroneous lines ps | grep which is both the ps and grep calls themselves
|
||||
const MIN_PROCESS_COUNT_LINUX = 2;
|
||||
|
||||
type LinuxOptions = {
|
||||
// Add the prefix to the command
|
||||
// eg. command - "./Beat Saber.exe" --no-yeet, prefix - "path/to/proton" run
|
||||
@@ -44,8 +41,8 @@ function updateCommand(command: string, options: BsmSpawnOptions) {
|
||||
|
||||
if (process.platform === "linux") {
|
||||
// "/bin/sh" does not see flatpak-spawn
|
||||
// Most Debian and Arch should also support "/bin/bash"
|
||||
options.options.shell = "/bin/bash";
|
||||
// All distros should support "bash" by default
|
||||
options.options.shell = "bash";
|
||||
|
||||
if (options.linux?.prefix) {
|
||||
command = `${options.linux.prefix} ${command}`;
|
||||
@@ -101,14 +98,24 @@ export function bsmExec(command: string, options?: BsmExecOptions): Promise<{
|
||||
});
|
||||
}
|
||||
|
||||
// Transform command from "steam" to "[s]team"
|
||||
// NOTE: Can add an option to isProcessRunning/getProcessId to ignore this transformation
|
||||
// in the future if needed
|
||||
const transformProcessNameForPS = (name: string) => `[${name.at(0)}]${name.substring(1)}`;
|
||||
|
||||
async function isProcessRunningLinux(name: string): Promise<boolean> {
|
||||
if (!name) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const { stdout: count } = await bsmExec(`ps awwxo args | grep -c "${name}"`, {
|
||||
const processName = transformProcessNameForPS(name);
|
||||
const { stdout: count } = await bsmExec(`ps awwxo args | grep -c "${processName}"`, {
|
||||
log: true,
|
||||
flatpak: { host: IS_FLATPAK },
|
||||
});
|
||||
|
||||
return +count.trim() > MIN_PROCESS_COUNT_LINUX;
|
||||
return +count.trim() > 0;
|
||||
} catch(error) {
|
||||
log.error(error);
|
||||
return false;
|
||||
@@ -143,14 +150,22 @@ async function isProcessRunningWindows(name: string): Promise<boolean> {
|
||||
}
|
||||
|
||||
async function getProcessIdLinux(name: string): Promise<number | null> {
|
||||
if (!name) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const { stdout } = await bsmExec(`ps awwxo pid,args | grep "${name}"`, {
|
||||
const processName = transformProcessNameForPS(name);
|
||||
const { stdout } = await bsmExec(`ps awwxo pid,args | grep "${processName}"`, {
|
||||
log: true,
|
||||
flatpak: { host: IS_FLATPAK },
|
||||
});
|
||||
|
||||
if (!stdout) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const line = stdout.split("\n")
|
||||
.slice(0, -MIN_PROCESS_COUNT_LINUX)
|
||||
.map(line => line.trimStart())
|
||||
.find(line => line.includes(name) && !line.includes("grep"));
|
||||
return line ? +line.split(" ").at(0) : null;
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
import log from 'electron-log';
|
||||
import { RegDwordValue, RegSzValue } from "regedit-rs"
|
||||
import { execOnOs } from "./env.helpers";
|
||||
import { bootstrap } from 'global-agent';
|
||||
import { StaticConfigurationService } from "../services/static-configuration.service";
|
||||
|
||||
const staticConfig = StaticConfigurationService.getInstance();
|
||||
|
||||
const { list } = (execOnOs({ win32: () => require("regedit-rs") }, true) ?? {}) as typeof import("regedit-rs");
|
||||
|
||||
async function isProxyEnabled(): Promise<boolean>{
|
||||
const res = await list("HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings");
|
||||
const key = res["HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings"];
|
||||
if(!key.exists){ throw new Error("Key \"HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\" not exist"); }
|
||||
const registryValue = key.values.ProxyEnable as RegDwordValue;
|
||||
if(!registryValue){ throw new Error("Value \"ProxyEnable\" not exist"); }
|
||||
return registryValue.value === 1;
|
||||
}
|
||||
|
||||
async function getProxyServer(): Promise<string>{
|
||||
const res = await list("HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings");
|
||||
const key = res["HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings"];
|
||||
if(!key.exists){ throw new Error("Key \"HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\" not exist"); }
|
||||
const registryValue = key.values.ProxyServer as RegSzValue;
|
||||
if(!registryValue){ throw new Error("Value \"ProxyServer\" not exist"); }
|
||||
return registryValue.value;
|
||||
}
|
||||
|
||||
async function getProxyOverride(): Promise<string>{
|
||||
const res = await list("HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings");
|
||||
const key = res["HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings"];
|
||||
if(!key.exists){ throw new Error("Key \"HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\" not exist"); }
|
||||
const registryValue = key.values.ProxyOverride as RegSzValue;
|
||||
if(!registryValue){ throw new Error("Value \"ProxyOverride\" not exist"); }
|
||||
return registryValue.value;
|
||||
}
|
||||
|
||||
async function enableWindowsProxy(enable: boolean): Promise<void> {
|
||||
if (!(await isProxyEnabled().catch(err => log.error(err)))) {
|
||||
log.info("enableWindowsProxy: System proxy not detected");
|
||||
return;
|
||||
}
|
||||
|
||||
let { GLOBAL_AGENT: globalProxyAgent } = global as any;
|
||||
if (!globalProxyAgent) { // If this is undefined, call bootstrap to set it up
|
||||
if (!bootstrap()) {
|
||||
log.error("enableWindowsProxy: Could not setup proxy stuff");
|
||||
return;
|
||||
}
|
||||
globalProxyAgent = (global as any).GLOBAL_AGENT;
|
||||
}
|
||||
|
||||
if (enable) {
|
||||
const httpProxyUrl = `http://${await getProxyServer().catch(err => log.error(err))}`
|
||||
globalProxyAgent.HTTP_PROXY = httpProxyUrl;
|
||||
globalProxyAgent.HTTPS_PROXY = httpProxyUrl;
|
||||
globalProxyAgent.NO_PROXY = `${await getProxyOverride().catch(err => log.error(err))}`;
|
||||
|
||||
log.info(`enableWindowsProxy: Using system proxy: ${httpProxyUrl}`);
|
||||
} else {
|
||||
delete globalProxyAgent.HTTP_PROXY;
|
||||
delete globalProxyAgent.HTTPS_PROXY;
|
||||
delete globalProxyAgent.NO_PROXY;
|
||||
|
||||
log.info("enableWindowsProxy: proxy disabled");
|
||||
}
|
||||
}
|
||||
|
||||
export function configureProxy() {
|
||||
if (process.platform === "win32") {
|
||||
enableWindowsProxy(staticConfig.get("use-system-proxy"));
|
||||
|
||||
staticConfig.$watch("use-system-proxy").subscribe((useSystemProxy) => {
|
||||
enableWindowsProxy(useSystemProxy);
|
||||
|
||||
log.info(`configureProxy: UseSystemProxy is set to ${useSystemProxy}`);
|
||||
});
|
||||
} else {
|
||||
log.info("configureProxy: Unsupported platform");
|
||||
}
|
||||
}
|
||||
@@ -4,27 +4,32 @@ import { from } from "rxjs";
|
||||
|
||||
const ipc = IpcService.getInstance();
|
||||
|
||||
ipc.on("get-available-mods", (args, reply) => {
|
||||
ipc.on("bs-mods.get-available-mods", (args, reply) => {
|
||||
const modsManager = BsModsManagerService.getInstance();
|
||||
reply(from(modsManager.getAvailableMods(args)));
|
||||
});
|
||||
|
||||
ipc.on("get-installed-mods", (args, reply) => {
|
||||
ipc.on("bs-mods.get-installed-mods", (args, reply) => {
|
||||
const modsManager = BsModsManagerService.getInstance();
|
||||
reply(from(modsManager.getInstalledMods(args)));
|
||||
});
|
||||
|
||||
ipc.on("install-mods", (args, reply) => {
|
||||
ipc.on("bs-mods.import-mods", (args, reply) => {
|
||||
const modsManager = BsModsManagerService.getInstance();
|
||||
reply(modsManager.importMods(args.paths, args.version));
|
||||
});
|
||||
|
||||
ipc.on("bs-mods.install-mods", (args, reply) => {
|
||||
const modsManager = BsModsManagerService.getInstance();
|
||||
reply(modsManager.installMods(args.mods, args.version));
|
||||
});
|
||||
|
||||
ipc.on("uninstall-mods", (args, reply) => {
|
||||
ipc.on("bs-mods.uninstall-mods", (args, reply) => {
|
||||
const modsManager = BsModsManagerService.getInstance();
|
||||
reply(modsManager.uninstallMods(args.mods, args.version));
|
||||
});
|
||||
|
||||
ipc.on("uninstall-all-mods", (args, reply) => {
|
||||
ipc.on("bs-mods.uninstall-all-mods", (args, reply) => {
|
||||
const modsManager = BsModsManagerService.getInstance();
|
||||
reply(modsManager.uninstallAllMods(args));
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { BsOculusDownloaderService } from "../../services/bs-version-download/bs-oculus-downloader.service";
|
||||
import { BsSteamDownloaderService } from "../../services/bs-version-download/bs-steam-downloader.service";
|
||||
import { IpcService } from "../../services/ipc.service";
|
||||
import { of } from "rxjs";
|
||||
import { from, of } from "rxjs";
|
||||
import { BSLocalVersionService } from "../../services/bs-local-version.service";
|
||||
|
||||
const ipc = IpcService.getInstance();
|
||||
@@ -42,14 +42,29 @@ ipc.on("send-input-bs-download", (args, reply) => {
|
||||
|
||||
// #region Oculus
|
||||
|
||||
ipc.on("bs-oculus-download", async (args, reply) => {
|
||||
ipc.on("bs-oculus-download", (args, reply) => {
|
||||
const oculusDownloader = BsOculusDownloaderService.getInstance();
|
||||
reply(oculusDownloader.downloadVersion(args));
|
||||
});
|
||||
|
||||
ipc.on("bs-oculus-stop-download", async (_, reply) => {
|
||||
ipc.on("bs-oculus-stop-download", (_, reply) => {
|
||||
const oculusDownloader = BsOculusDownloaderService.getInstance();
|
||||
reply(of(oculusDownloader.stopDownload()));
|
||||
});
|
||||
|
||||
ipc.on("login-with-meta", (stay, reply) => {
|
||||
const oculusDownloader = BsOculusDownloaderService.getInstance();
|
||||
reply(from(oculusDownloader.getUserTokenFromMetaAuth(stay)));
|
||||
});
|
||||
|
||||
ipc.on("delete-meta-session", (_, reply) => {
|
||||
const oculusDownloader = BsOculusDownloaderService.getInstance();
|
||||
reply(from(oculusDownloader.clearAuthToken()));
|
||||
});
|
||||
|
||||
ipc.on("meta-session-exists", (_, reply) => {
|
||||
const oculusDownloader = BsOculusDownloaderService.getInstance();
|
||||
reply(from(oculusDownloader.metaSessionExists()));
|
||||
});
|
||||
|
||||
// #endregion
|
||||
|
||||
@@ -2,10 +2,11 @@ import { shell } from "electron";
|
||||
import { BSVersionLibService } from "../services/bs-version-lib.service";
|
||||
import { BSLocalVersionService } from "../services/bs-local-version.service";
|
||||
import { IpcService } from "../services/ipc.service";
|
||||
import { from } from "rxjs";
|
||||
import { from, of } from "rxjs";
|
||||
import path from "path";
|
||||
import { pathExists } from "fs-extra";
|
||||
import { VersionFolderLinkerService } from "../services/version-folder-linker.service";
|
||||
import { FolderLinkerService } from "main/services/folder-linker.service";
|
||||
|
||||
const ipc = IpcService.getInstance();
|
||||
|
||||
@@ -67,3 +68,8 @@ ipc.on("relink-all-versions-folders", (_, reply) => {
|
||||
const versionLinker = VersionFolderLinkerService.getInstance();
|
||||
reply(from(versionLinker.relinkAllVersionsFolders()));
|
||||
});
|
||||
|
||||
ipc.on("get-shared-folder", (_, reply) => {
|
||||
const versionLinker = FolderLinkerService.getInstance();
|
||||
reply(of(versionLinker.sharedFolder()));
|
||||
});
|
||||
|
||||
@@ -15,3 +15,4 @@ import "./bs-model-ipcs";
|
||||
import "./bs-version-download/bs-download-ipcs";
|
||||
import "./static-configuration.ipcs";
|
||||
import "./linux.ipcs.ts";
|
||||
import "./oculus.ipcs";
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import { OculusService } from "../services/oculus.service";
|
||||
import { IpcService } from "../services/ipc.service";
|
||||
import { from } from "rxjs";
|
||||
|
||||
const ipc = IpcService.getInstance();
|
||||
|
||||
ipc.on("is-oculus-sideloaded-apps-enabled", (_, reply) => {
|
||||
const oculusService = OculusService.getInstance();
|
||||
reply(from(oculusService.isSideLoadedAppsEnabled()));
|
||||
});
|
||||
|
||||
ipc.on("enable-oculus-sideloaded-apps", (_, reply) => {
|
||||
const oculusService = OculusService.getInstance();
|
||||
reply(from(oculusService.enableSideloadedApps()));
|
||||
});
|
||||
@@ -12,3 +12,7 @@ ipc.on("static-configuration.get", (args, reply) => {
|
||||
ipc.on("static-configuration.set", (args, reply) => {
|
||||
reply(from(staticConfig.set(args.key, args.value)));
|
||||
});
|
||||
|
||||
ipc.on("static-configuration.delete", (key, reply) => {
|
||||
reply(of(staticConfig.delete(key)));
|
||||
});
|
||||
|
||||
+14
-2
@@ -25,6 +25,7 @@ import { FileAssociationService } from "./services/file-association.service";
|
||||
import { SongDetailsCacheService } from "./services/additional-content/maps/song-details-cache.service";
|
||||
import { readdirSync, statSync, unlinkSync } from "fs-extra";
|
||||
import { StaticConfigurationService } from "./services/static-configuration.service";
|
||||
import { configureProxy } from './helpers/proxy.helpers';
|
||||
|
||||
const isDebug = process.env.NODE_ENV === "development" || process.env.DEBUG_PROD === "true";
|
||||
const staticConfig = StaticConfigurationService.getInstance();
|
||||
@@ -46,6 +47,7 @@ staticConfig.take("disable-hadware-acceleration", disabled => {
|
||||
}
|
||||
});
|
||||
|
||||
configureProxy();
|
||||
|
||||
if (process.env.NODE_ENV === "production") {
|
||||
const sourceMapSupport = require("source-map-support");
|
||||
@@ -69,7 +71,7 @@ const installExtensions = async () => {
|
||||
.catch(log.error);
|
||||
};
|
||||
|
||||
const createWindow = async (window: AppWindow = "launcher.html") => {
|
||||
const createWindow = async (window: AppWindow) => {
|
||||
if (isDebug) {
|
||||
await installExtensions();
|
||||
}
|
||||
@@ -131,7 +133,9 @@ if (!gotTheLock) {
|
||||
} else if (associatedFile) {
|
||||
FileAssociationService.getInstance().handleFileAssociation(associatedFile);
|
||||
} else {
|
||||
createWindow();
|
||||
createWindow(process.platform === "linux"
|
||||
? "index.html" : "launcher.html"
|
||||
);
|
||||
}
|
||||
|
||||
SteamLauncherService.getInstance().restoreSteamVR();
|
||||
@@ -262,3 +266,11 @@ function deleteOlestLogs(): void{
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function addFilterStringLog(filter: string): void {
|
||||
filterStrings.add(filter);
|
||||
}
|
||||
|
||||
export function addFilterPatternLog(filter: RegExp): void {
|
||||
filterPatterns.add(filter);
|
||||
}
|
||||
|
||||
@@ -1,30 +1,48 @@
|
||||
import path from "path";
|
||||
import fs from "fs";
|
||||
import { ChildProcessWithoutNullStreams, SpawnOptionsWithoutStdio, spawn } from "child_process";
|
||||
import { Observable, ReplaySubject, Subscriber, filter, map, share } from "rxjs";
|
||||
import { DepotDownloaderArgsOptions, DepotDownloaderErrorEvent, DepotDownloaderEvent, DepotDownloaderEventType, DepotDownloaderEventTypes, DepotDownloaderInfoEvent, DepotDownloaderWarningEvent } from "../../shared/models/bs-version-download/depot-downloader.model";
|
||||
import { UtilsService } from 'main/services/utils.service';
|
||||
import { CustomError } from "shared/models/exceptions/custom-error.class";
|
||||
|
||||
export class DepotDownloader {
|
||||
|
||||
private static readonly EXE_PATH = path.join(
|
||||
UtilsService.getInstance().getAssetsScriptsPath(),
|
||||
process.platform === "win32" ? "DepotDownloader.exe" : "DepotDownloader"
|
||||
);
|
||||
|
||||
private process: ChildProcessWithoutNullStreams;
|
||||
private processOut$: Observable<string>;
|
||||
private subscriber: Subscriber<string>;
|
||||
|
||||
|
||||
public constructor(
|
||||
options: {
|
||||
command: string, args?: string[], options?: SpawnOptionsWithoutStdio, echoStartData?: unknown
|
||||
},
|
||||
args?: string[], options?: SpawnOptionsWithoutStdio, echoStartData?: unknown
|
||||
},
|
||||
logger?: Logger
|
||||
){
|
||||
this.processOut$ = new Observable<string>(subscriber => {
|
||||
) {
|
||||
if (!fs.existsSync(DepotDownloader.EXE_PATH)) {
|
||||
throw new CustomError(
|
||||
"DepotDownloader executable not found",
|
||||
process.platform === "win32"
|
||||
? DepotDownloaderErrorEvent.ExeNotFoundWindows
|
||||
: DepotDownloaderErrorEvent.ExeNotFoundLinux
|
||||
);
|
||||
}
|
||||
|
||||
this.processOut$ = new Observable<string>(subscriber => {
|
||||
this.subscriber = subscriber;
|
||||
|
||||
this.process = spawn(options.command, options.args ?? [], options.options);
|
||||
this.process = spawn(DepotDownloader.EXE_PATH, options.args ?? [], options.options);
|
||||
|
||||
subscriber.next(`[Info]|[Start]|${JSON.stringify(options.echoStartData) ?? ""}`);
|
||||
|
||||
|
||||
this.process.stdout.on("data", data => {
|
||||
const stringData: string = data.toString();
|
||||
|
||||
|
||||
if(!stringData.includes(DepotDownloaderInfoEvent.Progress) && !stringData.includes(DepotDownloaderInfoEvent.Validated)){
|
||||
logger?.info("DepotDownloader stdout:", stringData);
|
||||
}
|
||||
@@ -32,7 +50,7 @@ export class DepotDownloader {
|
||||
const lines: string[] = stringData.split("\n");
|
||||
lines.forEach(line => subscriber.next(line));
|
||||
});
|
||||
|
||||
|
||||
this.process.on("error", error => subscriber.error(error));
|
||||
this.process.stderr.on("error", error => subscriber.error(error));
|
||||
this.process.on("exit", () => subscriber.complete());
|
||||
@@ -74,7 +92,7 @@ export class DepotDownloader {
|
||||
data: splitedLine[2],
|
||||
}
|
||||
|
||||
}),
|
||||
}),
|
||||
filter(Boolean));
|
||||
}
|
||||
|
||||
@@ -93,7 +111,7 @@ export class DepotDownloader {
|
||||
const args: string[] = [];
|
||||
|
||||
for(const [key, value] of Object.entries(depotDownloaderArgs)){
|
||||
|
||||
|
||||
if(value === true){
|
||||
args.push(`-${key}`);
|
||||
}
|
||||
@@ -102,7 +120,7 @@ export class DepotDownloader {
|
||||
args.push(`${value}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
@@ -112,4 +130,4 @@ interface Logger {
|
||||
info: (...args: unknown[]) => void,
|
||||
warn: (...args: unknown[]) => void,
|
||||
error: (...args: unknown[]) => void,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,10 +19,16 @@ export class AutoUpdaterService {
|
||||
autoUpdater.autoDownload = false;
|
||||
}
|
||||
|
||||
public isUpdateAvailable(): Promise<boolean> {
|
||||
return autoUpdater.checkForUpdates().then(info => {
|
||||
return !!info?.updateInfo && gt(info.updateInfo.version, autoUpdater.currentVersion.version);
|
||||
}).catch(() => false);
|
||||
public async isUpdateAvailable(): Promise<boolean> {
|
||||
return autoUpdater.checkForUpdates()
|
||||
.then(info => {
|
||||
return !!info?.updateInfo && gt(
|
||||
info.updateInfo.version, autoUpdater.currentVersion.version
|
||||
)})
|
||||
.catch(error => {
|
||||
log.error("Could not get update", error);
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
public downloadUpdate(): Observable<Progression> {
|
||||
|
||||
@@ -7,6 +7,7 @@ import { sToMs } from "../../../shared/helpers/time.helpers";
|
||||
import { LinuxService } from "../linux.service";
|
||||
import { bsmSpawn } from "main/helpers/os.helpers";
|
||||
import { IS_FLATPAK } from "main/constants";
|
||||
import { LaunchMods } from "shared/models/bs-launch/launch-option.interface";
|
||||
|
||||
export abstract class AbstractLauncherService {
|
||||
|
||||
@@ -24,16 +25,20 @@ export abstract class AbstractLauncherService {
|
||||
if(!launchOptions.version.steam && !launchOptions.version.oculus){
|
||||
launchArgs.push("--no-yeet")
|
||||
}
|
||||
if (launchOptions.oculus) {
|
||||
if(launchOptions.launchMods?.includes(LaunchMods.OCULUS)) {
|
||||
launchArgs.push("-vrmode");
|
||||
launchArgs.push("oculus");
|
||||
}
|
||||
if (launchOptions.desktop) {
|
||||
if(launchOptions.launchMods?.includes(LaunchMods.FPFC)) {
|
||||
launchArgs.push("fpfc");
|
||||
}
|
||||
if (launchOptions.debug) {
|
||||
if(launchOptions.launchMods?.includes(LaunchMods.DEBUG)) {
|
||||
launchArgs.push("--verbose");
|
||||
}
|
||||
if(launchOptions.launchMods?.includes(LaunchMods.EDITOR)) {
|
||||
launchArgs.push("editor");
|
||||
}
|
||||
|
||||
if (launchOptions.additionalArgs) {
|
||||
launchArgs.push(...launchOptions.additionalArgs);
|
||||
}
|
||||
@@ -52,7 +57,7 @@ export abstract class AbstractLauncherService {
|
||||
spawnOptions.shell = true; // For windows to spawn properly
|
||||
return bsmSpawn(`"${bsExePath}"`, {
|
||||
args, options: spawnOptions, log: true,
|
||||
linux: { prefix: this.linux.getProtonCommand() },
|
||||
linux: { prefix: this.linux.getProtonPrefix() },
|
||||
flatpak: {
|
||||
host: IS_FLATPAK,
|
||||
env: [
|
||||
|
||||
@@ -20,6 +20,8 @@ import { SteamLauncherService } from "./steam-launcher.service";
|
||||
import { OculusLauncherService } from "./oculus-launcher.service";
|
||||
import { BSVersion } from "shared/bs-version.interface";
|
||||
import { BsStore } from "../../../shared/models/bs-store.enum";
|
||||
import { LaunchMod, LaunchMods } from "shared/models/bs-launch/launch-option.interface";
|
||||
import { StaticConfigurationService } from "../static-configuration.service";
|
||||
|
||||
export class BSLauncherService {
|
||||
private static instance: BSLauncherService;
|
||||
@@ -31,6 +33,7 @@ export class BSLauncherService {
|
||||
private readonly remoteVersion: BSVersionLibService;
|
||||
private readonly steamLauncher: SteamLauncherService;
|
||||
private readonly oculusLauncher: OculusLauncherService;
|
||||
private readonly staticConfig: StaticConfigurationService;
|
||||
|
||||
public static getInstance(): BSLauncherService {
|
||||
if (!BSLauncherService.instance) {
|
||||
@@ -47,6 +50,7 @@ export class BSLauncherService {
|
||||
this.remoteVersion = BSVersionLibService.getInstance();
|
||||
this.steamLauncher = SteamLauncherService.getInstance();
|
||||
this.oculusLauncher = OculusLauncherService.getInstance();
|
||||
this.staticConfig = StaticConfigurationService.getInstance();
|
||||
|
||||
this.bsmProtocolService.on("launch", link => {
|
||||
log.info("Launch from bsm protocol", link.toString());
|
||||
@@ -72,6 +76,8 @@ export class BSLauncherService {
|
||||
return throwError(() => new Error("Unable to get launcher for the provided version"));
|
||||
}
|
||||
|
||||
this.staticConfig.set("last-version-launched", launchOptions.version);
|
||||
|
||||
return launcher.launch(launchOptions);
|
||||
}
|
||||
|
||||
@@ -94,6 +100,14 @@ export class BSLauncherService {
|
||||
}
|
||||
|
||||
private shortcutParamsToLaunchOption(params: ShortcutParams): LaunchOption{
|
||||
|
||||
const launchMods: LaunchMod[] = [];
|
||||
|
||||
if(params.oculusMode === "true"){ launchMods.push(LaunchMods.OCULUS); }
|
||||
if(params.desktopMode === "true"){ launchMods.push(LaunchMods.FPFC); }
|
||||
if(params.debug === "true"){ launchMods.push(LaunchMods.DEBUG); }
|
||||
if(params.skipSteam === "true"){ launchMods.push(LaunchMods.SKIP_STEAM); }
|
||||
|
||||
const res: LaunchOption = {
|
||||
version: {
|
||||
BSVersion: params.version,
|
||||
@@ -102,10 +116,8 @@ export class BSLauncherService {
|
||||
oculus: params.versionOculus === "true",
|
||||
ino: +params.versionIno
|
||||
},
|
||||
oculus: params.oculusMode === "true",
|
||||
desktop: params.desktopMode === "true",
|
||||
debug: params.debug === "true",
|
||||
additionalArgs: params.additionalArgs
|
||||
additionalArgs: params.additionalArgs,
|
||||
launchMods,
|
||||
};
|
||||
|
||||
return res;
|
||||
@@ -119,10 +131,12 @@ export class BSLauncherService {
|
||||
if(launchOptions.version.oculus){ res.versionOculus = `${launchOptions.version.oculus}`; }
|
||||
if(launchOptions.version.ino){ res.versionIno = `${launchOptions.version.ino}`; }
|
||||
|
||||
if(launchOptions.oculus){ res.oculusMode = "true"; }
|
||||
if(launchOptions.desktop){ res.desktopMode = "true"; }
|
||||
if(launchOptions.debug){ res.debug = "true"; }
|
||||
if(launchOptions.launchMods?.includes(LaunchMods.OCULUS)){ res.oculusMode = "true"; }
|
||||
if(launchOptions.launchMods?.includes(LaunchMods.FPFC)){ res.desktopMode = "true"; }
|
||||
if(launchOptions.launchMods?.includes(LaunchMods.DEBUG)){ res.debug = "true"; }
|
||||
if(launchOptions.additionalArgs){ res.additionalArgs = launchOptions.additionalArgs; }
|
||||
if(launchOptions.launchMods?.includes(LaunchMods.SKIP_STEAM)){ res.skipSteam = "true"; }
|
||||
if(launchOptions.launchMods?.includes(LaunchMods.PROTON_LOGS)){ res.protonLogs = "true"; }
|
||||
|
||||
return res;
|
||||
}
|
||||
@@ -239,6 +253,8 @@ type ShortcutParams = {
|
||||
desktopMode?: string;
|
||||
debug?: string;
|
||||
additionalArgs?: string[];
|
||||
skipSteam?: string;
|
||||
protonLogs?: string;
|
||||
version: string;
|
||||
versionName?: string;
|
||||
versionIno?: string;
|
||||
|
||||
@@ -1,20 +1,15 @@
|
||||
import { Observable, ReplaySubject, catchError, lastValueFrom, of, take, timeout } from "rxjs";
|
||||
import { Observable, ReplaySubject } from "rxjs";
|
||||
import { StoreLauncherInterface } from "./store-launcher.interface";
|
||||
import { BSLaunchError, BSLaunchEvent, BSLaunchEventData, LaunchOption } from "../../../shared/models/bs-launch";
|
||||
import { OculusService } from "../oculus.service";
|
||||
import { BS_EXECUTABLE, OCULUS_BS_BACKUP_DIR, OCULUS_BS_DIR } from "../../constants";
|
||||
import { BS_EXECUTABLE } from "../../constants";
|
||||
import path from "path";
|
||||
import log from "electron-log";
|
||||
import { sToMs } from "../../../shared/helpers/time.helpers";
|
||||
import { lstat, pathExists, readdir, readlink, rename, symlink, unlink } from "fs-extra";
|
||||
import { pathExists } from "fs-extra";
|
||||
import { AbstractLauncherService } from "./abstract-launcher.service";
|
||||
import { isProcessRunning } from "../../helpers/os.helpers";
|
||||
import { CustomError } from "../../../shared/models/exceptions/custom-error.class";
|
||||
import { InstallationLocationService } from "../installation-location.service";
|
||||
import { ensurePathNotAlreadyExist } from "../../helpers/fs.helpers";
|
||||
import { UtilsService } from "../utils.service";
|
||||
import { spawn } from "child_process";
|
||||
import { tryit } from "../../../shared/helpers/error.helpers";
|
||||
|
||||
export class OculusLauncherService extends AbstractLauncherService implements StoreLauncherInterface {
|
||||
|
||||
@@ -28,7 +23,6 @@ export class OculusLauncherService extends AbstractLauncherService implements St
|
||||
}
|
||||
|
||||
private readonly oculus: OculusService;
|
||||
private readonly pathsService: InstallationLocationService;
|
||||
private readonly util: UtilsService;
|
||||
|
||||
private readonly oculusLib$ = new ReplaySubject<string>();
|
||||
@@ -36,120 +30,11 @@ export class OculusLauncherService extends AbstractLauncherService implements St
|
||||
private constructor() {
|
||||
super();
|
||||
this.oculus = OculusService.getInstance();
|
||||
this.pathsService = InstallationLocationService.getInstance();
|
||||
this.util = UtilsService.getInstance();
|
||||
|
||||
this.oculus.tryGetGameFolder([OCULUS_BS_DIR, OCULUS_BS_BACKUP_DIR]).then(async dirPath => {
|
||||
if(dirPath){
|
||||
return this.oculusLib$.next( path.join(dirPath, "..") );
|
||||
}
|
||||
const defaultLib = ((await this.oculus.getOculusLibs()) || []).find(lib => lib.isDefault);
|
||||
if(defaultLib?.path){ return this.oculusLib$.next(path.join(defaultLib.path, "Software")); }
|
||||
this.oculusLib$.next(null);
|
||||
}).catch(err => {
|
||||
log.error("Error while getting Oculus libs", err);
|
||||
this.oculusLib$.next(null);
|
||||
});
|
||||
}
|
||||
|
||||
public async deleteBsSymlinks(): Promise<void> {
|
||||
const oculusLibPath = await lastValueFrom(this.oculusLib$.pipe(take(1), timeout(sToMs(30)), catchError(() => of(null))));
|
||||
|
||||
if(!oculusLibPath || !(await pathExists(oculusLibPath))){
|
||||
throw new Error("Oculus library not found, deleteBsSymlinks");
|
||||
}
|
||||
|
||||
const libContents = await readdir(oculusLibPath);
|
||||
const symlinks = (await Promise.all(libContents.map(async dir => {
|
||||
return (await lstat(path.join(oculusLibPath, dir))).isSymbolicLink() ? dir : null;
|
||||
}))).filter(Boolean);
|
||||
|
||||
log.info("Symlinks found in Oculus library", symlinks);
|
||||
|
||||
const bsSymlinks = symlinks.filter(dirent => dirent.startsWith(OCULUS_BS_DIR));
|
||||
|
||||
const bsmSymlinks = (await Promise.all(bsSymlinks.map(async symlink => {
|
||||
const symlinkPath = path.join(oculusLibPath, symlink);
|
||||
const targetPath = await readlink(symlinkPath).catch(err => log.error(err));
|
||||
|
||||
log.info("Oculus Symlink", symlink, "target", targetPath);
|
||||
|
||||
if(!targetPath){ return null; }
|
||||
|
||||
const bsmVersionsDir = path.join(this.pathsService.INSTALLATION_FOLDER, this.pathsService.VERSIONS_FOLDER);
|
||||
|
||||
if(!targetPath.includes(bsmVersionsDir)){ return null; }
|
||||
|
||||
return symlink;
|
||||
}))).filter(Boolean);
|
||||
|
||||
await Promise.all(bsmSymlinks.map(symlink => {
|
||||
log.info("Delete symlink", symlink);
|
||||
return unlink(path.join(oculusLibPath, symlink));
|
||||
}));
|
||||
}
|
||||
|
||||
private async backupOriginalBeatSaber(): Promise<void>{
|
||||
const bsFolder = await this.oculus.getGameFolder(OCULUS_BS_DIR);
|
||||
if(!bsFolder){ return; }
|
||||
const backupPath = await ensurePathNotAlreadyExist(path.join(bsFolder, "..", OCULUS_BS_BACKUP_DIR));
|
||||
log.info("Backing up original Beat Saber", bsFolder, backupPath);
|
||||
return rename(bsFolder, backupPath);
|
||||
}
|
||||
|
||||
public async restoreOriginalBeatSaber(): Promise<void>{
|
||||
const bsFolderBackupPath = await this.oculus.getGameFolder(OCULUS_BS_BACKUP_DIR);
|
||||
if(!(await pathExists(bsFolderBackupPath))){ return; }
|
||||
const originalPath = path.join(bsFolderBackupPath, "..", OCULUS_BS_DIR);
|
||||
log.info("Restoring original Beat Saber", bsFolderBackupPath, originalPath);
|
||||
return rename(bsFolderBackupPath, originalPath);
|
||||
}
|
||||
|
||||
private launchSymlinkCleaner(pid: number, symlinkPath: string){
|
||||
|
||||
log.info("Launch Symlink Cleaner", pid, symlinkPath);
|
||||
|
||||
const exeName = "oculus_symlink_cleaner.exe";
|
||||
const scriptPath = this.util.getAssetsScriptsPath();
|
||||
spawn(path.join(scriptPath, exeName), [`${pid}`, symlinkPath], { detached: true, stdio: "ignore" });
|
||||
}
|
||||
|
||||
public launch(launchOptions: LaunchOption): Observable<BSLaunchEventData> {
|
||||
|
||||
const prepareOriginalVersion: () => Promise<string> = async () => {
|
||||
await this.restoreOriginalBeatSaber();
|
||||
const bsPath = await this.oculus.getGameFolder(OCULUS_BS_DIR);
|
||||
if(!bsPath){
|
||||
throw new Error("Oculus Beat Saber path not found");
|
||||
}
|
||||
return bsPath;
|
||||
}
|
||||
|
||||
const prepareDowngradedVersion: () => Promise<string> = async () => {
|
||||
|
||||
const originalVersionPath = await prepareOriginalVersion().catch(() => null);
|
||||
if (!originalVersionPath) {
|
||||
throw CustomError.fromError(new Error("Original Oculus Beat Saber not installed"), BSLaunchError.ORIGINAL_OCULUS_NOT_INSTALLED);
|
||||
}
|
||||
|
||||
const oculusLib = await lastValueFrom(this.oculusLib$.pipe(take(1), timeout(sToMs(30)), catchError(() => of(null))));
|
||||
|
||||
if(!oculusLib){
|
||||
throw CustomError.fromError(new Error("No Oculus library found"), BSLaunchError.OCULUS_LIB_NOT_FOUND);
|
||||
}
|
||||
|
||||
// Backup original Beat Saber folder
|
||||
await this.backupOriginalBeatSaber();
|
||||
|
||||
// Create symlink in the oculus library from the BSM BS version
|
||||
const symlinkTarget = await this.localVersions.getInstalledVersionPath(launchOptions.version);
|
||||
const symlinkPath = path.join(oculusLib, OCULUS_BS_DIR);
|
||||
log.info("Creating symlink", symlinkTarget, symlinkPath);
|
||||
await symlink(symlinkTarget, symlinkPath, "junction");
|
||||
|
||||
return symlinkPath;
|
||||
}
|
||||
|
||||
return new Observable<BSLaunchEventData>(obs => {
|
||||
(async () => {
|
||||
|
||||
@@ -159,11 +44,7 @@ export class OculusLauncherService extends AbstractLauncherService implements St
|
||||
throw CustomError.fromError(new Error("Cannot start two instance of Beat Saber for Oculus"), BSLaunchError.BS_ALREADY_RUNNING);
|
||||
}
|
||||
|
||||
// Remove previously symlinks created by BSM
|
||||
await this.deleteBsSymlinks().catch(err => log.error("Error while deleting BSM symlinks", err));
|
||||
|
||||
const bsPath = await (launchOptions.version.oculus ? prepareOriginalVersion() : prepareDowngradedVersion());
|
||||
|
||||
const bsPath = await this.localVersions.getInstalledVersionPath(launchOptions.version);
|
||||
const exePath = path.join(bsPath, BS_EXECUTABLE);
|
||||
|
||||
if(!(await pathExists(exePath))){
|
||||
@@ -178,13 +59,6 @@ export class OculusLauncherService extends AbstractLauncherService implements St
|
||||
// Launch Beat Saber
|
||||
const process = this.launchBs(exePath, this.buildBsLaunchArgs(launchOptions));
|
||||
|
||||
if(launchOptions.version.oculus !== true && process?.process?.pid){
|
||||
const { error } = tryit(() => this.launchSymlinkCleaner(process.process.pid, bsPath));
|
||||
if(error){
|
||||
log.error("Error while launching symlink cleaner", error);
|
||||
}
|
||||
}
|
||||
|
||||
return process.exit.catch(err => {
|
||||
throw CustomError.fromError(err, BSLaunchError.BS_EXIT_ERROR);
|
||||
});
|
||||
|
||||
@@ -10,6 +10,7 @@ import { AbstractLauncherService } from "./abstract-launcher.service";
|
||||
import { CustomError } from "../../../shared/models/exceptions/custom-error.class";
|
||||
import { UtilsService } from "../utils.service";
|
||||
import { exec } from "child_process";
|
||||
import { LaunchMods } from "shared/models/bs-launch/launch-option.interface";
|
||||
|
||||
export class SteamLauncherService extends AbstractLauncherService implements StoreLauncherInterface{
|
||||
|
||||
@@ -73,8 +74,11 @@ export class SteamLauncherService extends AbstractLauncherService implements Sto
|
||||
throw CustomError.fromError(new Error(`Path not exist : ${bsExePath}`), BSLaunchError.BS_NOT_FOUND);
|
||||
}
|
||||
|
||||
const skipSteam: boolean = launchOptions.launchMods?.includes(LaunchMods.SKIP_STEAM) ?? false;
|
||||
|
||||
// Open Steam if not running
|
||||
if(!(await this.steam.isSteamRunning())){
|
||||
if(!skipSteam && !(await this.steam.isSteamRunning())){
|
||||
|
||||
obs.next({type: BSLaunchEvent.STEAM_LAUNCHING});
|
||||
|
||||
await this.steam.openSteam().then(() => {
|
||||
@@ -84,9 +88,12 @@ export class SteamLauncherService extends AbstractLauncherService implements Sto
|
||||
obs.next({type: BSLaunchWarning.UNABLE_TO_LAUNCH_STEAM});
|
||||
});
|
||||
}
|
||||
else {
|
||||
obs.next({ type: BSLaunchEvent.SKIPPING_STEAM_LAUNCH});
|
||||
}
|
||||
|
||||
// Backup SteamVR when desktop mode is enabled
|
||||
if(launchOptions.desktop){
|
||||
if(launchOptions.launchMods?.includes(LaunchMods.FPFC)){
|
||||
await this.backupSteamVR().catch(() => {
|
||||
return this.restoreSteamVR();
|
||||
});
|
||||
@@ -106,7 +113,7 @@ export class SteamLauncherService extends AbstractLauncherService implements Sto
|
||||
|
||||
// Linux setup
|
||||
if (process.platform === "linux") {
|
||||
this.linux.setupLaunch(launchOptions, steamPath, bsFolderPath, env);
|
||||
await this.linux.setupLaunch(launchOptions, steamPath, bsFolderPath, env);
|
||||
}
|
||||
|
||||
obs.next({type: BSLaunchEvent.BS_LAUNCHING});
|
||||
|
||||
@@ -18,6 +18,7 @@ import { Observable, Subject, catchError, finalize, from, map, switchMap, throwE
|
||||
import { BsStore } from "../../shared/models/bs-store.enum";
|
||||
import { CustomError } from "../../shared/models/exceptions/custom-error.class";
|
||||
import crypto from "crypto";
|
||||
import { StaticConfigurationService } from "./static-configuration.service";
|
||||
|
||||
|
||||
export class BSLocalVersionService {
|
||||
@@ -32,6 +33,7 @@ export class BSLocalVersionService {
|
||||
private readonly remoteVersionService: BSVersionLibService;
|
||||
private readonly configService: ConfigurationService;
|
||||
private readonly linker: FolderLinkerService;
|
||||
private readonly staticConfig: StaticConfigurationService;
|
||||
private readonly _loadedVersions$: Subject<BSVersion[]>;
|
||||
|
||||
public static getInstance(): BSLocalVersionService {
|
||||
@@ -48,6 +50,7 @@ export class BSLocalVersionService {
|
||||
this.remoteVersionService = BSVersionLibService.getInstance();
|
||||
this.configService = ConfigurationService.getInstance();
|
||||
this.linker = FolderLinkerService.getInstance();
|
||||
this.staticConfig = StaticConfigurationService.getInstance();
|
||||
|
||||
this._loadedVersions$ = new Subject<BSVersion[]>();
|
||||
}
|
||||
@@ -104,6 +107,7 @@ export class BSLocalVersionService {
|
||||
|
||||
const versionFilePath = path.join(bsPath, 'Beat Saber_Data', 'globalgamemanagers');
|
||||
const folderVersion = await this.getVersionFromGlobalGameManagerFile(versionFilePath);
|
||||
folderVersion.path = bsPath;
|
||||
|
||||
if(!folderVersion){ return null; }
|
||||
|
||||
@@ -170,6 +174,24 @@ export class BSLocalVersionService {
|
||||
this.setCustomVersions([...this.getCustomVersions() ?? [], version]);
|
||||
}
|
||||
|
||||
private updateLastVersionLaunched(version: BSVersion, editedVersion: BSVersion): void {
|
||||
const lastVersion = this.staticConfig.get("last-version-launched");
|
||||
if (!lastVersion) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
version.BSVersion !== lastVersion.BSVersion
|
||||
|| version.name !== lastVersion.name
|
||||
|| version.steam !== lastVersion.steam
|
||||
|| version.oculus !== lastVersion.oculus
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.staticConfig.set("last-version-launched", editedVersion);
|
||||
}
|
||||
|
||||
private getCustomVersions(): BSVersion[]{
|
||||
return this.configService.get<BSVersion[]>(this.CUSTOM_VERSIONS_KEY) || [];
|
||||
}
|
||||
@@ -316,6 +338,7 @@ export class BSLocalVersionService {
|
||||
if(oldPath === newPath){
|
||||
this.deleteCustomVersion(version);
|
||||
this.addCustomVersion(editedVersion);
|
||||
this.updateLastVersionLaunched(version, editedVersion);
|
||||
return editedVersion;
|
||||
}
|
||||
|
||||
@@ -326,6 +349,7 @@ export class BSLocalVersionService {
|
||||
return rename(oldPath, newPath).then(() => {
|
||||
this.deleteCustomVersion(version);
|
||||
this.addCustomVersion(editedVersion);
|
||||
this.updateLastVersionLaunched(version, editedVersion);
|
||||
return editedVersion;
|
||||
}).catch((err: Error) => {
|
||||
log.error("edit version error", err, version, name, color);
|
||||
|
||||
@@ -8,6 +8,12 @@ import path from "path";
|
||||
import { DownloadInfo } from "./bs-steam-downloader.service";
|
||||
import { BsStore } from "../../../shared/models/bs-store.enum";
|
||||
import { isOculusTokenValid } from "../../../shared/helpers/oculus.helpers";
|
||||
import { CustomError } from "shared/models/exceptions/custom-error.class";
|
||||
import { minToMs } from "shared/helpers/time.helpers";
|
||||
import { session } from "electron";
|
||||
import { WindowManagerService } from "../window-manager.service";
|
||||
import { addFilterStringLog } from "../../main";
|
||||
import { MetaAuthErrorCodes } from "shared/models/bs-version-download/oculus-download.model";
|
||||
|
||||
export class BsOculusDownloaderService {
|
||||
|
||||
@@ -23,13 +29,63 @@ export class BsOculusDownloaderService {
|
||||
|
||||
private readonly oculusDownloader: OculusDownloader;
|
||||
private readonly versions: BSLocalVersionService;
|
||||
private readonly windows: WindowManagerService;
|
||||
|
||||
private constructor() {
|
||||
this.versions = BSLocalVersionService.getInstance();
|
||||
|
||||
this.windows = WindowManagerService.getInstance();
|
||||
this.oculusDownloader = new OculusDownloader();
|
||||
}
|
||||
|
||||
public async getUserTokenFromMetaAuth(keepToken: boolean): Promise<string>{
|
||||
|
||||
const loginUrl = "https://secure.oculus.com";
|
||||
const redirectUrl = `${loginUrl}/my/profile`;
|
||||
const window = await this.windows.openWindow(loginUrl, { frame: true, width: 650, height: 800 });
|
||||
|
||||
let timout: NodeJS.Timeout;
|
||||
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
timout = setTimeout(() => {
|
||||
reject(new CustomError("Trying to get Oculus user token timed out", MetaAuthErrorCodes.META_LOGIN_TIMED_OUT));
|
||||
window.close();
|
||||
}, minToMs(10));
|
||||
|
||||
const tryExtractToken = async () => {
|
||||
log.info("Meta auth window navigated to", new URL(window.webContents.getURL()).origin + new URL(window.webContents.getURL()).pathname); // Dot not log full url for privacy reasons
|
||||
if(!window.webContents.getURL()?.startsWith(redirectUrl)){ return; }
|
||||
|
||||
const token = (await window.webContents.session.cookies.get({ name: "oc_ac_at" })).at(0)?.value;
|
||||
|
||||
if(!isOculusTokenValid(token, log.info)){
|
||||
return;
|
||||
}
|
||||
|
||||
addFilterStringLog(token);
|
||||
resolve(token);
|
||||
}
|
||||
|
||||
window.webContents.on("did-stop-loading", tryExtractToken);
|
||||
window.webContents.on("did-navigate", tryExtractToken);
|
||||
window.webContents.on("did-navigate-in-page", tryExtractToken);
|
||||
|
||||
window.on("closed", () => {
|
||||
reject(new CustomError("Oculus login window closed by user", MetaAuthErrorCodes.META_LOGIN_WINDOW_CLOSED_BY_USER));
|
||||
});
|
||||
}).finally(() => {
|
||||
|
||||
clearTimeout(timout);
|
||||
|
||||
if(!keepToken){
|
||||
this.clearAuthToken();
|
||||
}
|
||||
|
||||
if(!window.isDestroyed() && window.isClosable()){
|
||||
window.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async createDownloadVersion(version: BSVersion): Promise<{version: BSVersion, dest: string}>{
|
||||
const dest = await ensurePathNotAlreadyExist(await this.versions.getVersionPath(version));
|
||||
return {
|
||||
@@ -46,7 +102,7 @@ export class BsOculusDownloaderService {
|
||||
this.oculusDownloader.stopDownload();
|
||||
}
|
||||
|
||||
public downloadVersion(downloadInfo: DownloadInfo): Observable<Progression<BSVersion>>{
|
||||
public downloadVersion(downloadInfo: OculusDownloadInfo): Observable<Progression<BSVersion>>{
|
||||
|
||||
let downloadVersion: BSVersion
|
||||
|
||||
@@ -69,9 +125,16 @@ export class BsOculusDownloaderService {
|
||||
);
|
||||
}
|
||||
|
||||
public clearAuthToken(): Promise<void>{
|
||||
return session.defaultSession.clearStorageData({ storages: ["cookies"], origin: ".oculus.com" })
|
||||
}
|
||||
|
||||
public metaSessionExists(): Promise<boolean>{
|
||||
return session.defaultSession.cookies.get({ domain: ".oculus.com" }).then(cookies => cookies.length > 0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export interface OculusDownloadInfo {
|
||||
version: BSVersion;
|
||||
stay?: boolean;
|
||||
export interface OculusDownloadInfo extends DownloadInfo {
|
||||
token: string;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { BS_APP_ID, BS_DEPOT } from "../../constants";
|
||||
import path from "path";
|
||||
import { BSVersion } from "shared/bs-version.interface";
|
||||
import { UtilsService } from "../utils.service";
|
||||
import log from "electron-log";
|
||||
import { InstallationLocationService } from "../installation-location.service";
|
||||
import { BSLocalVersionService } from "../bs-local-version.service";
|
||||
@@ -17,14 +16,12 @@ import { CustomError } from "shared/models/exceptions/custom-error.class";
|
||||
export class BsSteamDownloaderService {
|
||||
private static instance: BsSteamDownloaderService;
|
||||
|
||||
private readonly utils: UtilsService;
|
||||
private readonly installLocationService: InstallationLocationService;
|
||||
private readonly localVersionService: BSLocalVersionService;
|
||||
|
||||
private depotDownloader: DepotDownloader;
|
||||
|
||||
private constructor() {
|
||||
this.utils = UtilsService.getInstance();
|
||||
this.installLocationService = InstallationLocationService.getInstance();
|
||||
this.localVersionService = BSLocalVersionService.getInstance();
|
||||
|
||||
@@ -40,10 +37,6 @@ export class BsSteamDownloaderService {
|
||||
return BsSteamDownloaderService.instance;
|
||||
}
|
||||
|
||||
private getDepotDownloaderExePath(): string {
|
||||
return path.join(this.utils.getAssetsScriptsPath(), process.platform === 'linux' ? "DepotDownloader" : "DepotDownloader.exe");
|
||||
}
|
||||
|
||||
private async buildDepotDownloaderInstance(downloadInfos: DownloadSteamInfo, qr?: boolean): Promise<{depotDownloader: DepotDownloader, depotDownloaderOptions: DepotDownloaderArgsOptions, version: BSVersion}> {
|
||||
|
||||
const versionPath = await this.localVersionService.getVersionPath(downloadInfos.bsVersion);
|
||||
@@ -68,11 +61,9 @@ export class BsSteamDownloaderService {
|
||||
|
||||
await ensureDir(this.installLocationService.versionsDirectory());
|
||||
|
||||
const exePath = this.getDepotDownloaderExePath();
|
||||
const args = DepotDownloader.buildArgs(depotDownloaderOptions);
|
||||
|
||||
const depotDownloader = new DepotDownloader({
|
||||
command: exePath,
|
||||
args,
|
||||
options: { cwd: this.installLocationService.versionsDirectory() },
|
||||
echoStartData: downloadVersion
|
||||
@@ -104,11 +95,21 @@ export class BsSteamDownloaderService {
|
||||
finalize(() => this.localVersionService.initVersionMetadata(version, { store: BsStore.STEAM }))
|
||||
).subscribe(sub);
|
||||
|
||||
}).catch(err => sub.error({
|
||||
type: DepotDownloaderEventType.Error,
|
||||
subType: DepotDownloaderErrorEvent.Unknown,
|
||||
data: err
|
||||
} as DepotDownloaderEvent));
|
||||
}).catch(err => {
|
||||
if (err instanceof CustomError
|
||||
&& Object.values(DepotDownloaderErrorEvent).includes(
|
||||
err.code as DepotDownloaderErrorEvent
|
||||
)
|
||||
) {
|
||||
return sub.error(err);
|
||||
}
|
||||
|
||||
return sub.error({
|
||||
type: DepotDownloaderEventType.Error,
|
||||
subType: DepotDownloaderErrorEvent.Unknown,
|
||||
data: err
|
||||
} as DepotDownloaderEvent)
|
||||
});
|
||||
|
||||
return () => {
|
||||
depotDownloaderBuildPromise.then(({ depotDownloader }) => depotDownloader.stop());
|
||||
@@ -146,7 +147,6 @@ export interface DownloadInfo {
|
||||
bsVersion: BSVersion;
|
||||
isVerification?: boolean;
|
||||
stay?: boolean;
|
||||
token?: string;
|
||||
}
|
||||
|
||||
export interface DownloadSteamInfo extends DownloadInfo {
|
||||
|
||||
@@ -3,10 +3,11 @@ import path from "path";
|
||||
import { writeFileSync } from "fs";
|
||||
import { BSVersion } from "shared/bs-version.interface";
|
||||
import { RequestService } from "./request.service";
|
||||
import { pathExistsSync, readJSON } from "fs-extra";
|
||||
import { readJSON } from "fs-extra";
|
||||
import { allSettled } from "../../shared/helpers/promise.helpers";
|
||||
import { LinuxService } from "./linux.service";
|
||||
import { IS_FLATPAK } from "main/constants";
|
||||
import { StaticConfigurationService } from "./static-configuration.service";
|
||||
|
||||
export class BSVersionLibService {
|
||||
private readonly REMOTE_BS_VERSIONS_URL: string = "https://raw.githubusercontent.com/Zagrios/bs-manager/master/assets/jsons/bs-versions.json";
|
||||
@@ -14,9 +15,10 @@ export class BSVersionLibService {
|
||||
|
||||
private static instance: BSVersionLibService;
|
||||
|
||||
private linuxService: LinuxService;
|
||||
private utilsService: UtilsService;
|
||||
private requestService: RequestService;
|
||||
private readonly linuxService: LinuxService;
|
||||
private readonly utilsService: UtilsService;
|
||||
private readonly requestService: RequestService;
|
||||
private readonly configService: StaticConfigurationService;
|
||||
|
||||
private bsVersions: BSVersion[];
|
||||
|
||||
@@ -24,6 +26,7 @@ export class BSVersionLibService {
|
||||
this.linuxService = LinuxService.getInstance();
|
||||
this.utilsService = UtilsService.getInstance();
|
||||
this.requestService = RequestService.getInstance();
|
||||
this.configService = StaticConfigurationService.getInstance();
|
||||
}
|
||||
|
||||
public static getInstance(): BSVersionLibService {
|
||||
@@ -37,25 +40,32 @@ export class BSVersionLibService {
|
||||
return this.requestService.getJSON<BSVersion[]>(this.REMOTE_BS_VERSIONS_URL).then(res => res.data);
|
||||
}
|
||||
|
||||
private async shouldLoadFromConfig(): Promise<boolean> {
|
||||
// Some special cases of readonly memory installations
|
||||
return process.platform === "linux" && (IS_FLATPAK || this.linuxService.isNixOS());
|
||||
}
|
||||
|
||||
private async getLocalVersions(): Promise<BSVersion[]> {
|
||||
if (IS_FLATPAK) {
|
||||
const flatpakVersionsPath = path.join(this.linuxService.getFlatpakLocalVersionFolder(), this.VERSIONS_FILE);
|
||||
if (pathExistsSync(flatpakVersionsPath)) {
|
||||
return readJSON(flatpakVersionsPath);
|
||||
}
|
||||
const localVersionsPath = path.join(this.utilsService.getAssestsJsonsPath(), this.VERSIONS_FILE);
|
||||
|
||||
if (!(await this.shouldLoadFromConfig())) {
|
||||
return readJSON(localVersionsPath);
|
||||
}
|
||||
|
||||
const localVersionsPath = path.join(this.utilsService.getAssestsJsonsPath(), this.VERSIONS_FILE);
|
||||
return readJSON(localVersionsPath);
|
||||
let versions = this.configService.get("versions");
|
||||
if (!versions) {
|
||||
versions = await readJSON(localVersionsPath)
|
||||
}
|
||||
return versions;
|
||||
}
|
||||
|
||||
private async updateLocalVersions(versions: BSVersion[]): Promise<void> {
|
||||
const localVersionsPath = path.join(
|
||||
IS_FLATPAK
|
||||
? this.linuxService.getFlatpakLocalVersionFolder()
|
||||
: this.utilsService.getAssestsJsonsPath(),
|
||||
this.VERSIONS_FILE
|
||||
);
|
||||
if (await this.shouldLoadFromConfig()) {
|
||||
this.configService.set("versions", versions);
|
||||
return;
|
||||
}
|
||||
|
||||
const localVersionsPath = path.join(this.utilsService.getAssestsJsonsPath(), this.VERSIONS_FILE);
|
||||
writeFileSync(localVersionsPath, JSON.stringify(versions, null, "\t"), { encoding: "utf-8", flag: "w" });
|
||||
}
|
||||
|
||||
|
||||
@@ -21,27 +21,32 @@ export class FolderLinkerService {
|
||||
private readonly installLocationService = InstallationLocationService.getInstance();
|
||||
private readonly staticConfig: StaticConfigurationService;
|
||||
|
||||
private linkingType: "junction" | "symlink" = "junction";
|
||||
// Only Windows support "junction", this is disregarded in other os'es
|
||||
private linkingType: "junction" | "symlink" =
|
||||
process.platform === "win32" ? "junction" : "symlink";
|
||||
|
||||
private constructor() {
|
||||
this.installLocationService = InstallationLocationService.getInstance();
|
||||
this.staticConfig = StaticConfigurationService.getInstance();
|
||||
|
||||
this.linkingType = this.staticConfig.get("use-symlinks") === true ? "symlink" : "junction";
|
||||
log.info(`Linking type is set to ${this.linkingType}`);
|
||||
if (process.platform === "win32") {
|
||||
// Only Windows support "junction", this is disregarded in other os'es
|
||||
this.linkingType = this.staticConfig.get("use-symlinks") === true ? "symlink" : "junction";
|
||||
log.info(`Linking type is set to ${this.linkingType}`);
|
||||
|
||||
this.staticConfig.$watch("use-symlinks").subscribe((useSymlink) => {
|
||||
this.linkingType = useSymlink === true ? "symlink" : "junction";
|
||||
log.info(`Linking type set to ${this.linkingType}`);
|
||||
});
|
||||
this.staticConfig.$watch("use-symlinks").subscribe((useSymlink) => {
|
||||
this.linkingType = useSymlink === true ? "symlink" : "junction";
|
||||
log.info(`Linking type set to ${this.linkingType}`);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async sharedFolder(): Promise<string> {
|
||||
public sharedFolder(): string {
|
||||
return this.installLocationService.sharedContentPath();
|
||||
}
|
||||
|
||||
private async getSharedFolder(folderPath: string, intermediateFolder?: string): Promise<string> {
|
||||
return path.join(await this.sharedFolder(), intermediateFolder ?? "", path.basename(folderPath));
|
||||
return path.join(this.sharedFolder(), intermediateFolder ?? "", path.basename(folderPath));
|
||||
}
|
||||
|
||||
private getBackupFolder(folderPath: string): string {
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import fs from "fs-extra";
|
||||
import log from "electron-log";
|
||||
import path from "path";
|
||||
import { BS_APP_ID, PROTON_BINARY_PREFIX, WINE_BINARY_PREFIX } from "main/constants";
|
||||
import { BS_APP_ID, IS_FLATPAK, PROTON_BINARY_PREFIX, WINE_BINARY_PREFIX } from "main/constants";
|
||||
import { StaticConfigurationService } from "./static-configuration.service";
|
||||
import { CustomError } from "shared/models/exceptions/custom-error.class";
|
||||
import { BSLaunchError, LaunchOption } from "shared/models/bs-launch";
|
||||
import { app } from "electron";
|
||||
import config from "../../../electron-builder.config";
|
||||
import { bsmExec } from "main/helpers/os.helpers";
|
||||
import { LaunchMods } from "shared/models/bs-launch/launch-option.interface";
|
||||
|
||||
export class LinuxService {
|
||||
private static instance: LinuxService;
|
||||
@@ -19,7 +19,9 @@ export class LinuxService {
|
||||
}
|
||||
|
||||
private readonly staticConfig: StaticConfigurationService;
|
||||
private protonCommand = "";
|
||||
private protonPrefix = "";
|
||||
|
||||
private nixOS: boolean | undefined;
|
||||
|
||||
private constructor() {
|
||||
this.staticConfig = StaticConfigurationService.getInstance();
|
||||
@@ -27,7 +29,7 @@ export class LinuxService {
|
||||
|
||||
// === Launching === //
|
||||
|
||||
public setupLaunch(
|
||||
public async setupLaunch(
|
||||
launchOptions: LaunchOption,
|
||||
steamPath: string,
|
||||
bsFolderPath: string,
|
||||
@@ -64,7 +66,10 @@ export class LinuxService {
|
||||
BSLaunchError.PROTON_NOT_FOUND
|
||||
);
|
||||
}
|
||||
this.protonCommand = `"${protonPath}" run`;
|
||||
|
||||
this.protonPrefix = await this.isNixOS()
|
||||
? `steam-run "${protonPath}" run`
|
||||
: `"${protonPath}" run`;
|
||||
|
||||
// Setup Proton environment variables
|
||||
Object.assign(env, {
|
||||
@@ -74,11 +79,15 @@ export class LinuxService {
|
||||
"STEAM_COMPAT_CLIENT_INSTALL_PATH": steamPath,
|
||||
"STEAM_COMPAT_APP_ID": BS_APP_ID,
|
||||
// Run game in steam environment; fixes #585 for unicode song titles
|
||||
"SteamEnv": "1",
|
||||
// Uncomment these to create a proton log file in the Beat Saber install directory.
|
||||
// "PROTON_LOG": 1,
|
||||
// "PROTON_LOG_DIR": bsFolderPath,
|
||||
"SteamEnv": 1,
|
||||
});
|
||||
|
||||
if (launchOptions.launchMods?.includes(LaunchMods.PROTON_LOGS)) {
|
||||
Object.assign(env, {
|
||||
"PROTON_LOG": 1,
|
||||
"PROTON_LOG_DIR": path.join(bsFolderPath, "Logs"),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public verifyProtonPath(protonFolder: string = ""): boolean {
|
||||
@@ -111,19 +120,29 @@ export class LinuxService {
|
||||
return winePath;
|
||||
}
|
||||
|
||||
public getProtonCommand(): string {
|
||||
public getProtonPrefix(): string {
|
||||
// Set in setupLaunch
|
||||
return this.protonCommand;
|
||||
return this.protonPrefix;
|
||||
}
|
||||
|
||||
// === Flatpak Specific === //
|
||||
// === NixOS Specific === //
|
||||
|
||||
public getFlatpakLocalVersionFolder(): string {
|
||||
return path.join(
|
||||
app.getPath("home"),
|
||||
".var", "app", config.appId,
|
||||
"resources", "assets", "jsons"
|
||||
);
|
||||
public async isNixOS(): Promise<boolean> {
|
||||
if (this.nixOS !== undefined) {
|
||||
return this.nixOS;
|
||||
}
|
||||
|
||||
try {
|
||||
await bsmExec("nixos-version", {
|
||||
log: true,
|
||||
flatpak: { host: IS_FLATPAK },
|
||||
});
|
||||
this.nixOS = true;
|
||||
} catch (error) {
|
||||
log.info("Not NixOS", error);
|
||||
this.nixOS = false;
|
||||
}
|
||||
|
||||
return this.nixOS;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import { deleteFolder, pathExist, Progression, unlinkPath } from "../../helpers/
|
||||
import { lastValueFrom, Observable } from "rxjs";
|
||||
import recursiveReadDir from "recursive-readdir";
|
||||
import { sToMs } from "../../../shared/helpers/time.helpers";
|
||||
import { pathExistsSync } from "fs-extra";
|
||||
import { copyFile, ensureDir, pathExistsSync } from "fs-extra";
|
||||
import { CustomError } from "shared/models/exceptions/custom-error.class";
|
||||
import { popElement } from "shared/helpers/array.helpers";
|
||||
import { LinuxService } from "../linux.service";
|
||||
@@ -19,6 +19,7 @@ import { tryit } from "shared/helpers/error.helpers";
|
||||
import crypto from "crypto";
|
||||
import { BsmZipExtractor } from "main/models/bsm-zip-extractor.class";
|
||||
import { bsmSpawn } from "main/helpers/os.helpers";
|
||||
import { ExternalMod } from "shared/models/mods/mod.interface";
|
||||
|
||||
export class BsModsManagerService {
|
||||
private static instance: BsModsManagerService;
|
||||
@@ -320,6 +321,108 @@ export class BsModsManagerService {
|
||||
return Array.from(modsDict.values());
|
||||
}
|
||||
|
||||
private async importMod(modPath: string, destination: string): Promise<string[]> {
|
||||
const extensionName = path.extname(modPath).toLowerCase();
|
||||
|
||||
if (extensionName === ".dll") {
|
||||
const filename = path.basename(modPath);
|
||||
// Defaultly copy the ".dll" to the "IPA/Pending/Plugins" folder
|
||||
const fileFolder = path.join(destination, ModsInstallFolder.PLUGINS);
|
||||
|
||||
log.info("Copying", `${modPath}`, "to", `"${fileFolder}"`);
|
||||
await ensureDir(fileFolder);
|
||||
const copied = await copyFile(modPath, path.join(fileFolder, filename))
|
||||
.then(() => true)
|
||||
.catch(error => {
|
||||
log.warn("Could not copy", `"${modPath}"`, error);
|
||||
return false;
|
||||
});
|
||||
return copied ? [ filename ] : [];
|
||||
}
|
||||
|
||||
if (extensionName !== ".zip") {
|
||||
log.warn("Mod file is not a dll or zip file");
|
||||
return [];
|
||||
}
|
||||
|
||||
log.info("Extracting", `"${modPath}"`, "to", `"${destination}"`);
|
||||
const zip = await BsmZipExtractor.fromPath(modPath);
|
||||
|
||||
try {
|
||||
const dll = await zip.findEntry(entry => entry.fileName.endsWith(".dll"));
|
||||
|
||||
if (!dll) {
|
||||
log.warn("No \"dll\" found in zip", modPath);
|
||||
return [];
|
||||
}
|
||||
|
||||
return await zip.extract(destination);
|
||||
} catch (error) {
|
||||
log.warn("Could not extract", `"${modPath}"`, error);
|
||||
return [];
|
||||
} finally {
|
||||
zip.close();
|
||||
}
|
||||
}
|
||||
|
||||
public importMods(paths: string[], version: BSVersion): Observable<Progression<ExternalMod>> {
|
||||
return new Observable<Progression<ExternalMod>>(obs => {
|
||||
const progress: Progression<ExternalMod> = { total: 0, current: 0 };
|
||||
const externalMod: ExternalMod = {
|
||||
name: "",
|
||||
files: [],
|
||||
};
|
||||
let modsInstalledCount = 0;
|
||||
let modFilesCount = 0;
|
||||
const abortController = new AbortController();
|
||||
|
||||
(async () => {
|
||||
const versionPath = await this.bsLocalService.getVersionPath(version);
|
||||
const modsPendingFolder = path.join(versionPath, ModsInstallFolder.PENDING);
|
||||
|
||||
for (const modPath of paths) {
|
||||
if (abortController.signal?.aborted) {
|
||||
log.info("Mods import has been cancelled");
|
||||
return;
|
||||
}
|
||||
|
||||
progress.total = paths.length;
|
||||
obs.next(progress);
|
||||
|
||||
if (!pathExistsSync(modPath)) { continue; }
|
||||
|
||||
const modFiles = await this.importMod(modPath, modsPendingFolder);
|
||||
if (modFiles.length > 0) {
|
||||
++modsInstalledCount;
|
||||
modFilesCount += modFiles.length;
|
||||
|
||||
externalMod.name = path.basename(modPath, path.extname(modPath));
|
||||
externalMod.files = modFiles;
|
||||
progress.data = externalMod;
|
||||
} else {
|
||||
progress.data = undefined;
|
||||
}
|
||||
++progress.current;
|
||||
obs.next(progress);
|
||||
}
|
||||
})()
|
||||
.then(() => {
|
||||
if (modsInstalledCount === 0) {
|
||||
throw new CustomError("No \"dll\" found in any of the files dropped", "no-dlls");
|
||||
}
|
||||
log.info("Successfully imported", modsInstalledCount, "mods from", paths.length, "files paths. Total files/folders added", modFilesCount);
|
||||
})
|
||||
.catch(error => {
|
||||
obs.error(error);
|
||||
})
|
||||
.finally(() => obs.complete());
|
||||
|
||||
return () => {
|
||||
abortController.abort();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public installMods(mods: Mod[], version: BSVersion): Observable<Progression> {
|
||||
const progress = { current: 0, total: mods.length };
|
||||
|
||||
|
||||
@@ -7,14 +7,14 @@ import { shell } from "electron";
|
||||
import { isProcessRunning } from "../helpers/os.helpers";
|
||||
import { sToMs } from "../../shared/helpers/time.helpers";
|
||||
import { execOnOs } from "../helpers/env.helpers";
|
||||
import { UtilsService } from "./utils.service";
|
||||
import { exec } from "child_process";
|
||||
|
||||
const { list } = (execOnOs({ win32: () => require("regedit-rs") }, true) ?? {}) as typeof import("regedit-rs");
|
||||
|
||||
export class OculusService {
|
||||
private static instance: OculusService;
|
||||
|
||||
private oculusLibraries: OculusLibrary[];
|
||||
|
||||
public static getInstance(): OculusService {
|
||||
if (!OculusService.instance) {
|
||||
OculusService.instance = new OculusService();
|
||||
@@ -22,7 +22,14 @@ export class OculusService {
|
||||
return OculusService.instance;
|
||||
}
|
||||
|
||||
private constructor() {}
|
||||
|
||||
private readonly utils: UtilsService;
|
||||
|
||||
private oculusLibraries: OculusLibrary[];
|
||||
|
||||
private constructor() {
|
||||
this.utils = UtilsService.getInstance();
|
||||
}
|
||||
|
||||
public async getOculusLibs(): Promise<OculusLibrary[]> {
|
||||
if (process.platform !== "win32") {
|
||||
@@ -120,6 +127,67 @@ export class OculusService {
|
||||
}, sToMs(30));
|
||||
});
|
||||
}
|
||||
|
||||
public async isSideLoadedAppsEnabled(): Promise<boolean> {
|
||||
if(process.platform !== "win32"){
|
||||
log.info("Cannot check sideloaded apps on non-windows platforms");
|
||||
throw new Error("Cannot check sideloaded apps on non-windows platforms");
|
||||
}
|
||||
|
||||
const regPath = "HKLM\\SOFTWARE\\Wow6432Node\\Oculus VR, LLC\\Oculus";
|
||||
const res = await list(regPath).then(res => res[regPath]);
|
||||
|
||||
if(!res.exists){
|
||||
log.info("Registry key not found", regPath);
|
||||
return false;
|
||||
}
|
||||
|
||||
const value = res.values?.AllowDevSideloaded;
|
||||
|
||||
if(!value){
|
||||
log.info("Registry value not found", "AllowDevSideloaded");
|
||||
return false;
|
||||
}
|
||||
|
||||
return value.value === 1;
|
||||
}
|
||||
|
||||
public async enableSideloadedApps(): Promise<void> {
|
||||
if(process.platform !== "win32"){
|
||||
log.info("Cannot enable sideloaded apps on non-windows platforms");
|
||||
return;
|
||||
}
|
||||
|
||||
const enabled = await this.isSideLoadedAppsEnabled();
|
||||
|
||||
if(enabled){
|
||||
log.info("Sideloaded apps already enabled");
|
||||
return;
|
||||
}
|
||||
|
||||
const exePath = path.join(this.utils.getAssetsScriptsPath(), "oculus-allow-dev-sideloaded.exe");
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
log.info("Enabling sideloaded apps");
|
||||
const process = exec(exePath);
|
||||
process.on("exit", code => {
|
||||
if(code === 0){
|
||||
resolve();
|
||||
} else {
|
||||
reject(new Error(`Failed to enable sideloaded apps, exit code: ${code}`));
|
||||
}
|
||||
});
|
||||
|
||||
process.on("error", err => {
|
||||
log.error("Error while enabling sideloaded apps", err);
|
||||
reject(err);
|
||||
});
|
||||
|
||||
process.stdout.on("data", data => {
|
||||
log.info(data.toString?.() ?? data);
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export interface OculusLibrary {
|
||||
|
||||
@@ -4,6 +4,7 @@ import path from "path";
|
||||
import { PROTON_BINARY_PREFIX, WINE_BINARY_PREFIX } from "main/constants";
|
||||
import { Observable, Subject } from "rxjs";
|
||||
import { CustomError } from "shared/models/exceptions/custom-error.class";
|
||||
import { BSVersion } from "shared/bs-version.interface";
|
||||
|
||||
export class StaticConfigurationService {
|
||||
private static instance: StaticConfigurationService;
|
||||
@@ -87,9 +88,12 @@ export interface StaticConfigKeyValues {
|
||||
"song-details-cache-etag": string;
|
||||
"disable-hadware-acceleration": boolean;
|
||||
"use-symlinks": boolean;
|
||||
"use-system-proxy": boolean;
|
||||
"last-version-launched": BSVersion;
|
||||
|
||||
// Linux Specific static configs
|
||||
"proton-folder": string;
|
||||
"versions": BSVersion[];
|
||||
};
|
||||
|
||||
export type StaticConfigKeys = keyof StaticConfigKeyValues;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { BsvMapDetail } from "shared/models/maps";
|
||||
import { BsvPlaylistPage, MapFilter, PlaylistSearchParams, PlaylistSearchResponse, SearchParams, SearchResponse } from "shared/models/maps/beat-saver.model";
|
||||
import { BsvPlaylistPage, MapFilter, MapLeaderboard, PlaylistSearchParams, PlaylistSearchResponse, SearchParams, SearchResponse } from "shared/models/maps/beat-saver.model";
|
||||
import { RequestService } from "../../request.service";
|
||||
import { CustomError } from "shared/models/exceptions/custom-error.class";
|
||||
|
||||
@@ -32,6 +32,10 @@ export class BeatSaverApiService {
|
||||
return new URLSearchParams();
|
||||
}
|
||||
|
||||
if (!filter.leaderboard) {
|
||||
filter.leaderboard = MapLeaderboard.All;
|
||||
}
|
||||
|
||||
const enbledTagsString = filter.enabledTags ? Array.from(filter.enabledTags) : null;
|
||||
const excludedTagsString = filter.excludedTags ? Array.from(filter.excludedTags).map(tag => `!${tag}`) : null;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { BsvMapDetail, MapFilter, MapRequirement, MapSpecificity, MapStyle, MapTag, MapType } from "shared/models/maps/beat-saver.model";
|
||||
import { BsvMapDetail, MapFilter, MapLeaderboard, MapRequirement, MapSpecificity, MapStyle, MapTag, MapType } from "shared/models/maps/beat-saver.model";
|
||||
import { motion } from "framer-motion";
|
||||
import { MutableRefObject, useEffect, useRef, useState } from "react";
|
||||
import { BsmCheckbox } from "../../shared/bsm-checkbox.component";
|
||||
@@ -14,6 +14,8 @@ import { BsmLocalMap } from "shared/models/maps/bsm-local-map.interface";
|
||||
import { SongDetails } from "shared/models/maps";
|
||||
import formatDuration from "format-duration";
|
||||
import { MapInfo } from "shared/models/maps/info/map-info.model";
|
||||
import { BsmSelect, BsmSelectOption } from "renderer/components/shared/bsm-select.component";
|
||||
import { useConstant } from "renderer/hooks/use-constant.hook";
|
||||
|
||||
export type Props = {
|
||||
className?: string;
|
||||
@@ -45,6 +47,13 @@ export function FilterPanel({ className, ref, playlist = false, filter, localDat
|
||||
const isTagActivated = (tag: MapTag): boolean => filter?.enabledTags?.has(tag) || filter?.excludedTags?.has(tag);
|
||||
const isTagExcluded = (tag: MapTag): boolean => filter?.excludedTags?.has(tag);
|
||||
|
||||
const leaderboardOptions: BsmSelectOption<MapLeaderboard>[] = useConstant(
|
||||
() => Object.values(MapLeaderboard).map(key => ({
|
||||
text: `maps.map-leaderboard.${key}`,
|
||||
value: key,
|
||||
}))
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (firstRun.current) {
|
||||
firstRun.current = false;
|
||||
@@ -137,10 +146,6 @@ export function FilterPanel({ className, ref, playlist = false, filter, localDat
|
||||
return t(`maps.map-styles.${style}`);
|
||||
};
|
||||
|
||||
const translateMapSpecificity = (specificity: MapSpecificity): string => {
|
||||
return t(`maps.map-specificities.${specificity}`);
|
||||
};
|
||||
|
||||
type BooleanKeys<T> = { [k in keyof T]: T[k] extends boolean ? k : never }[keyof T];
|
||||
|
||||
const handleCheckbox = (key: BooleanKeys<MapFilter>) => {
|
||||
@@ -153,6 +158,12 @@ export function FilterPanel({ className, ref, playlist = false, filter, localDat
|
||||
onChange(newFilter);
|
||||
};
|
||||
|
||||
const handleLeaderboardChange = (value: MapLeaderboard) => {
|
||||
const newFilter = { ...(filter ?? {}) };
|
||||
newFilter.leaderboard = value;
|
||||
onChange(newFilter);
|
||||
}
|
||||
|
||||
const handleApply = () => {
|
||||
onApply(filter);
|
||||
setHaveChanged(() => false);
|
||||
@@ -173,9 +184,18 @@ export function FilterPanel({ className, ref, playlist = false, filter, localDat
|
||||
{Object.values(MapSpecificity).map(specificity => (
|
||||
<div key={specificity} className="flex justify-start items-center h-[22px] z-20 relative py-0.5 cursor-pointer" onClick={() => handleCheckbox(specificity)}>
|
||||
<BsmCheckbox className="h-full aspect-square relative bg-inherit mr-1" checked={filter?.[specificity]} onChange={() => handleCheckbox(specificity)} />
|
||||
<span className="grow capitalize">{translateMapSpecificity(specificity)}</span>
|
||||
<span className="grow capitalize">{t(`maps.map-specificities.${specificity}`)}</span>
|
||||
</div>
|
||||
))}
|
||||
|
||||
<h2 className="mb-1 uppercase text-sm">{t("maps.map-filter-panel.leaderboard")}</h2>
|
||||
<BsmSelect
|
||||
className="rounded-md bg-theme-1"
|
||||
options={leaderboardOptions}
|
||||
selected={filter.leaderboard || MapLeaderboard.All}
|
||||
onChange={handleLeaderboardChange}
|
||||
/>
|
||||
|
||||
<h2 className="my-1 uppercase text-sm">{t("maps.map-filter-panel.requirements")}</h2>
|
||||
{Object.values(MapRequirement).map(requirement => (
|
||||
<div key={requirement} className="flex justify-start items-center h-[22px] z-20 relative py-0.5 cursor-pointer" onClick={() => handleCheckbox(requirement)}>
|
||||
@@ -287,10 +307,23 @@ function isFitAutomapper(filter: MapFilter, automapper: boolean): boolean {
|
||||
return automapper;
|
||||
}
|
||||
|
||||
function isFitLeaderboard(filter: MapFilter, ranked: boolean, blRanked: boolean): boolean {
|
||||
switch (filter?.leaderboard) {
|
||||
case MapLeaderboard.All:
|
||||
return true;
|
||||
|
||||
function isFitRanked(filter: MapFilter, ranked: boolean): boolean {
|
||||
if (!filter?.ranked) { return true; }
|
||||
return ranked;
|
||||
case MapLeaderboard.Ranked:
|
||||
return ranked || blRanked;
|
||||
|
||||
case MapLeaderboard.ScoreSaber:
|
||||
return ranked;
|
||||
|
||||
case MapLeaderboard.BeatLeader:
|
||||
return blRanked;
|
||||
|
||||
default: // filter is undefined
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function isFitCurated(filter: MapFilter, curated: boolean): boolean {
|
||||
@@ -322,7 +355,7 @@ export const isLocalMapFitMapFilter = ({filter, map, search}: { filter: MapFilte
|
||||
if (!isFitChroma(filter, map.songDetails?.difficulties.some(diff => !!diff.chroma))) { return false; }
|
||||
if (!isFitFullSpread(filter, map.songDetails?.difficulties.length)) { return false; }
|
||||
if (!isFitAutomapper(filter, map.songDetails?.automapper)){ return false; }
|
||||
if (!isFitRanked(filter, map.songDetails?.ranked || map.songDetails?.blRanked)) { return false; }
|
||||
if (!isFitLeaderboard(filter, map.songDetails?.ranked, map.songDetails?.blRanked)) { return false; }
|
||||
if (!isFitCurated(filter, map.songDetails?.curated)) { return false; }
|
||||
if (!isFitVerified(filter, map.songDetails?.uploader.verified)) { return false; }
|
||||
if (!isFitSearch(search, {songName: map.mapInfo?.songName, songAuthorName: map.mapInfo?.songAuthorName, levelMappers: map.mapInfo?.levelMappers})) { return false; }
|
||||
@@ -343,7 +376,7 @@ export const isBsvMapFitMapFilter = ({filter, map, search}: { filter: MapFilter,
|
||||
if (!isFitChroma(filter, map.versions?.at(0)?.diffs.some(diff => !!diff.chroma))) { return false; }
|
||||
if (!isFitFullSpread(filter, map.versions?.at(0)?.diffs.length)) { return false; }
|
||||
if (!isFitAutomapper(filter, map.automapper)){ return false; }
|
||||
if (!isFitRanked(filter, map.ranked || map.blRanked)) { return false; }
|
||||
if (!isFitLeaderboard(filter, map.ranked, map.blRanked)) { return false; }
|
||||
if (!isFitCurated(filter, !!map.curator)) { return false; }
|
||||
if (!isFitVerified(filter, !!map.curatedAt)) { return false; }
|
||||
if (!isFitSearch(search, {songName: map.name, songAuthorName: map.metadata.songAuthorName, levelMappers: [map.metadata.levelAuthorName]})) { return false; }
|
||||
@@ -363,7 +396,7 @@ export const isSongDetailsFitMapFilter = ({filter, map, search}: { filter: MapFi
|
||||
if (!isFitChroma(filter, map.difficulties.some(diff => !!diff.chroma))) { return false; }
|
||||
if (!isFitFullSpread(filter, map.difficulties.length)) { return false; }
|
||||
if (!isFitAutomapper(filter, map.automapper)){ return false; }
|
||||
if (!isFitRanked(filter, map.ranked || map.blRanked)) { return false; }
|
||||
if (!isFitLeaderboard(filter, map.ranked, map.blRanked)) { return false; }
|
||||
if (!isFitCurated(filter, map.curated)) { return false; }
|
||||
if (!isFitVerified(filter, map.uploader.verified)) { return false; }
|
||||
if (!isFitSearch(search, {songName: map.name, songAuthorName: map.uploader.name, levelMappers: [map.uploader.name]})) { return false; }
|
||||
|
||||
@@ -324,12 +324,12 @@ export function MapItemComponent <T = unknown>({ hash, title, autor, songAutor,
|
||||
<motion.div className="w-full h-5 pb-1 pr-7 flex items-center gap-1" onHoverStart={bottomBarHoverStart} onHoverEnd={bottomBarHoverEnd}>
|
||||
{ranked && (
|
||||
<div className="text-yellow-300 bg-current rounded-full px-1 h-full flex items-center justify-center">
|
||||
<span className="uppercase text-xs font-bold tracking-wide brightness-[.25]">{t("maps.map-specificities.ranked")}</span>
|
||||
<span className="uppercase text-xs font-bold tracking-wide brightness-[.25]">{t("maps.map-leaderboard.Ranked")}</span>
|
||||
</div>
|
||||
)}
|
||||
{blRanked && (
|
||||
<div className="bg-pink-400 bg-current rounded-full px-1 h-full flex items-center justify-center">
|
||||
<span className="uppercase text-xs font-bold tracking-wide brightness-[.25]">{t("maps.map-specificities.ranked")}</span>
|
||||
<span className="uppercase text-xs font-bold tracking-wide brightness-[.25]">{t("maps.map-leaderboard.Ranked")}</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="h-full grow flex items-start content-start">{renderDiffPreview()}</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { BsmButton, BsmButtonType } from "renderer/components/shared/bsm-button.component";
|
||||
import { BsmImage } from "renderer/components/shared/bsm-image.component";
|
||||
import { cn } from "renderer/helpers/css-class.helpers";
|
||||
import { useTranslation } from "renderer/hooks/use-translation.hook";
|
||||
import { useTranslationV2 } from "renderer/hooks/use-translation.hook";
|
||||
import { ModalComponent, ModalExitCode } from "renderer/services/modale.service";
|
||||
|
||||
type BasicModalOptions = {
|
||||
@@ -12,7 +12,11 @@ type BasicModalOptions = {
|
||||
id: string;
|
||||
text: string;
|
||||
type: BsmButtonType,
|
||||
isCancel?: boolean;
|
||||
/**
|
||||
* true - ModalExitCode.COMPLETED
|
||||
* undefined/false - ModalExitCode.CANCELED
|
||||
*/
|
||||
onClick?: () => boolean;
|
||||
}[];
|
||||
buttonsLayout?: "row" | "column";
|
||||
};
|
||||
@@ -21,20 +25,32 @@ export const BasicModal: ModalComponent<BasicModalOptions["buttons"][0]["id"], B
|
||||
data: { title, image, body, buttons, buttonsLayout = "column" }
|
||||
} }) => {
|
||||
|
||||
const t = useTranslation();
|
||||
const t = useTranslationV2();
|
||||
|
||||
const handleClick = (button: BasicModalOptions["buttons"][0]) => {
|
||||
resolver({ exitCode: button.isCancel ? ModalExitCode.CANCELED : ModalExitCode.COMPLETED, data: button.id });
|
||||
resolver({
|
||||
exitCode: button.onClick?.()
|
||||
? ModalExitCode.COMPLETED
|
||||
: ModalExitCode.CANCELED,
|
||||
data: button.id
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<form className="text-gray-900 dark:text-white">
|
||||
<h1 className="text-3xl uppercase tracking-wide w-full text-center">{t(title)}</h1>
|
||||
<h1 className="text-3xl uppercase tracking-wide w-full text-center">{t.text(title)}</h1>
|
||||
<BsmImage className="mx-auto h-24" image={image} />
|
||||
{ body && <p className="w-full">{t(body)}</p> }
|
||||
{ body && <p className="w-full">{t.text(body)}</p> }
|
||||
<div className={cn("grid gap-2 mt-4")} style={{ gridAutoFlow: buttonsLayout, ...(buttonsLayout === "row" ? { gridTemplateRows: `repeat(${buttons.length}, 1fr)` } : { gridTemplateColumns: `repeat(${buttons.length}, 1fr)` }) }}>
|
||||
{buttons.map(button => (
|
||||
<BsmButton key={button.id} typeColor={button.type} className="h-8 rounded-md text-center flex justify-center items-center" onClick={() => handleClick(button)} withBar={false} text={button.text} />
|
||||
<BsmButton
|
||||
key={button.id}
|
||||
typeColor={button.type}
|
||||
className="h-8 rounded-md text-center flex justify-center items-center"
|
||||
onClick={() => handleClick(button)}
|
||||
withBar={false}
|
||||
text={button.text}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</form>
|
||||
|
||||
+42
-20
@@ -1,25 +1,52 @@
|
||||
import { ModalComponent, ModalExitCode } from "../../../../services/modale.service";
|
||||
import { useState } from "react";
|
||||
import { ModalComponent, ModalExitCode, ModalService } from "../../../../services/modale.service";
|
||||
import { MouseEventHandler, useState } from "react";
|
||||
import { BsmButton } from "renderer/components/shared/bsm-button.component";
|
||||
import { useTranslation } from "renderer/hooks/use-translation.hook";
|
||||
import { useTranslationV2 } from "renderer/hooks/use-translation.hook";
|
||||
import { MetaIcon } from "renderer/components/svgs/icons/meta-icon.component";
|
||||
import { BsmCheckbox } from "renderer/components/shared/bsm-checkbox.component";
|
||||
import Tippy from "@tippyjs/react";
|
||||
import { useService } from "renderer/hooks/use-service.hook";
|
||||
import { IpcService } from "renderer/services/ipc.service";
|
||||
import { lastValueFrom } from "rxjs";
|
||||
import { EnterMetaTokenModal } from "./enter-meta-token-modal.component";
|
||||
import { CustomError } from "shared/models/exceptions/custom-error.class";
|
||||
import { MetaAuthErrorCodes } from "shared/models/bs-version-download/oculus-download.model";
|
||||
import { NotificationService } from "renderer/services/notification.service";
|
||||
|
||||
type ReturnType = { method: MetaAuthMethod.META, stay: boolean } | { method: MetaAuthMethod.MANUAL };
|
||||
export const LoginToMetaModal: ModalComponent<string> = ({ resolver }) => {
|
||||
|
||||
export const LoginToMetaModal: ModalComponent<ReturnType> = ({ resolver }) => {
|
||||
const { text: t } = useTranslationV2();
|
||||
|
||||
const t = useTranslation();
|
||||
const ipc = useService(IpcService);
|
||||
const modal = useService(ModalService);
|
||||
const notifications = useService(NotificationService);
|
||||
|
||||
const [stay, setStay] = useState(false);
|
||||
const [stay, setStay] = useState(true);
|
||||
const [authWindowOpen, setAuthWindowOpen] = useState(false);
|
||||
|
||||
const submit = () => {
|
||||
resolver({ exitCode: ModalExitCode.COMPLETED, data: { method: MetaAuthMethod.META, stay } });
|
||||
};
|
||||
const handleLoginWithMeta = () => {
|
||||
setAuthWindowOpen(() => true);
|
||||
lastValueFrom(ipc.sendV2("login-with-meta", stay)).then(token => {
|
||||
resolver({ exitCode: ModalExitCode.COMPLETED, data: token });
|
||||
}).catch((e: CustomError) => {
|
||||
if(e.code === MetaAuthErrorCodes.META_LOGIN_WINDOW_CLOSED_BY_USER){
|
||||
return;
|
||||
}
|
||||
|
||||
const enterTokenManually = () => {
|
||||
resolver({ exitCode: ModalExitCode.COMPLETED, data: { method: MetaAuthMethod.MANUAL } });
|
||||
if(e.code){
|
||||
notifications.notifyError({title: "notifications.types.error", desc: `notifications.bs-download.oculus-download.errors.msg.${e.code}`});
|
||||
}
|
||||
|
||||
resolver({ exitCode: ModalExitCode.CANCELED });
|
||||
}).finally(() => {
|
||||
setAuthWindowOpen(() => false);
|
||||
})
|
||||
}
|
||||
|
||||
const handleEnterTokenManually: MouseEventHandler<HTMLButtonElement> = async (e) => {
|
||||
e.preventDefault();
|
||||
const res = await modal.openModal(EnterMetaTokenModal);
|
||||
resolver(res);
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -38,17 +65,12 @@ export const LoginToMetaModal: ModalComponent<ReturnType> = ({ resolver }) => {
|
||||
<span>{t("modals.connect-to-meta.stay")}</span>
|
||||
</div>
|
||||
|
||||
<BsmButton className="rounded-md flex justify-center items-center transition-all h-10 w-full" typeColor="primary" text="modals.connect-to-meta.connect-to-meta" withBar={false} onClick={submit}/>
|
||||
<BsmButton className="rounded-md flex justify-center items-center transition-all h-10 w-full" typeColor="primary" text="modals.connect-to-meta.connect-to-meta" withBar={false} onClick={handleLoginWithMeta} disabled={authWindowOpen}/>
|
||||
|
||||
<Tippy className="!bg-neutral-900" arrow={false} content={t("modals.connect-to-meta.body.enter-token-manually-tooltip")} >
|
||||
<p className="text-sm italic underline text-center -translate-y-1 leading-3 cursor-pointer" onClick={enterTokenManually}>{t("modals.connect-to-meta.body.enter-token-manually")}</p>
|
||||
<button className="text-sm italic underline text-center -translate-y-1 leading-3 cursor-pointer" onClick={handleEnterTokenManually}>{t("modals.connect-to-meta.body.enter-token-manually")}</button>
|
||||
</Tippy>
|
||||
|
||||
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
export enum MetaAuthMethod {
|
||||
MANUAL = "manual",
|
||||
META = "meta"
|
||||
}
|
||||
|
||||
+11
-16
@@ -2,31 +2,26 @@ import { ModalComponent, ModalExitCode } from "renderer/services/modale.service"
|
||||
import BeatConflict from "../../../../../assets/images/apngs/beat-conflict.png";
|
||||
import { BsmButton } from "renderer/components/shared/bsm-button.component";
|
||||
import { BsmImage } from "renderer/components/shared/bsm-image.component";
|
||||
import { BsmCheckbox } from "renderer/components/shared/bsm-checkbox.component";
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "renderer/hooks/use-translation.hook";
|
||||
import { useTranslationV2 } from "renderer/hooks/use-translation.hook";
|
||||
|
||||
export const OriginalOculusVersionBackupModal: ModalComponent<boolean, void> = ({ resolver }) => {
|
||||
export const EnableOculusSideloadedApps: ModalComponent<void, void> = ({ resolver }) => {
|
||||
|
||||
const t = useTranslation();
|
||||
const [dontShowAgain, setDontShowAgain] = useState(false);
|
||||
const { text: t } = useTranslationV2();
|
||||
|
||||
const submit = () => {
|
||||
resolver({ exitCode: ModalExitCode.COMPLETED, data: dontShowAgain });
|
||||
resolver({ exitCode: ModalExitCode.COMPLETED });
|
||||
}
|
||||
|
||||
return (
|
||||
<form className="max-w-[450px] text-gray-800 dark:text-gray-200">
|
||||
<h1 className="text-3xl uppercase tracking-wide w-full text-center text-gray-800 dark:text-gray-200">{t("modals.original-version-backup-oculus.title")}</h1>
|
||||
<BsmImage className="mx-auto h-20" image={BeatConflict} />
|
||||
<h1 className="text-3xl uppercase tracking-wide w-full text-center text-gray-800 dark:text-gray-200">{t("modals.enable-oculus-sideloaded-apps.title")}</h1>
|
||||
<BsmImage className="mx-auto h-24" image={BeatConflict} />
|
||||
|
||||
<p className="text-sm italic mb-4 font-bold">{t("modals.original-version-backup-oculus.body.must-be-installed-once")}</p>
|
||||
<p className="mb-4">{t("modals.original-version-backup-oculus.body.will-backup")}</p>
|
||||
<p className="mb-4">{t("modals.enable-oculus-sideloaded-apps.info-1")}</p>
|
||||
<p className="mb-4">{t("modals.enable-oculus-sideloaded-apps.info-2")}</p>
|
||||
<p className="mb-4">{t("modals.enable-oculus-sideloaded-apps.info-3")}</p>
|
||||
|
||||
<div className="flex flex-row justify-start items-center gap-1.5 my-4" >
|
||||
<BsmCheckbox className="relative z-[1] w-6 aspect-square" checked={dontShowAgain} onChange={setDontShowAgain} />
|
||||
<span>{t("modals.original-version-backup-oculus.not-remind-me")}</span>
|
||||
</div>
|
||||
<a className="underline mb-5 block" href="https://github.com/Zagrios/bs-manager/wiki/Activate-Oculus-sideloading" target="_blank">{t("modals.enable-oculus-sideloaded-apps.i-want-to-do-it-myself")}</a>
|
||||
|
||||
<div className="grid grid-flow-col grid-cols-2 gap-4">
|
||||
<BsmButton
|
||||
@@ -43,7 +38,7 @@ export const OriginalOculusVersionBackupModal: ModalComponent<boolean, void> = (
|
||||
className="rounded-md transition-all h-10 flex items-center justify-center"
|
||||
onClick={submit}
|
||||
withBar={false}
|
||||
text="modals.original-version-backup-oculus.understood"
|
||||
text="modals.enable-oculus-sideloaded-apps.understood"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
@@ -0,0 +1,108 @@
|
||||
import { BsmButton } from "renderer/components/shared/bsm-button.component";
|
||||
import { BsmImage } from "renderer/components/shared/bsm-image.component";
|
||||
import { useTranslationV2 } from "renderer/hooks/use-translation.hook";
|
||||
import { ModalComponent, ModalExitCode } from "renderer/services/modale.service";
|
||||
import BeatRunning from "../../../../../assets/images/apngs/beat-running.png";
|
||||
import Tippy from "@tippyjs/react";
|
||||
import { BsmLink } from "renderer/components/shared/bsm-link.component";
|
||||
import { BSVersion } from "shared/bs-version.interface";
|
||||
import { useConstant } from "renderer/hooks/use-constant.hook";
|
||||
import { useService } from "renderer/hooks/use-service.hook";
|
||||
import { VersionFolderLinkerService } from "renderer/services/version-folder-linker.service";
|
||||
import { useObservable } from "renderer/hooks/use-observable.hook";
|
||||
import { useEffect, useState } from "react";
|
||||
import { noop } from "shared/helpers/function.helpers";
|
||||
import { MODEL_TYPE_FOLDERS } from "shared/models/models/constants";
|
||||
import { PlaylistsManagerService } from "renderer/services/playlists-manager.service";
|
||||
import { MapsManagerService } from "renderer/services/maps-manager.service";
|
||||
import { map } from "rxjs";
|
||||
|
||||
export const LinkContentModal: ModalComponent<boolean, {version: BSVersion, contentType: "maps"|"playlists"|"avatars"|"sabers"|"platforms"|"blocks"}> = ({options: { data: { version, contentType } }, resolver }) => {
|
||||
const { text: t, element: te } = useTranslationV2();
|
||||
|
||||
const versionLinker = useService(VersionFolderLinkerService);
|
||||
|
||||
const sharedPath = useObservable(() => versionLinker.getSharedFolder().pipe(map(sharedPath => {
|
||||
switch(contentType){
|
||||
case "maps":
|
||||
return window.electron.path.join(sharedPath, "SharedMaps", "CustomLevels");
|
||||
case "playlists":
|
||||
return window.electron.path.join(sharedPath, PlaylistsManagerService.RELATIVE_PLAYLISTS_FOLDER);
|
||||
case "avatars":
|
||||
return window.electron.path.join(sharedPath, MODEL_TYPE_FOLDERS.avatar);
|
||||
case "sabers":
|
||||
return window.electron.path.join(sharedPath, MODEL_TYPE_FOLDERS.saber);
|
||||
case "platforms":
|
||||
return window.electron.path.join(sharedPath, MODEL_TYPE_FOLDERS.platform);
|
||||
case "blocks":
|
||||
return window.electron.path.join(sharedPath, MODEL_TYPE_FOLDERS.bloq);
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
})), "");
|
||||
|
||||
const contentsPath = useConstant(() => {
|
||||
switch(contentType){
|
||||
case "maps":
|
||||
return window.electron.path.join(version.path, MapsManagerService.RELATIVE_MAPS_FOLDER);
|
||||
case "playlists":
|
||||
return window.electron.path.join(version.path, PlaylistsManagerService.RELATIVE_PLAYLISTS_FOLDER);
|
||||
case "avatars":
|
||||
return window.electron.path.join(version.path, MODEL_TYPE_FOLDERS.avatar);
|
||||
case "sabers":
|
||||
return window.electron.path.join(version.path, MODEL_TYPE_FOLDERS.saber);
|
||||
case "platforms":
|
||||
return window.electron.path.join(version.path, MODEL_TYPE_FOLDERS.platform);
|
||||
case "blocks":
|
||||
return window.electron.path.join(version.path, MODEL_TYPE_FOLDERS.bloq);
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
});
|
||||
const [pathCopied, setPathCopied] = useState<"maps"|"shared">(null);
|
||||
|
||||
useEffect(() => {
|
||||
if(!pathCopied){
|
||||
return noop;
|
||||
}
|
||||
|
||||
const timeout = setTimeout(() => {
|
||||
setPathCopied(() => null);
|
||||
}, 1000);
|
||||
|
||||
return () => clearTimeout(timeout);
|
||||
}, [pathCopied])
|
||||
|
||||
const putInClipboard = (text: string, element: "maps"|"shared") => {
|
||||
navigator.clipboard.writeText(text);
|
||||
setPathCopied(() => element);
|
||||
}
|
||||
|
||||
return (
|
||||
<form className="text-gray-800 dark:text-gray-200">
|
||||
<h1 className="text-3xl uppercase tracking-wide w-full text-center">{t("modals.link-contents.title", { contentType: t(`misc.${contentType}`).toLowerCase() })}</h1>
|
||||
<BsmImage className="mx-auto h-24" image={BeatRunning} />
|
||||
<p className="max-w-sm w-full mb-3">{t("modals.link-contents.p-1", { contentType: t(`misc.${contentType}`).toLowerCase() })}</p>
|
||||
<p className="max-w-sm w-full mb-3">
|
||||
{te("modals.link-contents.p-2", {itemsHtml: (
|
||||
<Tippy content={pathCopied === "maps" ? t("misc.copied") : contentsPath} hideOnClick={false} theme="default" maxWidth={Infinity}>
|
||||
<button className="bg-theme-1 rounded-md px-1 cursor-pointer" onClick={e => { e.preventDefault(); putInClipboard(contentsPath, "maps") }}>{t(`misc.${contentType}`)}</button>
|
||||
</Tippy>
|
||||
), sharedHtml: (
|
||||
<Tippy content={pathCopied === "shared" ? t("misc.copied") : sharedPath} hideOnClick={false} theme="default" maxWidth={Infinity}>
|
||||
<button className="bg-theme-1 rounded-md px-1 cursor-pointer" onClick={e => { e.preventDefault(); putInClipboard(sharedPath, "shared") }}>{t("misc.shared")}</button>
|
||||
</Tippy>
|
||||
), contentType: <>{t(`misc.${contentType}`).toLowerCase()}</> })}
|
||||
</p>
|
||||
<p className="max-w-sm w-full italic my-2 text-warning-400">{t("modals.link-contents.warning", {contentType: t(`misc.${contentType}`).toLowerCase()})}</p>
|
||||
<div className="flex justify-center items-center gap-3 *:underline *:text-sm *:text-neutral-200">
|
||||
<BsmLink href="https://en.qrwp.org/Symbolic_link">{t("modals.link-contents.what-is-a-symbolic-link")}</BsmLink>
|
||||
<BsmLink href="https://discord.gg/uSqbHVpKdV">{t("modals.link-contents.i-need-help")}</BsmLink>
|
||||
</div>
|
||||
<div className="grid grid-flow-col grid-cols-2 gap-2 mt-4 h-8">
|
||||
<BsmButton typeColor="cancel" className="rounded-md text-center transition-all flex justify-center items-center" onClick={() => resolver({ exitCode: ModalExitCode.CANCELED })} withBar={false} text="misc.cancel" />
|
||||
<BsmButton typeColor="primary" className="rounded-md text-center transition-all flex justify-center items-center" onClick={() => resolver({ exitCode: ModalExitCode.COMPLETED })} withBar={false} text={t("modals.link-contents.valid-btn", { contentType: t(`misc.${contentType}`).toLowerCase() })} />
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
@@ -1,31 +0,0 @@
|
||||
import { useState } from "react";
|
||||
import { BsmButton } from "renderer/components/shared/bsm-button.component";
|
||||
import { BsmCheckbox } from "renderer/components/shared/bsm-checkbox.component";
|
||||
import { BsmImage } from "renderer/components/shared/bsm-image.component";
|
||||
import { useTranslation } from "renderer/hooks/use-translation.hook";
|
||||
import { ModalComponent, ModalExitCode } from "renderer/services/modale.service";
|
||||
import BeatRunning from "../../../../../assets/images/apngs/beat-running.png";
|
||||
|
||||
export const LinkMapsModal: ModalComponent<boolean> = ({ resolver }) => {
|
||||
const t = useTranslation();
|
||||
const [keepMaps, setKeepMaps] = useState(true);
|
||||
|
||||
return (
|
||||
<form className="text-gray-800 dark:text-gray-200">
|
||||
<h1 className="text-3xl uppercase tracking-wide w-full text-center">{t("modals.maps-actions.link-maps.title")}</h1>
|
||||
<BsmImage className="mx-auto h-24" image={BeatRunning} />
|
||||
<p className="max-w-sm w-full">{t("modals.maps-actions.link-maps.desc")}</p>
|
||||
<p className="max-w-sm w-full text-sm italic mt-2">{t("modals.maps-actions.link-maps.info")}</p>
|
||||
<div className="relative h-5 flex my-3 items-center">
|
||||
<BsmCheckbox className="h-full aspect-square relative bg-inherit mr-1.5 z-[1]" checked={keepMaps} onChange={setKeepMaps} />
|
||||
<span className="text-sm mb-0.5 cursor-help" title={t("modals.maps-actions.link-maps.keep-maps.title")}>
|
||||
{t("modals.maps-actions.link-maps.keep-maps.label")}
|
||||
</span>
|
||||
</div>
|
||||
<div className="grid grid-flow-col grid-cols-2 gap-4 mt-4">
|
||||
<BsmButton typeColor="cancel" className="rounded-md text-center transition-all" onClick={() => resolver({ exitCode: ModalExitCode.CANCELED })} withBar={false} text="misc.cancel" />
|
||||
<BsmButton typeColor="primary" className="rounded-md text-center transition-all" onClick={() => resolver({ exitCode: ModalExitCode.COMPLETED, data: keepMaps })} withBar={false} text="modals.maps-actions.link-maps.valid-btn" />
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
@@ -1,34 +0,0 @@
|
||||
import { useState } from "react";
|
||||
import { BsmButton } from "renderer/components/shared/bsm-button.component";
|
||||
import { BsmCheckbox } from "renderer/components/shared/bsm-checkbox.component";
|
||||
import { BsmImage } from "renderer/components/shared/bsm-image.component";
|
||||
import { useTranslation } from "renderer/hooks/use-translation.hook";
|
||||
import { ModalComponent, ModalExitCode } from "renderer/services/modale.service";
|
||||
import BeatRunning from "../../../../../assets/images/apngs/beat-running.png";
|
||||
import Tippy from "@tippyjs/react";
|
||||
|
||||
export const LinkPlaylistModal: ModalComponent<boolean> = ({ resolver }) => {
|
||||
const t = useTranslation();
|
||||
const [keepMaps, setKeepMaps] = useState(true);
|
||||
|
||||
return (
|
||||
<form className="text-gray-800 dark:text-gray-200">
|
||||
<h1 className="text-3xl uppercase tracking-wide w-full text-center">{t("playlist.link-playlists")}</h1>
|
||||
<BsmImage className="mx-auto h-24" image={BeatRunning} />
|
||||
<p className="max-w-sm w-full">{t("playlist.link-playlist-desc")}</p>
|
||||
<p className="max-w-sm w-full text-sm italic mt-2">{t("playlist.link-playlist-info")}</p>
|
||||
<div className="relative h-5 flex my-3 items-center">
|
||||
<BsmCheckbox className="h-full aspect-square relative bg-inherit mr-1.5 z-[1]" checked={keepMaps} onChange={setKeepMaps} />
|
||||
<Tippy content={t("playlist.keep-playlists-tip")} placement="top" theme="default">
|
||||
<span className="text-sm mb-0.5 cursor-help">
|
||||
{t("playlist.keep-playlists")}
|
||||
</span>
|
||||
</Tippy>
|
||||
</div>
|
||||
<div className="grid grid-flow-col grid-cols-2 gap-4 mt-4">
|
||||
<BsmButton typeColor="cancel" className="rounded-md text-center transition-all" onClick={() => resolver({ exitCode: ModalExitCode.CANCELED })} withBar={false} text="misc.cancel" />
|
||||
<BsmButton typeColor="primary" className="rounded-md text-center transition-all" onClick={() => resolver({ exitCode: ModalExitCode.COMPLETED, data: keepMaps })} withBar={false} text="playlist.link-playlists" />
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
@@ -1,32 +0,0 @@
|
||||
import { useState } from "react";
|
||||
import { BsmButton } from "renderer/components/shared/bsm-button.component";
|
||||
import { BsmCheckbox } from "renderer/components/shared/bsm-checkbox.component";
|
||||
import { BsmImage } from "renderer/components/shared/bsm-image.component";
|
||||
import { useTranslation } from "renderer/hooks/use-translation.hook";
|
||||
import { ModalComponent, ModalExitCode } from "renderer/services/modale.service";
|
||||
import { MSModelType } from "shared/models/models/model-saber.model";
|
||||
import BeatRunning from "../../../../../../assets/images/apngs/beat-running.png";
|
||||
|
||||
export const LinkModelsModal: ModalComponent<boolean, MSModelType> = ({ resolver, options: {data} }) => {
|
||||
const t = useTranslation();
|
||||
const [keepMaps, setKeepMaps] = useState(true);
|
||||
|
||||
return (
|
||||
<form className="text-gray-800 dark:text-gray-200">
|
||||
<h1 className="text-3xl uppercase tracking-wide w-full text-center">{t(`models.modals.link-models.${data}.title`)}</h1>
|
||||
<BsmImage className="mx-auto h-24" image={BeatRunning} />
|
||||
<p className="max-w-sm w-full">{t(`models.modals.link-models.${data}.desc`)}</p>
|
||||
<p className="max-w-sm w-full text-sm italic mt-2">{t(`models.modals.link-models.${data}.info`)}</p>
|
||||
<div className="relative h-5 flex my-3 items-center">
|
||||
<BsmCheckbox className="h-full aspect-square relative bg-inherit mr-1.5 z-[1]" checked={keepMaps} onChange={setKeepMaps} />
|
||||
<span className="text-sm mb-0.5 cursor-help" title={t(`models.modals.link-models.${data}.keep-models.title`)}>
|
||||
{t(`models.modals.link-models.${data}.keep-models.label`)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="grid grid-flow-col grid-cols-2 gap-4 mt-4">
|
||||
<BsmButton typeColor="cancel" className="rounded-md text-center transition-all" onClick={() => resolver({ exitCode: ModalExitCode.CANCELED })} withBar={false} text="misc.cancel" />
|
||||
<BsmButton typeColor="primary" className="rounded-md text-center transition-all" onClick={() => resolver({ exitCode: ModalExitCode.COMPLETED, data: keepMaps })} withBar={false} text={`models.modals.link-models.${data}.valid-btn`} />
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
@@ -1,31 +0,0 @@
|
||||
import { useState } from "react";
|
||||
import { BsmButton } from "renderer/components/shared/bsm-button.component";
|
||||
import { BsmCheckbox } from "renderer/components/shared/bsm-checkbox.component";
|
||||
import { BsmImage } from "renderer/components/shared/bsm-image.component";
|
||||
import { useTranslation } from "renderer/hooks/use-translation.hook";
|
||||
import { ModalComponent, ModalExitCode } from "renderer/services/modale.service";
|
||||
import BeatConflict from "../../../../../../assets/images/apngs/beat-conflict.png";
|
||||
import { MSModelType } from "shared/models/models/model-saber.model";
|
||||
|
||||
export const UnlinkModelsModal: ModalComponent<boolean, MSModelType> = ({ resolver, options: {data} }) => {
|
||||
const t = useTranslation();
|
||||
const [keepMaps, setKeepMaps] = useState(true);
|
||||
|
||||
return (
|
||||
<form className="text-gray-800 dark:text-gray-200">
|
||||
<h1 className="text-3xl uppercase tracking-wide w-full text-center">{t(`models.modals.unlink-models.${data}.title`)}</h1>
|
||||
<BsmImage className="mx-auto h-24" image={BeatConflict} />
|
||||
<p className="max-w-sm w-full">{t(`models.modals.unlink-models.${data}.desc`)}</p>
|
||||
<div className="relative h-5 flex my-3 items-center">
|
||||
<BsmCheckbox className="h-full aspect-square relative bg-inherit mr-1.5 z-[1]" checked={keepMaps} onChange={setKeepMaps} />
|
||||
<span className="text-sm mb-0.5 cursor-help" title={t(`models.modals.unlink-models.${data}.keep-models.title`)}>
|
||||
{t(`models.modals.unlink-models.${data}.keep-models.label`)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="grid grid-flow-col grid-cols-2 gap-4 mt-4">
|
||||
<BsmButton typeColor="cancel" className="rounded-md text-center transition-all" onClick={() => resolver({ exitCode: ModalExitCode.CANCELED })} withBar={false} text="misc.cancel" />
|
||||
<BsmButton typeColor="error" className="rounded-md text-center transition-all" onClick={() => resolver({ exitCode: ModalExitCode.COMPLETED, data: keepMaps })} withBar={false} text={`models.modals.unlink-models.${data}.valid-btn`} />
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
@@ -6,30 +6,45 @@ import { BsmButton } from "renderer/components/shared/bsm-button.component";
|
||||
import { useObservable } from "renderer/hooks/use-observable.hook";
|
||||
import { useService } from "renderer/hooks/use-service.hook";
|
||||
import { useThemeColor } from "renderer/hooks/use-theme-color.hook";
|
||||
import { useTranslation } from "renderer/hooks/use-translation.hook";
|
||||
import { useTranslationV2 } from "renderer/hooks/use-translation.hook";
|
||||
import { BSVersionManagerService } from "renderer/services/bs-version-manager.service";
|
||||
import { ConfigurationService } from "renderer/services/configuration.service";
|
||||
import { IpcService } from "renderer/services/ipc.service";
|
||||
import { ModalComponent } from "renderer/services/modale.service";
|
||||
import { ModalComponent, ModalService } from "renderer/services/modale.service";
|
||||
import { FolderLinkState, VersionFolderLinkerService, VersionLinkerActionType } from "renderer/services/version-folder-linker.service";
|
||||
import { lastValueFrom } from "rxjs";
|
||||
import { BSVersion } from "shared/bs-version.interface";
|
||||
import { NotificationService } from "renderer/services/notification.service";
|
||||
import { BasicModal } from "../basic-modal.component";
|
||||
import BeatConflict from "../../../../../assets/images/apngs/beat-conflict.png";
|
||||
|
||||
export const ShareFoldersModal: ModalComponent<void, BSVersion> = ({ options: {data} }) => {
|
||||
const SHARED_FOLDERS_KEY = "default-shared-folders";
|
||||
const SHARED_FOLDERS_KEY = "default-shared-folders";
|
||||
const SHARED_FOLDER_BLACKLIST = {
|
||||
error: [
|
||||
".DepotDownloader",
|
||||
"Beat Saber_Data",
|
||||
"IPA",
|
||||
"Libs",
|
||||
"Plugins",
|
||||
"MonoBleedingEdge",
|
||||
],
|
||||
warn: [
|
||||
"DLC",
|
||||
"Logs",
|
||||
],
|
||||
};
|
||||
|
||||
export const ShareFoldersModal: ModalComponent<void, BSVersion> = ({ options: { data: version } }) => {
|
||||
const config = useService(ConfigurationService);
|
||||
const ipc = useService(IpcService);
|
||||
const linker = useService(VersionFolderLinkerService);
|
||||
const versionManager = useService(BSVersionManagerService);
|
||||
|
||||
const t = useTranslation();
|
||||
const t = useTranslationV2();
|
||||
|
||||
const [folders, setFolders] = useState<string[]>(Array.from(new Set([...config.get<string[]>(SHARED_FOLDERS_KEY)]).values()));
|
||||
|
||||
useEffect(() => {
|
||||
linker
|
||||
.getLinkedFolders(data, { relative: true })
|
||||
.getLinkedFolders(version, { relative: true })
|
||||
.toPromise()
|
||||
.then(linkedFolders => {
|
||||
setFolders(prev => Array.from(new Set([...prev, ...linkedFolders]).values()));
|
||||
@@ -45,42 +60,24 @@ export const ShareFoldersModal: ModalComponent<void, BSVersion> = ({ options: {d
|
||||
config.set(SHARED_FOLDERS_KEY, folders);
|
||||
}, [folders]);
|
||||
|
||||
const addFolder = async () => {
|
||||
const versionPath = await lastValueFrom(versionManager.getVersionPath(data));
|
||||
const folder = await lastValueFrom(ipc.sendV2("choose-folder", {
|
||||
defaultPath: versionPath
|
||||
}));
|
||||
|
||||
if (!folder || folder.canceled || !folder.filePaths?.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const relativeFolder = await lastValueFrom(ipc.sendV2("full-version-path-to-relative", { version: data, fullPath: folder.filePaths[0] }));
|
||||
|
||||
if (folders.includes(relativeFolder)) {
|
||||
return;
|
||||
}
|
||||
|
||||
setFolders(pre => [...pre, relativeFolder]);
|
||||
};
|
||||
|
||||
const removeFolder = (index: number) => {
|
||||
setFolders(prev => prev.filter((_, i) => i !== index));
|
||||
};
|
||||
|
||||
const linkAll = () => {
|
||||
folders.forEach(relativeFolder => linker.linkVersionFolder({ version: data, relativeFolder, type: VersionLinkerActionType.Link }));
|
||||
folders.forEach(relativeFolder => linker.linkVersionFolder({ version, relativeFolder, type: VersionLinkerActionType.Link }));
|
||||
};
|
||||
|
||||
return (
|
||||
<form className="w-full max-w-md ">
|
||||
<h1 className="text-3xl uppercase tracking-wide w-full text-center text-gray-800 dark:text-gray-200">{t("modals.shared-folders.title")}</h1>
|
||||
<p className="my-3">{t("modals.shared-folders.description")}</p>
|
||||
<h1 className="text-3xl uppercase tracking-wide w-full text-center text-gray-800 dark:text-gray-200">{t.text("modals.shared-folders.title")}</h1>
|
||||
<p className="my-3">{t.text("modals.shared-folders.description")}</p>
|
||||
<ul className="flex flex-col gap-1 mb-2 h-[300px] max-h-[300px] overflow-scroll scrollbar-default px-1">
|
||||
{folders.map((folder, index) => (
|
||||
<FolderItem
|
||||
key={folder}
|
||||
version={data}
|
||||
version={version}
|
||||
relativeFolder={folder}
|
||||
onDelete={() => {
|
||||
removeFolder(index);
|
||||
@@ -89,13 +86,93 @@ export const ShareFoldersModal: ModalComponent<void, BSVersion> = ({ options: {d
|
||||
))}
|
||||
</ul>
|
||||
<div className="grid grid-flow-col gap-3 grid-cols-2">
|
||||
<BsmButton icon="add" className="h-8 rounded-md flex justify-center items-center font-bold bg-light-main-color-1 dark:bg-main-color-1" iconClassName="h-6 aspect-square text-current" onClick={addFolder} withBar={false} text="modals.shared-folders.buttons.add-folder" />
|
||||
<AddFolderButton
|
||||
version={version}
|
||||
folders={folders}
|
||||
setFolders={setFolders}
|
||||
/>
|
||||
<BsmButton icon="link" className="h-8 rounded-md flex justify-center items-center font-bold" typeColor="primary" iconClassName="h-6 aspect-square text-current -rotate-45" onClick={linkAll} withBar={false} text="modals.shared-folders.buttons.link-all" />
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
function AddFolderButton({
|
||||
version,
|
||||
folders,
|
||||
setFolders,
|
||||
}: Readonly<{
|
||||
version: BSVersion;
|
||||
folders: string[];
|
||||
setFolders: (value: string[]) => void;
|
||||
}>) {
|
||||
const config = useService(ConfigurationService);
|
||||
const ipc = useService(IpcService);
|
||||
const versionManager = useService(BSVersionManagerService);
|
||||
const notification = useService(NotificationService);
|
||||
const modal = useService(ModalService);
|
||||
|
||||
const t = useTranslationV2();
|
||||
|
||||
const addFolder = async () => {
|
||||
const versionPath = await lastValueFrom(versionManager.getVersionPath(version));
|
||||
const folder = await lastValueFrom(ipc.sendV2("choose-folder", {
|
||||
defaultPath: versionPath
|
||||
}));
|
||||
|
||||
if (!folder || folder.canceled || !folder.filePaths?.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const relativeFolder = await lastValueFrom(ipc.sendV2("full-version-path-to-relative", { version, fullPath: folder.filePaths[0] }));
|
||||
if (folders.includes(relativeFolder)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (SHARED_FOLDER_BLACKLIST.error.includes(relativeFolder)) {
|
||||
notification.notifyError({
|
||||
title: "notifications.shared-folder.adding-error.title",
|
||||
desc: t.text("notifications.shared-folder.adding-error.msg", {
|
||||
folder: relativeFolder
|
||||
}),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (SHARED_FOLDER_BLACKLIST.warn.includes(relativeFolder)) {
|
||||
await modal.openModal(BasicModal, { data: {
|
||||
title: "modals.adding-shared-folder.title",
|
||||
body: t.text("modals.adding-shared-folder.description", {
|
||||
folder: relativeFolder
|
||||
}),
|
||||
image: BeatConflict,
|
||||
buttons: [
|
||||
{ id: "cancel", text: "misc.cancel", type: "cancel" },
|
||||
{
|
||||
id: "confirm", text: "misc.confirm", type: "primary",
|
||||
onClick() {
|
||||
config.set(SHARED_FOLDERS_KEY, [...folders, relativeFolder]);
|
||||
return true;
|
||||
},
|
||||
}
|
||||
]
|
||||
}});
|
||||
return;
|
||||
}
|
||||
|
||||
setFolders([...folders, relativeFolder]);
|
||||
};
|
||||
|
||||
return <BsmButton
|
||||
icon="add"
|
||||
className="h-8 rounded-md flex justify-center items-center font-bold bg-light-main-color-1 dark:bg-main-color-1"
|
||||
iconClassName="h-6 aspect-square text-current"
|
||||
onClick={addFolder}
|
||||
withBar={false}
|
||||
text="modals.shared-folders.buttons.add-folder"
|
||||
/>;
|
||||
}
|
||||
|
||||
// -------- FOLDER ITEM --------
|
||||
|
||||
type FolderProps = {
|
||||
@@ -107,7 +184,7 @@ type FolderProps = {
|
||||
const FolderItem = ({ version, relativeFolder, onDelete }: FolderProps) => {
|
||||
const linker = useService(VersionFolderLinkerService);
|
||||
|
||||
const t = useTranslation();
|
||||
const t = useTranslationV2();
|
||||
|
||||
const color = useThemeColor("first-color");
|
||||
const state = useObservable(() => linker.$folderLinkedState(version, relativeFolder), FolderLinkState.Unlinked, [version, relativeFolder]);
|
||||
@@ -139,7 +216,7 @@ const FolderItem = ({ version, relativeFolder, onDelete }: FolderProps) => {
|
||||
{name}
|
||||
</span>
|
||||
<div className="flex flex-row gap-1.5">
|
||||
<Tippy placement="left" content={t(`modals.shared-folders.buttons.${state === FolderLinkState.Linked ? "unlink-folder" : "link-folder"}`)} arrow={false}>
|
||||
<Tippy placement="top" theme="default" content={t.text(`modals.shared-folders.buttons.${state === FolderLinkState.Linked ? "unlink-folder" : "link-folder"}`)}>
|
||||
<LinkButton
|
||||
className="p-0.5 h-7 shrink-0 aspect-square blur-0 cursor-pointer hover:brightness-75"
|
||||
state={state}
|
||||
@@ -164,15 +241,17 @@ const FolderItem = ({ version, relativeFolder, onDelete }: FolderProps) => {
|
||||
);
|
||||
}
|
||||
return (
|
||||
<BsmButton
|
||||
className="aspect-square h-7 rounded-md p-1"
|
||||
icon="trash"
|
||||
withBar={false}
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
onDelete?.();
|
||||
}}
|
||||
/>
|
||||
<Tippy content={t.text("modals.shared-folders.buttons.remove-from-the-list")} theme="default" hideOnClick={false} placement="top">
|
||||
<BsmButton
|
||||
className="aspect-square h-7 rounded-md p-1"
|
||||
icon="trash"
|
||||
withBar={false}
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
onDelete?.();
|
||||
}}
|
||||
/>
|
||||
</Tippy>
|
||||
);
|
||||
})()}
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
import { useState } from "react";
|
||||
import { BsmButton } from "renderer/components/shared/bsm-button.component";
|
||||
import { BsmCheckbox } from "renderer/components/shared/bsm-checkbox.component";
|
||||
import { BsmImage } from "renderer/components/shared/bsm-image.component";
|
||||
import { useTranslationV2 } from "renderer/hooks/use-translation.hook";
|
||||
import { ModalComponent, ModalExitCode } from "renderer/services/modale.service";
|
||||
import BeatConflict from "../../../../../assets/images/apngs/beat-conflict.png";
|
||||
import { BSVersion } from "shared/bs-version.interface";
|
||||
import Tippy from "@tippyjs/react";
|
||||
import { useObservable } from "renderer/hooks/use-observable.hook";
|
||||
import { useService } from "renderer/hooks/use-service.hook";
|
||||
import { VersionFolderLinkerService } from "renderer/services/version-folder-linker.service";
|
||||
import { map } from "rxjs";
|
||||
import { PlaylistsManagerService } from "renderer/services/playlists-manager.service";
|
||||
import { MODEL_TYPE_FOLDERS } from "shared/models/models/constants";
|
||||
import { useConstant } from "renderer/hooks/use-constant.hook";
|
||||
import { MapsManagerService } from "renderer/services/maps-manager.service";
|
||||
import { BsmLink } from "renderer/components/shared/bsm-link.component";
|
||||
|
||||
export const UnlinkContentsModal: ModalComponent<boolean, {version: BSVersion, contentType: "maps"|"playlists"|"avatars"|"sabers"|"platforms"|"blocks"}> = ({options: { data: { version, contentType } }, resolver }) => {
|
||||
const { text: t, element: te } = useTranslationV2();
|
||||
|
||||
const versionLinker = useService(VersionFolderLinkerService);
|
||||
|
||||
const sharedPath = useObservable(() => versionLinker.getSharedFolder().pipe(map(sharedPath => {
|
||||
switch(contentType){
|
||||
case "maps":
|
||||
return window.electron.path.join(sharedPath, "SharedMaps", "CustomLevels");
|
||||
case "playlists":
|
||||
return window.electron.path.join(sharedPath, PlaylistsManagerService.RELATIVE_PLAYLISTS_FOLDER);
|
||||
case "avatars":
|
||||
return window.electron.path.join(sharedPath, MODEL_TYPE_FOLDERS.avatar);
|
||||
case "sabers":
|
||||
return window.electron.path.join(sharedPath, MODEL_TYPE_FOLDERS.saber);
|
||||
case "platforms":
|
||||
return window.electron.path.join(sharedPath, MODEL_TYPE_FOLDERS.platform);
|
||||
case "blocks":
|
||||
return window.electron.path.join(sharedPath, MODEL_TYPE_FOLDERS.bloq);
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
})), "");
|
||||
|
||||
const contentsPath = useConstant(() => {
|
||||
switch(contentType){
|
||||
case "maps":
|
||||
return window.electron.path.join(version.path, MapsManagerService.RELATIVE_MAPS_FOLDER);
|
||||
case "playlists":
|
||||
return window.electron.path.join(version.path, PlaylistsManagerService.RELATIVE_PLAYLISTS_FOLDER);
|
||||
case "avatars":
|
||||
return window.electron.path.join(version.path, MODEL_TYPE_FOLDERS.avatar);
|
||||
case "sabers":
|
||||
return window.electron.path.join(version.path, MODEL_TYPE_FOLDERS.saber);
|
||||
case "platforms":
|
||||
return window.electron.path.join(version.path, MODEL_TYPE_FOLDERS.platform);
|
||||
case "blocks":
|
||||
return window.electron.path.join(version.path, MODEL_TYPE_FOLDERS.bloq);
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
});
|
||||
|
||||
const [keepContents, setKeepContents] = useState(true);
|
||||
const [pathCopied, setPathCopied] = useState<"maps"|"shared">(null);
|
||||
|
||||
const putInClipboard = (text: string, element: "maps"|"shared") => {
|
||||
navigator.clipboard.writeText(text);
|
||||
setPathCopied(() => element);
|
||||
}
|
||||
|
||||
return (
|
||||
<form className="text-gray-800 dark:text-gray-200">
|
||||
<h1 className="text-3xl uppercase tracking-wide w-full text-center">{t("modals.unlink-contents.title", { contentType: t(`misc.${contentType}`).toLowerCase() })}</h1>
|
||||
<BsmImage className="mx-auto h-24" image={BeatConflict} />
|
||||
<p className="max-w-sm w-full mb-3">{t("modals.unlink-contents.p-1", { contentType: t(`misc.${contentType}`).toLowerCase() })}</p>
|
||||
<p className="max-w-sm w-full mb-3">
|
||||
{te("modals.unlink-contents.p-2", { sharedHtml: (
|
||||
<Tippy content={pathCopied === "shared" ? t("misc.copied") : sharedPath} hideOnClick={false} theme="default" maxWidth={Infinity}>
|
||||
<button className="bg-theme-1 rounded-md px-1 cursor-pointer" onClick={e => { e.preventDefault(); putInClipboard(sharedPath, "shared") }}>{t("misc.shared")}</button>
|
||||
</Tippy>
|
||||
), itemsHtml: (
|
||||
<Tippy content={pathCopied === "maps" ? t("misc.copied") : contentsPath} hideOnClick={false} theme="default" maxWidth={Infinity}>
|
||||
<button className="bg-theme-1 rounded-md px-1 cursor-pointer" onClick={e => { e.preventDefault(); putInClipboard(contentsPath, "maps") }}>{t(`misc.${contentType}`)}</button>
|
||||
</Tippy>
|
||||
), contentType: <>{t(`misc.${contentType}`).toLowerCase()}</>})}
|
||||
</p>
|
||||
<div className="flex justify-center items-center gap-3 *:underline *:text-sm *:text-neutral-200">
|
||||
<BsmLink href="https://en.qrwp.org/Symbolic_link">{t("modals.link-contents.what-is-a-symbolic-link")}</BsmLink>
|
||||
<BsmLink href="https://discord.gg/uSqbHVpKdV">{t("modals.link-contents.i-need-help")}</BsmLink>
|
||||
</div>
|
||||
<Tippy content={t("modals.unlink-contents.do-not-copy-contents-tip", { contentType: t(`misc.${contentType}`).toLowerCase() })} theme="default">
|
||||
<div className="relative h-5 flex my-3 items-center w-fit">
|
||||
<BsmCheckbox className="h-full aspect-square relative bg-inherit mr-1.5 z-[1]" checked={!keepContents} onChange={v => setKeepContents(!v)} />
|
||||
<span className="text-sm mb-0.5 cursor-help">
|
||||
{t("modals.unlink-contents.do-not-copy-contents", { contentType: t(`misc.${contentType}`).toLowerCase() })}
|
||||
</span>
|
||||
</div>
|
||||
</Tippy>
|
||||
<div className="grid grid-flow-col grid-cols-2 gap-2 mt-4 h-8">
|
||||
<BsmButton typeColor="cancel" className="rounded-md text-center transition-all flex justify-center items-center" onClick={() => resolver({ exitCode: ModalExitCode.CANCELED })} withBar={false} text="misc.cancel" />
|
||||
<BsmButton typeColor="error" className="rounded-md text-center transition-all flex justify-center items-center" onClick={() => resolver({ exitCode: ModalExitCode.COMPLETED, data: keepContents })} withBar={false} text={t("modals.unlink-contents.valid-btn", { contentType: t(`misc.${contentType}`).toLowerCase() })} />
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
@@ -1,30 +0,0 @@
|
||||
import { useState } from "react";
|
||||
import { BsmButton } from "renderer/components/shared/bsm-button.component";
|
||||
import { BsmCheckbox } from "renderer/components/shared/bsm-checkbox.component";
|
||||
import { BsmImage } from "renderer/components/shared/bsm-image.component";
|
||||
import { useTranslation } from "renderer/hooks/use-translation.hook";
|
||||
import { ModalComponent, ModalExitCode } from "renderer/services/modale.service";
|
||||
import BeatConflict from "../../../../../assets/images/apngs/beat-conflict.png";
|
||||
|
||||
export const UnlinkMapsModal: ModalComponent<boolean> = ({ resolver }) => {
|
||||
const t = useTranslation();
|
||||
const [keepMaps, setKeepMaps] = useState(true);
|
||||
|
||||
return (
|
||||
<form className="text-gray-800 dark:text-gray-200">
|
||||
<h1 className="text-3xl uppercase tracking-wide w-full text-center">{t("modals.maps-actions.unlink-maps.title")}</h1>
|
||||
<BsmImage className="mx-auto h-24" image={BeatConflict} />
|
||||
<p className="max-w-sm w-full">{t("modals.maps-actions.unlink-maps.desc")}</p>
|
||||
<div className="relative h-5 flex my-3 items-center">
|
||||
<BsmCheckbox className="h-full aspect-square relative bg-inherit mr-1.5 z-[1]" checked={keepMaps} onChange={setKeepMaps} />
|
||||
<span className="text-sm mb-0.5 cursor-help" title={t("modals.maps-actions.unlink-maps.keep-maps.title")}>
|
||||
{t("modals.maps-actions.unlink-maps.keep-maps.label")}
|
||||
</span>
|
||||
</div>
|
||||
<div className="grid grid-flow-col grid-cols-2 gap-4 mt-4">
|
||||
<BsmButton typeColor="cancel" className="rounded-md text-center transition-all" onClick={() => resolver({ exitCode: ModalExitCode.CANCELED })} withBar={false} text="misc.cancel" />
|
||||
<BsmButton typeColor="error" className="rounded-md text-center transition-all" onClick={() => resolver({ exitCode: ModalExitCode.COMPLETED, data: keepMaps })} withBar={false} text="modals.maps-actions.unlink-maps.valid-btn" />
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
@@ -1,33 +0,0 @@
|
||||
import { useState } from "react";
|
||||
import { BsmButton } from "renderer/components/shared/bsm-button.component";
|
||||
import { BsmCheckbox } from "renderer/components/shared/bsm-checkbox.component";
|
||||
import { BsmImage } from "renderer/components/shared/bsm-image.component";
|
||||
import { useTranslation } from "renderer/hooks/use-translation.hook";
|
||||
import { ModalComponent, ModalExitCode } from "renderer/services/modale.service";
|
||||
import BeatConflict from "../../../../../assets/images/apngs/beat-conflict.png";
|
||||
import Tippy from "@tippyjs/react";
|
||||
|
||||
export const UnlinkPlaylistModal: ModalComponent<boolean> = ({ resolver }) => {
|
||||
const t = useTranslation();
|
||||
const [keepMaps, setKeepMaps] = useState(true);
|
||||
|
||||
return (
|
||||
<form className="text-gray-800 dark:text-gray-200">
|
||||
<h1 className="text-3xl uppercase tracking-wide w-full text-center">{t("playlist.unlink-playlists")}</h1>
|
||||
<BsmImage className="mx-auto h-24" image={BeatConflict} />
|
||||
<p className="max-w-sm w-full">{t("playlist.unlink-playlist-desc")}</p>
|
||||
<div className="relative h-5 flex my-3 items-center">
|
||||
<BsmCheckbox className="h-full aspect-square relative bg-inherit mr-1.5 z-[1]" checked={keepMaps} onChange={setKeepMaps} />
|
||||
<Tippy content={t("playlist.unlink-keep-playlists-tip")} placement="top" theme="default">
|
||||
<span className="text-sm mb-0.5 cursor-help">
|
||||
{t("playlist.keep-playlists")}
|
||||
</span>
|
||||
</Tippy>
|
||||
</div>
|
||||
<div className="grid grid-flow-col grid-cols-2 gap-4 mt-4">
|
||||
<BsmButton typeColor="cancel" className="rounded-md text-center transition-all" onClick={() => resolver({ exitCode: ModalExitCode.CANCELED })} withBar={false} text="misc.cancel" />
|
||||
<BsmButton typeColor="error" className="rounded-md text-center transition-all" onClick={() => resolver({ exitCode: ModalExitCode.COMPLETED, data: keepMaps })} withBar={false} text="playlist.unlink-playlists" />
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
@@ -2,7 +2,7 @@ import { BSVersion } from "shared/bs-version.interface";
|
||||
import { Link, useLocation } from "react-router-dom";
|
||||
import { useState } from "react";
|
||||
import { distinctUntilChanged, lastValueFrom, map, of, Subscription, switchMap, take } from "rxjs";
|
||||
import { BSLauncherService, LaunchMods } from "renderer/services/bs-launcher.service";
|
||||
import { BSLauncherService } from "renderer/services/bs-launcher.service";
|
||||
import { ConfigurationService } from "renderer/services/configuration.service";
|
||||
import { BSUninstallerService } from "renderer/services/bs-uninstaller.service";
|
||||
import { BSVersionManagerService } from "renderer/services/bs-version-manager.service";
|
||||
@@ -35,7 +35,7 @@ export function BsVersionItem(props: { version: BSVersion }) {
|
||||
|
||||
useOnUpdate(() => {
|
||||
const subs: Subscription[] = []
|
||||
|
||||
|
||||
subs.push(bsDownloader.downloadingVersion$.pipe(map(download => equal(download, props.version)), distinctUntilChanged()).subscribe(isDownloading => {
|
||||
setIsDownloading(() => isDownloading);
|
||||
}));
|
||||
@@ -60,9 +60,7 @@ export function BsVersionItem(props: { version: BSVersion }) {
|
||||
if(equal(downloadingVersion, props.version)){ return; }
|
||||
const launch$ = launcherService.launch({
|
||||
version: state,
|
||||
oculus: !!configService.get<boolean>(LaunchMods.OCULUS_MOD),
|
||||
desktop: !!configService.get<boolean>(LaunchMods.DESKTOP_MOD),
|
||||
debug: !!configService.get<boolean>(LaunchMods.DEBUG_MOD),
|
||||
launchMods: configService.get("launch-mods") ?? [],
|
||||
});
|
||||
return lastValueFrom(launch$).catch(() => {});
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ReactNode } from "react";
|
||||
import { useTranslation } from "renderer/hooks/use-translation.hook";
|
||||
import { useTranslationV2 } from "renderer/hooks/use-translation.hook";
|
||||
|
||||
type Props = {
|
||||
id?: string;
|
||||
@@ -8,11 +8,11 @@ type Props = {
|
||||
minorTitle?: string;
|
||||
description?: string;
|
||||
children?: ReactNode;
|
||||
os?: string;
|
||||
os?: "win32" | "linux";
|
||||
};
|
||||
|
||||
export function SettingContainer({ id, className, title, minorTitle, description, children, os }: Props) {
|
||||
const t = useTranslation();
|
||||
const t = useTranslationV2();
|
||||
|
||||
if (os && os !== window.electron.platform) {
|
||||
return undefined;
|
||||
@@ -20,9 +20,9 @@ export function SettingContainer({ id, className, title, minorTitle, description
|
||||
|
||||
return (
|
||||
<div id={id} className={className || "relative mb-5"}>
|
||||
{title && <h1 className="mb-1 text-2xl font-bold tracking-wide">{t(title)}</h1>}
|
||||
{minorTitle && <h2 className="mb-1 font-bold tracking-wide text-gray-600 dark:text-gray-300">{t(minorTitle)}</h2>}
|
||||
{description && <p className="mb-3 text-sm text-gray-600 dark:text-gray-400">{t(description)}</p>}
|
||||
{title && <h1 className="mb-1 text-2xl font-bold tracking-wide">{t.text(title)}</h1>}
|
||||
{minorTitle && <h2 className="mb-1 font-bold tracking-wide text-gray-600 dark:text-gray-300">{t.text(minorTitle)}</h2>}
|
||||
{description && <p className="mb-3 text-sm text-gray-600 dark:text-gray-400">{t.text(description)}</p>}
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ToogleSwitch } from "../shared/toogle-switch.component";
|
||||
|
||||
type Item = {
|
||||
export type Item = {
|
||||
text: string;
|
||||
desc?: string;
|
||||
checked?: boolean;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { motion } from "framer-motion";
|
||||
import { useThemeColor } from "renderer/hooks/use-theme-color.hook";
|
||||
import { BsmIcon } from "../svgs/bsm-icon.component";
|
||||
import { useTranslation } from "renderer/hooks/use-translation.hook";
|
||||
import { useTranslationV2 } from "renderer/hooks/use-translation.hook";
|
||||
import { FolderLinkState } from "renderer/services/version-folder-linker.service";
|
||||
import React from "react";
|
||||
import React, { forwardRef } from "react";
|
||||
|
||||
export type LinkBtnProps = {
|
||||
className?: string;
|
||||
@@ -12,8 +12,8 @@ export type LinkBtnProps = {
|
||||
onClick?: () => unknown;
|
||||
};
|
||||
|
||||
export function LinkButton({className, title, state, onClick}: LinkBtnProps) {
|
||||
const t = useTranslation();
|
||||
export const LinkButton = forwardRef<HTMLDivElement, LinkBtnProps>(({className, title, state, onClick}, forwardedRef) => {
|
||||
const { text: t } = useTranslationV2();
|
||||
|
||||
const color = useThemeColor("first-color");
|
||||
const disabled = state === FolderLinkState.Processing || state === FolderLinkState.Pending;
|
||||
@@ -41,6 +41,7 @@ export function LinkButton({className, title, state, onClick}: LinkBtnProps) {
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
ref={forwardedRef}
|
||||
variants={{ hover: { rotate: 22.5 }, tap: { rotate: 45 } }}
|
||||
whileHover="hover"
|
||||
whileTap="tap"
|
||||
@@ -55,4 +56,4 @@ export function LinkButton({className, title, state, onClick}: LinkBtnProps) {
|
||||
<BsmIcon className="p-1 absolute top-0 left-0 h-full w-full !bg-transparent -rotate-45 brightness-150" icon={state === FolderLinkState.Linked ? "link" : "unlink"} style={{ color: btnColor() }} />
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -16,7 +16,7 @@ import { TaskIcon } from "./icons/task-icon.component";
|
||||
import { CopyIcon } from "./icons/copy-icon.component";
|
||||
import { SteamIcon } from "./icons/steam-icon.component";
|
||||
import { CSSProperties, memo } from "react";
|
||||
import EditIcon from "./icons/edit-icon.component";
|
||||
import { EditIcon } from "./icons/edit-icon.component";
|
||||
import { ExportIcon } from "./icons/export-icon.component";
|
||||
import PatreonIcon from "./icons/patreon-icon.component";
|
||||
import { SearchIcon } from "./icons/search-icon.component";
|
||||
@@ -69,7 +69,8 @@ import { CancelIcon } from "./icons/cancel-icon.component";
|
||||
import { WarningIcon } from "./icons/warning-icon.component";
|
||||
|
||||
|
||||
export type BsmIconType = SongDetailDiffCharactertistic | ("settings" | "trash" | "favorite" | "folder" | "bsNote" | "check" | "three-dots" | "twitch" | "eye" | "play" | "checkCircleIcon" | "discord" | "info" | "eye-cross" | "terminal" | "desktop" | "oculus" | "add" | "cross" | "task" | "github" | "close" | "thumbUpFill" | "timerFill" | "pause" | "twitter" | "sync" | "chevron-top" | "copy" | "steam" | "edit" | "export" | "patreon" | "search" | "bsMapDifficulty" | "link" | "unlink" | "download" | "filter" | "mee6" | "volume-up" | "volume-off" | "volume-down" | "shortcut" | "backup-restore" | "web-site" | "clean" | "browse" | "add-file" | "cancel" | "warning" | "fr-FR-flag" | "es-ES-flag" | "en-US-flag" | "en-EN-flag" | "de-DE-flag" | "ru-RU-flag" | "zh-CN-flag" | "zh-TW-flag" | "ja-JP-flag" | "ko-KR-flag");
|
||||
|
||||
export type BsmIconType = SongDetailDiffCharactertistic | ("settings" | "trash" | "favorite" | "folder" | "bsNote" | "check" | "three-dots" | "twitch" | "eye" | "play" | "checkCircleIcon" | "discord" | "info" | "eye-cross" | "terminal" | "desktop" | "oculus" | "add" | "cross" | "task" | "github" | "close" | "thumbUpFill" | "timerFill" | "pause" | "twitter" | "sync" | "chevron-top" | "copy" | "steam" | "edit" | "export" | "patreon" | "search" | "bsMapDifficulty" | "link" | "unlink" | "download" | "filter" | "mee6" | "volume-up" | "volume-off" | "volume-down" | "shortcut" | "backup-restore" | "web-site" | "clean" | "browse" | "add-file" | "cancel" | "warning" | "fr-FR-flag" | "es-ES-flag" | "en-US-flag" | "en-EN-flag" | "de-DE-flag" | "ru-RU-flag" | "zh-CN-flag" | "zh-TW-flag" | "ja-JP-flag" | "ko-KR-flag" | "null" );
|
||||
|
||||
export const BsmIcon = memo(({ className, icon, style }: { className?: string; icon: BsmIconType; style?: CSSProperties }) => {
|
||||
// TODO : Very ugly very messy, need to find a better way to do this
|
||||
@@ -277,13 +278,14 @@ export const BsmIcon = memo(({ className, icon, style }: { className?: string; i
|
||||
if(icon === "add-file") {
|
||||
return <AddFileIcon className={className} style={style} />;
|
||||
}
|
||||
|
||||
if(icon === "cancel"){
|
||||
if(icon === "null") {
|
||||
return null;
|
||||
}
|
||||
if(icon === "cancel") {
|
||||
return <CancelIcon className={className} style={style} />;
|
||||
}
|
||||
|
||||
if(icon === "warning"){
|
||||
return <WarningIcon className={className} style={style} />;
|
||||
if(icon === "warning") {
|
||||
return <WarningIcon className={className} style={style} />
|
||||
}
|
||||
|
||||
return <TrashIcon className={className} style={style} />;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { CSSProperties } from "react";
|
||||
import { createSvgIcon } from "../svg-icon.type";
|
||||
|
||||
export function DesktopIcon(props: { className?: string; style?: CSSProperties }) {
|
||||
export const DesktopIcon = createSvgIcon((props, ref) => {
|
||||
return (
|
||||
<svg className={props.className} style={props.style} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40" height="40" width="40">
|
||||
<svg ref={ref} {...props} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40" height="40" width="40">
|
||||
<path fill="currentColor" d="M13.417 38V33.75H16.875V29.875H6.708Q4.917 29.875 3.687 28.646Q2.458 27.417 2.458 25.667V7.25Q2.458 5.5 3.687 4.271Q4.917 3.042 6.708 3.042H33.375Q35.125 3.042 36.375 4.271Q37.625 5.5 37.625 7.25V25.667Q37.625 27.417 36.375 28.646Q35.125 29.875 33.375 29.875H23.167V33.75H26.667V38ZM6.708 25.667H33.375Q33.375 25.667 33.375 25.667Q33.375 25.667 33.375 25.667V7.25Q33.375 7.25 33.375 7.25Q33.375 7.25 33.375 7.25H6.708Q6.708 7.25 6.708 7.25Q6.708 7.25 6.708 7.25V25.667Q6.708 25.667 6.708 25.667Q6.708 25.667 6.708 25.667ZM6.708 25.667Q6.708 25.667 6.708 25.667Q6.708 25.667 6.708 25.667V7.25Q6.708 7.25 6.708 7.25Q6.708 7.25 6.708 7.25Q6.708 7.25 6.708 7.25Q6.708 7.25 6.708 7.25V25.667Q6.708 25.667 6.708 25.667Q6.708 25.667 6.708 25.667Z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { CSSProperties } from "react";
|
||||
import { createSvgIcon } from "../svg-icon.type";
|
||||
|
||||
export default function EditIcon(props: { className?: string; style?: CSSProperties }) {
|
||||
export const EditIcon = createSvgIcon((props, ref) => {
|
||||
return (
|
||||
<svg className={props.className} style={props.style} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40" height="40" width="40">
|
||||
<svg ref={ref} {...props} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40" height="40" width="40">
|
||||
<path fill="currentColor" d="M7.833 32.25h1.875L26.75 15.208l-1.875-1.875L7.833 30.375Zm24.959-19.042-5.959-5.916 1.917-1.917q.833-.833 2.042-.833 1.208 0 2.083.875l1.833 1.833q.875.833.854 2.021-.02 1.187-.854 2.021ZM6.542 35.125q-.667 0-1.125-.458-.459-.459-.459-1.125v-3.709q0-.333.104-.604.105-.271.355-.521L24.875 9.25l5.958 5.958-19.458 19.459q-.25.25-.542.354-.291.104-.583.104Zm19.25-20.833-.917-.959 1.875 1.875Z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { HTMLProps } from "react";
|
||||
import { createSvgIcon } from "../svg-icon.type";
|
||||
|
||||
export function OculusIcon(props: HTMLProps<SVGSVGElement>) {
|
||||
export const OculusIcon = createSvgIcon((props, ref) => {
|
||||
return (
|
||||
<svg {...props} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
||||
<svg ref={ref} {...props} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M24.182 18.599c-0.427 0.297-0.901 0.474-1.411 0.552-0.51 0.083-1.016 0.068-1.521 0.068h-10.5c-0.51 0-1.016 0.021-1.526-0.068-0.51-0.083-0.979-0.255-1.411-0.552-0.854-0.604-1.365-1.563-1.365-2.604 0-1.057 0.516-2.016 1.37-2.599 0.417-0.297 0.896-0.479 1.396-0.557 0.5-0.083 1-0.083 1.526-0.083h10.5c0.5 0 1.016-0.016 1.516 0.063s0.984 0.26 1.401 0.542c0.865 0.578 1.365 1.557 1.365 2.599 0 1.036-0.526 2-1.38 2.599zM28.411 8.526c-1.125-0.906-2.417-1.531-3.818-1.865-0.802-0.193-1.604-0.281-2.432-0.307-0.599-0.021-1.198-0.010-1.818-0.010h-8.661c-0.609 0-1.224-0.010-1.833 0.010-0.823 0.026-1.63 0.109-2.432 0.307-1.401 0.339-2.698 0.964-3.818 1.865-2.281 1.823-3.599 4.568-3.599 7.474 0 2.911 1.318 5.656 3.583 7.474 1.13 0.906 2.422 1.531 3.823 1.87 0.802 0.193 1.609 0.281 2.432 0.302 0.599 0.021 1.198 0.016 1.818 0.016h8.661c0.599 0 1.219 0.005 1.818-0.016 0.823-0.021 1.62-0.109 2.417-0.302 1.401-0.344 2.682-0.969 3.823-1.87 2.307-1.823 3.625-4.568 3.625-7.474s-1.318-5.656-3.589-7.474z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
})
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import { createSvgIcon } from "../svg-icon.type";
|
||||
|
||||
export const PinIcon = createSvgIcon((props, ref) => {
|
||||
return (
|
||||
<svg ref={ref} {...props} xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" fill="currentColor">
|
||||
<path d="M648.13-763.11v274.74l66.57 66.57q6.71 6.71 10.07 15.05 3.36 8.34 3.36 17.53v26.59q0 19.15-13.17 32.33-13.18 13.17-32.33 13.17H525.5v232.09q0 19.15-13.17 32.32Q499.15-39.54 480-39.54t-32.33-13.18Q434.5-65.89 434.5-85.04v-232.09H277.37q-19.15 0-32.33-13.17-13.17-13.18-13.17-32.33v-26.59q0-9.19 3.36-17.53 3.36-8.34 10.07-15.05l66.57-66.57v-274.74q-17-1.67-28.5-14.25-11.5-12.57-11.5-30.29 0-19.15 13.17-32.33 13.18-13.17 32.33-13.17h325.26q19.15 0 32.33 13.17 13.17 13.18 13.17 32.33 0 17.72-11.5 30.29-11.5 12.58-28.5 14.25Z"/>
|
||||
</svg>
|
||||
)
|
||||
});
|
||||
@@ -1,9 +1,9 @@
|
||||
import { CSSProperties } from "react";
|
||||
import { createSvgIcon } from "../svg-icon.type";
|
||||
|
||||
export function TerminalIcon(props: { className?: string; style?: CSSProperties }) {
|
||||
export const TerminalIcon = createSvgIcon((props, ref) => {
|
||||
return (
|
||||
<svg className={props.className} style={props.style} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40" height="40" width="40">
|
||||
<svg ref={ref} {...props} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40" height="40" width="40">
|
||||
<path fill="currentColor" d="M6.333 33.375Q5.125 33.375 4.229 32.479Q3.333 31.583 3.333 30.333V9.667Q3.333 8.417 4.229 7.521Q5.125 6.625 6.333 6.625H33.667Q34.875 6.625 35.771 7.521Q36.667 8.417 36.667 9.667V30.333Q36.667 31.583 35.771 32.479Q34.875 33.375 33.667 33.375ZM6.333 30.333H33.667Q33.667 30.333 33.667 30.333Q33.667 30.333 33.667 30.333V13.042H6.333V30.333Q6.333 30.333 6.333 30.333Q6.333 30.333 6.333 30.333ZM20.375 28V25.375H29.667V28ZM12.5 27.875 10.667 26.042 14.958 21.708 10.625 17.375 12.5 15.5 18.667 21.708Z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
})
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import { createSvgIcon } from "../svg-icon.type"
|
||||
|
||||
export const UnpinIcon = createSvgIcon((props, ref) => {
|
||||
return (
|
||||
<svg ref={ref} {...props} xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" fill="currentColor">
|
||||
<path d="M570.57-514.15 294.3-790.65q-5.47-5.72-8.21-12.2-2.74-6.48-2.74-12.95 0-14.2 10.07-25.78 10.08-11.57 26.51-11.57h322.7q19.15 0 32.33 13.77 13.17 13.77 13.17 37.23 0 16.24-13.78 23.93-13.78 7.7-26.22 16.07v215.41q0 30.59-27.97 42.4-27.96 11.82-49.59-9.81ZM434.5-78.09v-239.04H290.98q-27.39 0-44.07-19.05-16.67-19.06-16.67-43.21 0-11.72 4.62-23.32 4.62-11.59 14.86-22.07l62.15-63.59v-46.72L74.91-774.28Q63.2-786 62.93-803.1q-.26-17.1 12.22-29.57 11.72-11.72 29.2-11.72 17.48 0 29.19 11.72l690.72 690.71q12.48 12.48 12.1 29.58-.38 17.1-12.86 29.58-12.48 11.71-29.32 12.09-16.83.38-29.31-12.09L530.3-317.13l-4.8.48v238.56q0 19.16-13.17 32.33Q499.15-32.59 480-32.59t-32.33-13.17Q434.5-58.93 434.5-78.09Z"/>
|
||||
</svg>
|
||||
)
|
||||
})
|
||||
@@ -11,28 +11,59 @@ import "./title-bar.component.css";
|
||||
import { useService } from "renderer/hooks/use-service.hook";
|
||||
import { lastValueFrom } from "rxjs";
|
||||
import { useWindowControls } from "renderer/hooks/use-window-controls.hook";
|
||||
import { useTranslationV2 } from "renderer/hooks/use-translation.hook";
|
||||
|
||||
function TitleBarTags() {
|
||||
const ipcService = useService(IpcService);
|
||||
const t = useTranslationV2();
|
||||
|
||||
const [previewVersion, setPreviewVersion] = useState("");
|
||||
const [outdated, setOutdated] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const requests: Promise<any>[] = [
|
||||
lastValueFrom(ipcService.sendV2("current-version"))
|
||||
];
|
||||
if (window.electron.platform === "linux") {
|
||||
requests.push(lastValueFrom(ipcService.sendV2("check-update")));
|
||||
}
|
||||
|
||||
Promise.all(requests).then(([ currentVersion, outdated ]) => {
|
||||
handlePrerelease(currentVersion);
|
||||
setOutdated(outdated);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const handlePrerelease = (version: string) => {
|
||||
if (version.toLowerCase().includes("alpha")) {
|
||||
return setPreviewVersion("ALPHA");
|
||||
}
|
||||
if (version.toLowerCase().includes("beta")) {
|
||||
return setPreviewVersion("BETA");
|
||||
}
|
||||
}
|
||||
|
||||
return <>
|
||||
{previewVersion &&
|
||||
<span className="bg-main-color-1 text-white dark:text-black dark:bg-white rounded-full ml-1 text-[10px] italic px-1 uppercase h-3.5 font-bold">
|
||||
{previewVersion}
|
||||
</span>
|
||||
}
|
||||
{outdated &&
|
||||
<span className="bg-warning-500 text-black rounded-full ml-1 text-[10px] italic px-1 uppercase h-3.5 font-bold">
|
||||
{t.text("title-bar.outdated")}
|
||||
</span>
|
||||
}
|
||||
</>;
|
||||
}
|
||||
|
||||
export default function TitleBar({ template = "index.html" }: { template: AppWindow }) {
|
||||
const ipcService = useService(IpcService);
|
||||
const audio = useService(AudioPlayerService);
|
||||
const windowControls = useWindowControls();
|
||||
|
||||
const volume = useObservable(() => audio.volume$, audio.volume);
|
||||
const color = useThemeColor("first-color");
|
||||
|
||||
const [previewVersion, setPreviewVersion] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
lastValueFrom(ipcService.sendV2("current-version")).then(version => {
|
||||
if (version.toLowerCase().includes("alpha")) {
|
||||
return setPreviewVersion("ALPHA");
|
||||
}
|
||||
if (version.toLowerCase().includes("beta")) {
|
||||
return setPreviewVersion("BETA");
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
const [maximized, setMaximized] = useState(false);
|
||||
|
||||
const closeWindow = () => {
|
||||
@@ -76,7 +107,7 @@ export default function TitleBar({ template = "index.html" }: { template: AppWin
|
||||
<div id="drag-region" className="grow basis-0 h-full">
|
||||
<div id="window-title" className="pl-1">
|
||||
<span className="text-gray-800 dark:text-gray-100 font-bold text-xs italic">BSManager</span>
|
||||
{previewVersion && <span className="bg-main-color-1 text-white dark:text-black dark:bg-white rounded-full ml-1 text-[10px] italic px-1 uppercase h-3.5 font-bold">{previewVersion}</span>}
|
||||
<TitleBarTags />
|
||||
</div>
|
||||
</div>
|
||||
<div id="window-controls" className="h-full flex shrink-0 items-center">
|
||||
|
||||
+11
-10
@@ -1,33 +1,34 @@
|
||||
import Tippy from "@tippyjs/react";
|
||||
import { GlowEffect } from "renderer/components/shared/glow-effect.component";
|
||||
import { BsmIcon, BsmIconType } from "renderer/components/svgs/bsm-icon.component";
|
||||
import { useTranslation } from "renderer/hooks/use-translation.hook";
|
||||
import { BsmIcon } from "renderer/components/svgs/bsm-icon.component";
|
||||
import { SvgIcon } from "renderer/components/svgs/svg-icon.type";
|
||||
import { useTranslationV2 } from "renderer/hooks/use-translation.hook";
|
||||
|
||||
type Props = {
|
||||
onClick: (active: boolean) => void;
|
||||
active: boolean;
|
||||
text: string;
|
||||
icon: BsmIconType;
|
||||
icon?: SvgIcon;
|
||||
infoText?: string;
|
||||
};
|
||||
|
||||
export function LaunchModToogle({ onClick, active, text, icon, infoText }: Props) {
|
||||
const t = useTranslation();
|
||||
export function LaunchModToogle({ onClick, active, text, icon: Icon, infoText }: Props) {
|
||||
const { text: t } = useTranslationV2();
|
||||
|
||||
return (
|
||||
<div className={`relative rounded-full cursor-pointer group active:scale-95 transition-transform ${!active && "shadow-md shadow-black"}`} onClick={() => onClick(!active)}>
|
||||
<button className={`shrink-0 relative rounded-full cursor-pointer group active:scale-95 transition-transform ${!active && "shadow-md shadow-black"}`} onClick={() => onClick(!active)}>
|
||||
<GlowEffect visible={active} className="absolute !rounded-full blur-[2px]" />
|
||||
<div className="w-full h-full px-6 flex gap-1.5 justify-center items-center bg-light-main-color-2 dark:bg-main-color-2 p-3 rounded-full text-gray-800 dark:text-white group-hover:bg-light-main-color-1 dark:group-hover:bg-main-color-1">
|
||||
<BsmIcon icon={icon} className="h-7 text-gray-800 dark:text-white" />
|
||||
{Icon && <Icon className="h-7 shrink-0 text-gray-800 dark:text-white" />}
|
||||
<span className="w-fit min-w-fit text-lg font-bold uppercase tracking-wide italic ">{t(text)}</span>
|
||||
{infoText && (
|
||||
<Tippy content={t(infoText)} placement="bottom" delay={[400, 0]}>
|
||||
<div className="h-[25px] w-[25px] p-1.5 rounded-full cursor-help bg-light-main-color-1 dark:bg-main-color-3 hover:brightness-110">
|
||||
<Tippy content={t(infoText)} className="break-words" placement="top" theme="default" delay={[200, 0]}>
|
||||
<div className="h-[25px] w-[25px] shrink-0 p-1.5 rounded-full cursor-help bg-light-main-color-1 dark:bg-main-color-3 hover:brightness-110">
|
||||
<BsmIcon className="w-full h-full" icon="info" />
|
||||
</div>
|
||||
</Tippy>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
import Tippy from "@tippyjs/react";
|
||||
import { BsmCheckbox } from "renderer/components/shared/bsm-checkbox.component";
|
||||
import { PinIcon } from "renderer/components/svgs/icons/pin-icon.component";
|
||||
import { UnpinIcon } from "renderer/components/svgs/icons/unpin-icon.component";
|
||||
import { SvgIcon } from "renderer/components/svgs/svg-icon.type";
|
||||
import { cn } from "renderer/helpers/css-class.helpers";
|
||||
import { useTranslationV2 } from "renderer/hooks/use-translation.hook";
|
||||
import { LaunchMod } from "shared/models/bs-launch/launch-option.interface";
|
||||
|
||||
type Props = {
|
||||
readonly className?: string;
|
||||
readonly open: boolean;
|
||||
readonly launchArgs?: string;
|
||||
readonly launchMods?: LaunchModItemProps[];
|
||||
readonly onLaunchArgsChange?: (args: string) => void;
|
||||
};
|
||||
|
||||
export function LaunchOptionsPanel({ className, open, launchArgs, launchMods, onLaunchArgsChange }: Props) {
|
||||
|
||||
const {text: t} = useTranslationV2();
|
||||
|
||||
return (
|
||||
<div className={cn("grid grid-rows-[0fr] transition-[grid-template-rows] bg-theme-2 rounded-md shadow-md shadow-black overflow-hidden", open && "!grid-rows-[1fr]", className)}>
|
||||
<div className="flex flex-col overflow-y-scroll scrollbar-default">
|
||||
<div className="p-3.5">
|
||||
<input className="h-10 w-full rounded-md bg-theme-1 text-center mb-2" type="text" placeholder={t("pages.version-viewer.launch-mods.advanced-launch.placeholder")} value={launchArgs} onChange={e => onLaunchArgsChange(e.target.value)}/>
|
||||
<div className={cn("flex gap-2 flex-wrap")}>
|
||||
{launchMods?.map((mod) => (
|
||||
<LaunchModItem key={mod.id} {...mod}/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export type LaunchModItemProps = {
|
||||
readonly id: LaunchMod;
|
||||
readonly icon?: SvgIcon;
|
||||
readonly label: string;
|
||||
readonly description: string;
|
||||
readonly active: boolean;
|
||||
readonly visible?: boolean;
|
||||
readonly pinned?: boolean;
|
||||
readonly onChange?: (val: boolean) => void;
|
||||
readonly onPinChange?: (val: boolean) => void;
|
||||
}
|
||||
|
||||
export function LaunchModItem({ id, icon: Icon, label, description, active, visible, pinned, onChange, onPinChange }: LaunchModItemProps) {
|
||||
|
||||
const { text: t } = useTranslationV2();
|
||||
|
||||
return (
|
||||
<Tippy theme="default" className="break-words" placement="top" content={description}>
|
||||
<button id={id} className={cn("grow rounded-md bg-theme-1 relative flex justify-center items-center h-10 py-1 px-3", visible === false && "hidden")} onClick={e => { e.preventDefault(); e.stopPropagation(); onChange?.(!active) }}>
|
||||
<BsmCheckbox className="h-4 aspect-square z-[1] relative mr-1.5" checked={active} onChange={onChange}/>
|
||||
{Icon && <Icon className="h-full w-fit py-0.5 mr-1.5"/>}
|
||||
<span className="font-bold">{label}</span>
|
||||
{onPinChange && (
|
||||
<Tippy theme="default" placement="right" content={pinned ? t("misc.unpin") : t("misc.pin")} hideOnClick>
|
||||
<button className="h-full py-2 px-1.5" onClick={e => { e.preventDefault(); e.stopPropagation(); onPinChange?.(!pinned) }}>
|
||||
{pinned ? <UnpinIcon className="size-full"/> : <PinIcon className="size-full"/>}
|
||||
</button>
|
||||
</Tippy>
|
||||
)}
|
||||
</button>
|
||||
</Tippy>
|
||||
);
|
||||
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
import { ChangeEvent, useEffect, useMemo, useState } from "react";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { BsmButton } from "renderer/components/shared/bsm-button.component";
|
||||
import { useObservable } from "renderer/hooks/use-observable.hook";
|
||||
import { useTranslationV2 } from "renderer/hooks/use-translation.hook";
|
||||
import { BSLauncherService, LaunchMods } from "renderer/services/bs-launcher.service";
|
||||
import { BSLauncherService } from "renderer/services/bs-launcher.service";
|
||||
import { ConfigurationService } from "renderer/services/configuration.service";
|
||||
import { BSVersion } from "shared/bs-version.interface";
|
||||
import { LaunchModToogle } from "./launch-mod-toogle.component";
|
||||
@@ -19,6 +19,13 @@ import { BSVersionManagerService } from "renderer/services/bs-version-manager.se
|
||||
import { safeLt } from "shared/helpers/semver.helpers";
|
||||
import { WarningIcon } from "renderer/components/svgs/icons/warning-icon.component";
|
||||
import Tippy from "@tippyjs/react";
|
||||
import { LaunchModItemProps, LaunchOptionsPanel } from "./launch-options-panel.component";
|
||||
import { LaunchMod, LaunchMods } from "shared/models/bs-launch/launch-option.interface";
|
||||
import { OculusIcon } from "renderer/components/svgs/icons/oculus-icon.component";
|
||||
import { DesktopIcon } from "renderer/components/svgs/icons/desktop-icon.component";
|
||||
import { TerminalIcon } from "renderer/components/svgs/icons/terminal-icon.component";
|
||||
import { DefaultConfigKey } from "renderer/config/default-configuration.config";
|
||||
import { EditIcon } from "renderer/components/svgs/icons/edit-icon.component";
|
||||
|
||||
type Props = { version: BSVersion };
|
||||
|
||||
@@ -30,12 +37,11 @@ export function LaunchSlide({ version }: Props) {
|
||||
const bsDownloader = useService(BsDownloaderService);
|
||||
const versions = useService(BSVersionManagerService);
|
||||
|
||||
const [oculusMode, setOculusMode] = useState(!!configService.get<boolean>(LaunchMods.OCULUS_MOD));
|
||||
const [desktopMode, setDesktopMode] = useState(!!configService.get<boolean>(LaunchMods.DESKTOP_MOD));
|
||||
const [debugMode, setDebugMode] = useState(!!configService.get<boolean>(LaunchMods.DEBUG_MOD));
|
||||
const [advancedLaunch, setAdvancedLaunch] = useState(false);
|
||||
const [additionalArgsString, setAdditionalArgsString] = useState<string>(configService.get<string>("additionnal-args") || "");
|
||||
const versionDownloading = useObservable(() => bsDownloader.downloadingVersion$);
|
||||
const [activeLaunchMods, setActiveLaunchMods] = useState<LaunchMod[]>(configService.get("launch-mods") ?? []);
|
||||
const [pinnedLaunchMods, setPinnedLaunchMods] = useState<LaunchMod[]>(configService.get("pinned-launch-mods" as DefaultConfigKey) ?? []);
|
||||
|
||||
const versionRunning = useObservable(() => bsLauncherService.versionRunning$);
|
||||
|
||||
@@ -44,32 +50,107 @@ export function LaunchSlide({ version }: Props) {
|
||||
}, [additionalArgsString]);
|
||||
|
||||
useEffect(() => {
|
||||
if(desktopMode){ return; }
|
||||
bsLauncherService.restoreSteamVR();
|
||||
}, [desktopMode]);
|
||||
configService.set("pinned-launch-mods", pinnedLaunchMods);
|
||||
}, [pinnedLaunchMods])
|
||||
|
||||
const setMode = (mode: LaunchMods, value: boolean) => {
|
||||
useEffect(() => {
|
||||
configService.set("launch-mods", activeLaunchMods);
|
||||
|
||||
if (mode === LaunchMods.DEBUG_MOD) {
|
||||
setDebugMode(value);
|
||||
} else if (mode === LaunchMods.OCULUS_MOD) {
|
||||
setOculusMode(value);
|
||||
} else if (mode === LaunchMods.DESKTOP_MOD) {
|
||||
setDesktopMode(value);
|
||||
if(!activeLaunchMods.includes("fpfc")){
|
||||
bsLauncherService.restoreSteamVR();
|
||||
}
|
||||
configService.set(mode, value);
|
||||
};
|
||||
}, [activeLaunchMods]);
|
||||
|
||||
const handleAdditionalArgsChange = (e: ChangeEvent<HTMLInputElement>) => setAdditionalArgsString(() => e.target.value);
|
||||
const toggleActiveLaunchMod = (checked: boolean, launchMod: LaunchMod) => checked
|
||||
? setActiveLaunchMods(prev => [...prev, launchMod])
|
||||
: setActiveLaunchMods(prev => prev.filter(mod => mod !== launchMod));
|
||||
|
||||
const launch = () => {
|
||||
const togglePinnedLaunchMod = (pinned: boolean, launchMod: LaunchMod) => pinned
|
||||
? setPinnedLaunchMods(prev => [...prev, launchMod])
|
||||
: setPinnedLaunchMods(prev => prev.filter(mod => mod !== launchMod));
|
||||
|
||||
const launchModItems = useMemo<LaunchModItemProps[]>(() => {
|
||||
|
||||
let protonLogsPath: string[] = [];
|
||||
if (window.electron.platform === "linux") {
|
||||
protonLogsPath = version.steam
|
||||
? [version.path, "Logs"]
|
||||
: ["BSInstances", version.name, "Logs"];
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
id: LaunchMods.OCULUS,
|
||||
icon: OculusIcon,
|
||||
label: t("pages.version-viewer.launch-mods.oculus"),
|
||||
description: t("pages.version-viewer.launch-mods.oculus-description"),
|
||||
active: activeLaunchMods.includes(LaunchMods.OCULUS),
|
||||
visible: !(version.metadata?.store === BsStore.OCULUS),
|
||||
pinned: pinnedLaunchMods.includes(LaunchMods.OCULUS),
|
||||
onChange: (checked) => toggleActiveLaunchMod(checked, LaunchMods.OCULUS),
|
||||
onPinChange: (pinned) => togglePinnedLaunchMod(pinned, LaunchMods.OCULUS),
|
||||
},
|
||||
{
|
||||
id: LaunchMods.FPFC,
|
||||
icon: DesktopIcon,
|
||||
label: t("pages.version-viewer.launch-mods.desktop"),
|
||||
description: t("pages.version-viewer.launch-mods.desktop-description"),
|
||||
active: activeLaunchMods.includes(LaunchMods.FPFC),
|
||||
pinned: pinnedLaunchMods.includes(LaunchMods.FPFC),
|
||||
onChange: (checked) => toggleActiveLaunchMod(checked, LaunchMods.FPFC),
|
||||
onPinChange: (pinned) => togglePinnedLaunchMod(pinned, LaunchMods.FPFC),
|
||||
},
|
||||
{
|
||||
id: LaunchMods.DEBUG,
|
||||
icon: TerminalIcon,
|
||||
label: t("pages.version-viewer.launch-mods.debug"),
|
||||
description: t("pages.version-viewer.launch-mods.debug-description"),
|
||||
active: activeLaunchMods.includes(LaunchMods.DEBUG),
|
||||
pinned: pinnedLaunchMods.includes(LaunchMods.DEBUG),
|
||||
onChange: (checked) => toggleActiveLaunchMod(checked, LaunchMods.DEBUG),
|
||||
onPinChange: (pinned) => togglePinnedLaunchMod(pinned, LaunchMods.DEBUG),
|
||||
},
|
||||
{
|
||||
id: LaunchMods.SKIP_STEAM,
|
||||
label: t("pages.version-viewer.launch-mods.skipsteam"),
|
||||
description: t("pages.version-viewer.launch-mods.skipsteam-description"),
|
||||
active: activeLaunchMods.includes(LaunchMods.SKIP_STEAM),
|
||||
pinned: pinnedLaunchMods.includes(LaunchMods.SKIP_STEAM),
|
||||
onChange: (checked) => toggleActiveLaunchMod(checked, LaunchMods.SKIP_STEAM),
|
||||
onPinChange: (pinned) => togglePinnedLaunchMod(pinned, LaunchMods.SKIP_STEAM),
|
||||
},
|
||||
{
|
||||
id: LaunchMods.EDITOR,
|
||||
icon: EditIcon,
|
||||
label: t("pages.version-viewer.launch-mods.map-editor"),
|
||||
description: t("pages.version-viewer.launch-mods.map-editor-description"),
|
||||
active: activeLaunchMods.includes(LaunchMods.EDITOR),
|
||||
pinned: pinnedLaunchMods.includes(LaunchMods.EDITOR),
|
||||
visible: !safeLt(version.BSVersion, "1.23.0"),
|
||||
onChange: (checked) => toggleActiveLaunchMod(checked, LaunchMods.EDITOR),
|
||||
onPinChange: (pinned) => togglePinnedLaunchMod(pinned, LaunchMods.EDITOR),
|
||||
},
|
||||
{
|
||||
id: LaunchMods.PROTON_LOGS,
|
||||
label: t("pages.version-viewer.launch-mods.proton-logs"),
|
||||
description: t("pages.version-viewer.launch-mods.proton-logs-description", {
|
||||
versionPath: `${window.electron.path.join(...protonLogsPath)}/`
|
||||
}),
|
||||
active: activeLaunchMods.includes(LaunchMods.PROTON_LOGS),
|
||||
pinned: pinnedLaunchMods.includes(LaunchMods.PROTON_LOGS),
|
||||
visible: window.electron.platform === "linux",
|
||||
onChange: (checked) => toggleActiveLaunchMod(checked, LaunchMods.PROTON_LOGS),
|
||||
onPinChange: (pinned) => togglePinnedLaunchMod(pinned, LaunchMods.PROTON_LOGS),
|
||||
},
|
||||
]
|
||||
}, [activeLaunchMods, pinnedLaunchMods, version]);
|
||||
|
||||
const launch = async () => {
|
||||
const additionalArgs = additionalArgsString?.split(";").map(arg => arg.trim()).filter(arg => arg.length > 0);
|
||||
|
||||
const launch$ = bsLauncherService.launch({
|
||||
version,
|
||||
oculus: version.oculus ? false : oculusMode,
|
||||
desktop: desktopMode,
|
||||
debug: debugMode,
|
||||
launchMods: activeLaunchMods,
|
||||
additionalArgs: advancedLaunch ? additionalArgs : [],
|
||||
});
|
||||
|
||||
@@ -81,9 +162,11 @@ export function LaunchSlide({ version }: Props) {
|
||||
}, [version]);
|
||||
|
||||
return (
|
||||
<div className="w-full shrink-0 items-center relative flex flex-col justify-start">
|
||||
<div className="flex flex-col gap-3 justify-center items-center mb-5">
|
||||
<BsmImage className="relative object-cover h-28" image={BSLogo} />
|
||||
<div className="w-full shrink-0 items-center relative flex flex-col justify-start overflow-hidden">
|
||||
<div className="flex flex-col gap-3 justify-center items-center mb-4">
|
||||
<div className="h-24 flex justify-center items-center">
|
||||
<BsmImage className="relative object-cover h-28" image={BSLogo} />
|
||||
</div>
|
||||
<h1 className="relative text-4xl font-bold italic -top-3">
|
||||
{version.name ? `${version.BSVersion} - ${version.name}` : version.BSVersion}
|
||||
{isOutdated && (
|
||||
@@ -95,14 +178,23 @@ export function LaunchSlide({ version }: Props) {
|
||||
)}
|
||||
</h1>
|
||||
</div>
|
||||
<div className="grid grid-flow-col gap-6">
|
||||
{!(version.oculus || version.metadata?.store === BsStore.OCULUS) && <LaunchModToogle infoText="pages.version-viewer.launch-mods.oculus-description" icon="oculus" onClick={() => setMode(LaunchMods.OCULUS_MOD, !oculusMode)} active={oculusMode} text="pages.version-viewer.launch-mods.oculus" />}
|
||||
<LaunchModToogle infoText="pages.version-viewer.launch-mods.desktop-description" icon="desktop" onClick={() => setMode(LaunchMods.DESKTOP_MOD, !desktopMode)} active={desktopMode} text="pages.version-viewer.launch-mods.desktop" />
|
||||
<LaunchModToogle infoText="pages.version-viewer.launch-mods.debug-description" icon="terminal" onClick={() => setMode(LaunchMods.DEBUG_MOD, !debugMode)} active={debugMode} text="pages.version-viewer.launch-mods.debug" />
|
||||
<div className="w-full flex justify-center items-center gap-4 flex-wrap scrollbar-default">
|
||||
{pinnedLaunchMods.map(id => launchModItems.find(mod => mod.id === id)).map(launchMod => {
|
||||
if(launchMod?.visible === false || !launchMod?.pinned) { return undefined; }
|
||||
return (
|
||||
<LaunchModToogle
|
||||
icon={launchMod.icon}
|
||||
infoText={launchMod.description}
|
||||
text={launchMod.label}
|
||||
active={launchMod.active}
|
||||
onClick={() => launchMod.onChange(!launchMod.active)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div className="pt-4 w-2/3 flex flex-col items-center gap-3">
|
||||
<div className="mt-4 flex flex-col items-center justify-center gap-3">
|
||||
<div className="relative">
|
||||
<GlowEffect className="!rounded-full" visible={!!(advancedLaunch && additionalArgsString)}/>
|
||||
<GlowEffect className="!rounded-full" visible={!!(activeLaunchMods?.length || additionalArgsString)}/>
|
||||
<BsmButton
|
||||
className={cn("rounded-full w-fit text-lg py-1 px-7 bg-theme-2 text-gray-800 dark:text-white", (advancedLaunch && additionalArgsString) ? "" : "shadow-md shadow-black")}
|
||||
text="pages.version-viewer.launch-mods.advanced-launch.button"
|
||||
@@ -113,15 +205,13 @@ export function LaunchSlide({ version }: Props) {
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="bg-light-main-color-2 dark:bg-main-color-2 h-9 rounded-full overflow-hidden flex items-center justify-center transition-all duration-100 ease-in-out w-full origin-top shadow-black shadow-sm" style={{ scale: advancedLaunch ? "100% 100%" : "0 0" }}>
|
||||
<input className="w-[calc(100%-12px)] h-[calc(100%-12px)] bg-light-main-color-1 dark:bg-main-color-1 text-black dark:text-white rounded-full outline-none text-center" type="text" placeholder={t("pages.version-viewer.launch-mods.advanced-launch.placeholder")} value={additionalArgsString} onChange={handleAdditionalArgsChange} />
|
||||
</div>
|
||||
</div>
|
||||
<div className='grow flex justify-center items-center'>
|
||||
<LaunchOptionsPanel open={advancedLaunch} launchMods={launchModItems} launchArgs={additionalArgsString} className="w-full max-w-3xl mt-3" onLaunchArgsChange={setAdditionalArgsString}/>
|
||||
<div className='grow flex justify-center items-center p-2'>
|
||||
<BsmButton
|
||||
onClick={launch}
|
||||
active={JSON.stringify(version) === JSON.stringify(versionRunning)}
|
||||
className='relative -translate-y-1/2 text-5xl text-gray-800 dark:text-gray-200 font-bold tracking-wide pt-1 pb-3 px-7 rounded-lg shadow-md italic shadow-black active:scale-90 transition-transform'
|
||||
className='relative text-5xl text-gray-800 dark:text-gray-200 font-bold tracking-wide pt-1 pb-3 px-7 rounded-lg shadow-md italic shadow-black active:scale-90 transition-transform'
|
||||
text="misc.launch"
|
||||
disabled={equal(version, versionDownloading)}
|
||||
/>
|
||||
@@ -129,5 +219,3 @@ export function LaunchSlide({ version }: Props) {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -17,9 +17,10 @@ type Props = {
|
||||
uninstallMod?: (mods: Mod) => void;
|
||||
uninstallAllMods?: () => void;
|
||||
unselectAllMods?: () => void;
|
||||
openModsDropZone?: () => void;
|
||||
};
|
||||
|
||||
export function ModsGrid({ modsMap, installed, modsSelected, onModChange, moreInfoMod, onWantInfos, disabled, uninstallMod, uninstallAllMods, unselectAllMods }: Props) {
|
||||
export function ModsGrid({ modsMap, installed, modsSelected, onModChange, moreInfoMod, onWantInfos, disabled, uninstallMod, uninstallAllMods, unselectAllMods, openModsDropZone }: Props) {
|
||||
|
||||
const [filter, setFilter] = useState("");
|
||||
const [filterEnabled, setFilterEnabled] = useState(false);
|
||||
@@ -68,6 +69,7 @@ export function ModsGrid({ modsMap, installed, modsSelected, onModChange, moreIn
|
||||
<span className="z-10 sticky flex items-center justify-center top-0 bg-inherit border-b-2 border-main-color-1 h-8 whitespace-nowrap">{t("pages.version-viewer.mods.mods-grid.header-bar.description")}</span>
|
||||
<span className="z-10 sticky top-0 bg-inherit border-b-2 border-main-color-1 h-8 flex justify-start items-center py-1 pl-[3px] min-w-[50px]">
|
||||
<BsmDropdownButton className="h-full aspect-square relative rounded-full bg-light-main-color-1 dark:bg-main-color-3" withBar={false} icon="three-dots" buttonClassName="!rounded-full !p-[2px] !bg-light-main-color-2 dark:!bg-main-color-2 hover:!bg-light-main-color-1 dark:hover:!bg-main-color-3" menuTranslationY="5px" items={[
|
||||
{ text: "pages.version-viewer.mods.mods-grid.header-bar.dropdown.import-mods", icon: "download", onClick: () => openModsDropZone?.() },
|
||||
{ text: "pages.version-viewer.mods.mods-grid.header-bar.dropdown.unselect-all", icon: "cancel", onClick: () => unselectAllMods?.() },
|
||||
{ text: "pages.version-viewer.mods.mods-grid.header-bar.dropdown.uninstall-all", icon: "trash", onClick: () => uninstallAllMods?.() }
|
||||
]} />
|
||||
|
||||
@@ -21,10 +21,13 @@ import { useService } from "renderer/hooks/use-service.hook";
|
||||
import { NotificationService } from "renderer/services/notification.service";
|
||||
import { noop } from "shared/helpers/function.helpers";
|
||||
import { UninstallAllModsModal } from "renderer/components/modal/modal-types/uninstall-all-mods-modal.component";
|
||||
import { Dropzone } from "renderer/components/shared/dropzone.component";
|
||||
|
||||
export function ModsSlide({ version, onDisclamerDecline }: { version: BSVersion; onDisclamerDecline: () => void }) {
|
||||
const ACCEPTED_DISCLAIMER_KEY = "accepted-mods-disclaimer";
|
||||
|
||||
const t = useTranslation();
|
||||
|
||||
const modsManager = useService(BsModsManagerService);
|
||||
const configService = useService(ConfigurationService);
|
||||
const notification = useService(NotificationService);
|
||||
@@ -42,6 +45,7 @@ export function ModsSlide({ version, onDisclamerDecline }: { version: BSVersion;
|
||||
const isOnline = useObservable(() => os.isOnline$);
|
||||
const [installing, setInstalling] = useState(false);
|
||||
const [uninstalling, setUninstalling] = useState(false);
|
||||
const [modsDropZoneOpen, setModsDropZoneOpen] = useState(false);
|
||||
|
||||
const downloadRef = useRef(null);
|
||||
const [downloadWith, setDownloadWidth] = useState(0);
|
||||
@@ -166,6 +170,11 @@ export function ModsSlide({ version, onDisclamerDecline }: { version: BSVersion;
|
||||
}).catch(noop).finally(() => setInstalling(() => false));
|
||||
};
|
||||
|
||||
const importMods = (files: string[]): void => {
|
||||
setModsDropZoneOpen(false);
|
||||
modsManager.importMods(files, version);
|
||||
};
|
||||
|
||||
const uninstallMod = (mod: Mod): void => {
|
||||
setUninstalling(() => true);
|
||||
lastValueFrom(modsManager.uninstallMod(mod, version)).catch(noop).finally(() => {
|
||||
@@ -192,7 +201,7 @@ export function ModsSlide({ version, onDisclamerDecline }: { version: BSVersion;
|
||||
setModsSelected(() => []);
|
||||
}
|
||||
|
||||
const loadMods = (): Promise<void> => {
|
||||
const loadMods = async (): Promise<void> => {
|
||||
if (os.isOffline) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
@@ -265,6 +274,7 @@ export function ModsSlide({ version, onDisclamerDecline }: { version: BSVersion;
|
||||
</ModStatus>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="grow overflow-y-scroll w-full min-h-0 scrollbar-default p-0 m-0">
|
||||
@@ -279,6 +289,7 @@ export function ModsSlide({ version, onDisclamerDecline }: { version: BSVersion;
|
||||
uninstallMod={uninstallMod}
|
||||
uninstallAllMods={uninstallAllMods}
|
||||
unselectAllMods={unselectAllMods}
|
||||
openModsDropZone={() => setModsDropZoneOpen(true)}
|
||||
/>
|
||||
</div>
|
||||
<div className="shrink-0 flex items-center justify-between px-3 py-2">
|
||||
@@ -297,7 +308,23 @@ export function ModsSlide({ version, onDisclamerDecline }: { version: BSVersion;
|
||||
|
||||
return (
|
||||
<div ref={ref} className="shrink-0 w-full h-full px-8 pb-7 flex justify-center">
|
||||
<div className="relative flex flex-col grow-0 bg-light-main-color-2 dark:bg-main-color-2 size-full rounded-md shadow-black shadow-center overflow-hidden">{renderContent()}</div>
|
||||
<Dropzone
|
||||
className="w-full h-full shrink-0"
|
||||
onFiles={importMods}
|
||||
text={t("pages.version-viewer.mods.drop-zone.text")}
|
||||
subtext={t("pages.version-viewer.mods.drop-zone.subtext")}
|
||||
open={modsDropZoneOpen}
|
||||
onClose={modsDropZoneOpen ? () => setModsDropZoneOpen(false) : undefined}
|
||||
filters={[
|
||||
{ name: ".zip", extensions: ["zip"] },
|
||||
{ name: ".dll", extensions: ["dll"] }
|
||||
]}
|
||||
dialogOptions={{ dialog: {
|
||||
properties: ["openFile", "multiSelections"],
|
||||
}}}
|
||||
>
|
||||
<div className="relative flex flex-col grow-0 bg-light-main-color-2 dark:bg-main-color-2 size-full rounded-md shadow-black shadow-center overflow-hidden">{renderContent()}</div>
|
||||
</Dropzone>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { LaunchMod, LaunchMods } from "shared/models/bs-launch/launch-option.interface";
|
||||
import { BsvSearchOrder } from "shared/models/maps/beat-saver.model";
|
||||
|
||||
// NOTE: To refactor. Rename to LocalStorageConfigKeyValues since these are stored in the
|
||||
@@ -12,6 +13,7 @@ export interface DefaultConfigKeyValues {
|
||||
"default-shared-folders": string[];
|
||||
"playlist-sort-order": BsvSearchOrder;
|
||||
"map-sort-order": BsvSearchOrder;
|
||||
"pinned-launch-mods": LaunchMod[];
|
||||
};
|
||||
|
||||
export type DefaultConfigKey = keyof DefaultConfigKeyValues;
|
||||
@@ -31,6 +33,8 @@ export const defaultConfiguration: {
|
||||
],
|
||||
"playlist-sort-order": BsvSearchOrder.Relevance,
|
||||
"map-sort-order": BsvSearchOrder.Relevance,
|
||||
"pinned-launch-mods": [LaunchMods.OCULUS, LaunchMods.DEBUG]
|
||||
};
|
||||
|
||||
export type ThemeConfig = "dark" | "light" | "os";
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ import Tippy from "@tippyjs/react";
|
||||
import { MapsManagerService } from "renderer/services/maps-manager.service";
|
||||
import { PlaylistsManagerService } from "renderer/services/playlists-manager.service";
|
||||
import { ModelsManagerService } from "renderer/services/models-management/models-manager.service";
|
||||
import { useTranslation } from "renderer/hooks/use-translation.hook";
|
||||
import { useTranslation, useTranslationV2 } from "renderer/hooks/use-translation.hook";
|
||||
import { VersionFolderLinkerService } from "renderer/services/version-folder-linker.service";
|
||||
import { useService } from "renderer/hooks/use-service.hook";
|
||||
import { lastValueFrom } from "rxjs";
|
||||
@@ -40,12 +40,13 @@ import { SteamIcon } from "renderer/components/svgs/icons/steam-icon.component";
|
||||
import { OculusIcon } from "renderer/components/svgs/icons/oculus-icon.component";
|
||||
import { BsDownloaderService } from "renderer/services/bs-version-download/bs-downloader.service";
|
||||
import BeatConflict from "../../../assets/images/apngs/beat-conflict.png";
|
||||
import { SettingToogleSwitchGrid } from "renderer/components/settings/setting-toogle-switch-grid.component";
|
||||
import { Item, SettingToogleSwitchGrid } from "renderer/components/settings/setting-toogle-switch-grid.component";
|
||||
import { BasicModal } from "renderer/components/modal/basic-modal.component";
|
||||
import { StaticConfigurationService } from "renderer/services/static-configuration.service";
|
||||
import { tryit } from "shared/helpers/error.helpers";
|
||||
import { InstallationLocationService } from "renderer/services/installation-location.service";
|
||||
import { AutoUpdaterService } from "renderer/services/auto-updater.service";
|
||||
import { OculusDownloaderService } from "renderer/services/bs-version-download/oculus-downloader.service";
|
||||
|
||||
export function SettingsPage() {
|
||||
|
||||
@@ -55,6 +56,7 @@ export function SettingsPage() {
|
||||
const modalService = useService(ModalService);
|
||||
const bsDownloader = useService(BsDownloaderService);
|
||||
const steamDownloader = useService(SteamDownloaderService);
|
||||
const oculusDownloader = useService(OculusDownloaderService);
|
||||
const progressBarService = useService(ProgressBarService);
|
||||
const notificationService = useService(NotificationService);
|
||||
const i18nService = useService(I18nService);
|
||||
@@ -96,8 +98,6 @@ export function SettingsPage() {
|
||||
const [playlistsDeepLinkEnabled, setPlaylistsDeepLinkEnabled] = useState(false);
|
||||
const [modelsDeepLinkEnabled, setModelsDeepLinkEnabled] = useState(false);
|
||||
const [hasDownloaderSession, setHasDownloaderSession] = useState(false);
|
||||
const [hardwareAccelerationEnabled, setHardwareAccelerationEnabled] = useState(true);
|
||||
const [useSymlink, setUseSymlink] = useState(false);
|
||||
const appVersion = useObservable(() => autoUpdater.getAppVersion());
|
||||
|
||||
useEffect(() => {
|
||||
@@ -107,8 +107,6 @@ export function SettingsPage() {
|
||||
playlistsManager.isDeepLinksEnabled().then(enabled => setPlaylistsDeepLinkEnabled(() => enabled));
|
||||
modelsManager.isDeepLinksEnabled().then(enabled => setModelsDeepLinkEnabled(() => enabled));
|
||||
|
||||
staticConfig.get("disable-hadware-acceleration").then(disabled =>setHardwareAccelerationEnabled(() => disabled !== true));
|
||||
staticConfig.get("use-symlinks").then(useSymlinks => setUseSymlink(() => useSymlinks));
|
||||
staticConfig.get("proton-folder").then(setProtonFolder);
|
||||
}, []);
|
||||
|
||||
@@ -125,16 +123,17 @@ export function SettingsPage() {
|
||||
});
|
||||
};
|
||||
|
||||
const loadDownloadersSession = () => {
|
||||
setHasDownloaderSession(steamDownloader.sessionExist());
|
||||
const loadDownloadersSession = async () => {
|
||||
setHasDownloaderSession(steamDownloader.sessionExist() || await oculusDownloader.metaSessionExists());
|
||||
}
|
||||
|
||||
const clearDownloadersSession = () => {
|
||||
const clearDownloadersSession = async () => {
|
||||
if (!hasDownloaderSession) {
|
||||
return;
|
||||
}
|
||||
|
||||
steamDownloader.deleteSteamSession();
|
||||
await oculusDownloader.deleteMetaSession()
|
||||
notificationService.notifyInfo({
|
||||
title: "pages.settings.steam-and-oculus.logout-success",
|
||||
});
|
||||
@@ -233,66 +232,6 @@ export function SettingsPage() {
|
||||
});
|
||||
};
|
||||
|
||||
const onChangeHardwareAcceleration = async (newHardwareAccelerationEnabled: boolean) => {
|
||||
if(newHardwareAccelerationEnabled === hardwareAccelerationEnabled){ return; }
|
||||
|
||||
const res = await modalService.openModal(BasicModal, { data: {
|
||||
title: "pages.settings.advanced.hardware-acceleration.modal.title",
|
||||
body: "pages.settings.advanced.hardware-acceleration.modal.body",
|
||||
image: BeatConflict,
|
||||
buttons: [
|
||||
{ id: "cancel", text: "misc.cancel", type: "cancel", isCancel: true },
|
||||
{ id: "confirm", text: "pages.settings.advanced.hardware-acceleration.modal.confirm-btn", type: "error" }
|
||||
]
|
||||
}});
|
||||
|
||||
if(res.exitCode !== ModalExitCode.COMPLETED || res.data !== "confirm"){ return; }
|
||||
|
||||
const { error } = await tryit(() => staticConfig.set("disable-hadware-acceleration", !newHardwareAccelerationEnabled));
|
||||
|
||||
if(error){
|
||||
notificationService.notifyError({ title: "notifications.types.error", desc: "pages.settings.advanced.hardware-acceleration.error-notification.message" });
|
||||
setHardwareAccelerationEnabled(() => !newHardwareAccelerationEnabled);
|
||||
return;
|
||||
}
|
||||
|
||||
setHardwareAccelerationEnabled(() => newHardwareAccelerationEnabled);
|
||||
|
||||
if(!progressBarService.require()){
|
||||
return;
|
||||
}
|
||||
|
||||
await lastValueFrom(ipcService.sendV2("restart-app"));
|
||||
};
|
||||
|
||||
const onChangeUseSymlinks = async (newUseSymlink: boolean) => {
|
||||
|
||||
if(newUseSymlink === useSymlink){ return; }
|
||||
|
||||
if(newUseSymlink){
|
||||
const res = await modalService.openModal(BasicModal, { data: {
|
||||
title: "pages.settings.advanced.use-symlinks.modal.title",
|
||||
body: "pages.settings.advanced.use-symlinks.modal.body",
|
||||
image: BeatConflict,
|
||||
buttons: [
|
||||
{ id: "cancel", text: "misc.cancel", type: "cancel", isCancel: true },
|
||||
{ id: "confirm", text: "pages.settings.advanced.use-symlinks.modal.confirm-btn", type: "error" }
|
||||
]
|
||||
}});
|
||||
|
||||
if(res.exitCode !== ModalExitCode.COMPLETED || res.data !== "confirm"){ return; }
|
||||
}
|
||||
|
||||
const { error } = await tryit(() => staticConfig.set("use-symlinks", newUseSymlink));
|
||||
|
||||
if(error){
|
||||
notificationService.notifyError({ title: "notifications.types.error", desc: "pages.settings.advanced.use-symlinks.error-notification.message" });
|
||||
return;
|
||||
}
|
||||
|
||||
setUseSymlink(() => newUseSymlink);
|
||||
}
|
||||
|
||||
const toogleShowSupporters = () => {
|
||||
setShowSupporters(show => !show);
|
||||
};
|
||||
@@ -585,12 +524,7 @@ export function SettingsPage() {
|
||||
</SettingContainer>
|
||||
</SettingContainer>
|
||||
|
||||
<SettingContainer title="pages.settings.advanced.title" description="pages.settings.advanced.description">
|
||||
<SettingToogleSwitchGrid items={[
|
||||
{ checked: hardwareAccelerationEnabled, text: t("pages.settings.advanced.hardware-acceleration.title"), desc: t("pages.settings.advanced.hardware-acceleration.description"), onChange: onChangeHardwareAcceleration },
|
||||
{ checked: useSymlink, text: t("pages.settings.advanced.use-symlinks.title"), desc: t("pages.settings.advanced.use-symlinks.description"), onChange: onChangeUseSymlinks },
|
||||
]}/>
|
||||
</SettingContainer>
|
||||
<AdvancedSettings />
|
||||
|
||||
<span className="bg-light-main-color-1 dark:bg-main-color-1 rounded-md py-1 px-2 font-bold float-right mb-5">v{appVersion}</span>
|
||||
</div>
|
||||
@@ -598,3 +532,128 @@ export function SettingsPage() {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function AdvancedSettings() {
|
||||
const ipc = useService(IpcService);
|
||||
const modal = useService(ModalService);
|
||||
const notification = useService(NotificationService);
|
||||
const progressBar = useService(ProgressBarService);
|
||||
const staticConfig = useService(StaticConfigurationService);
|
||||
|
||||
const t = useTranslationV2();
|
||||
|
||||
const [hardwareAccelerationEnabled, setHardwareAccelerationEnabled] = useState(true);
|
||||
const [useSymlink, setUseSymlink] = useState(false);
|
||||
const [useSystemProxy, setUseSystemProxy] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
staticConfig.get("disable-hadware-acceleration").then(disabled =>setHardwareAccelerationEnabled(() => disabled !== true));
|
||||
staticConfig.get("use-symlinks").then(useSymlinks => setUseSymlink(() => useSymlinks));
|
||||
staticConfig.get("use-system-proxy").then(useSystemProxy => setUseSystemProxy(() => useSystemProxy));
|
||||
}, []);
|
||||
|
||||
const onChangeHardwareAcceleration = async (newHardwareAccelerationEnabled: boolean) => {
|
||||
if(newHardwareAccelerationEnabled === hardwareAccelerationEnabled){ return; }
|
||||
|
||||
const res = await modal.openModal(BasicModal, { data: {
|
||||
title: "pages.settings.advanced.hardware-acceleration.modal.title",
|
||||
body: "pages.settings.advanced.hardware-acceleration.modal.body",
|
||||
image: BeatConflict,
|
||||
buttons: [
|
||||
{ id: "cancel", text: "misc.cancel", type: "cancel" },
|
||||
{ id: "confirm", text: "pages.settings.advanced.hardware-acceleration.modal.confirm-btn", type: "error", onClick: () => true },
|
||||
]
|
||||
}});
|
||||
|
||||
if(res.exitCode !== ModalExitCode.COMPLETED || res.data !== "confirm"){ return; }
|
||||
|
||||
const { error } = await tryit(() => staticConfig.set("disable-hadware-acceleration", !newHardwareAccelerationEnabled));
|
||||
|
||||
if(error){
|
||||
notification.notifyError({ title: "notifications.types.error", desc: "pages.settings.advanced.hardware-acceleration.error-notification.message" });
|
||||
setHardwareAccelerationEnabled(() => !newHardwareAccelerationEnabled);
|
||||
return;
|
||||
}
|
||||
|
||||
setHardwareAccelerationEnabled(() => newHardwareAccelerationEnabled);
|
||||
|
||||
if(!progressBar.require()){
|
||||
return;
|
||||
}
|
||||
|
||||
await lastValueFrom(ipc.sendV2("restart-app"));
|
||||
};
|
||||
|
||||
const onChangeUseSymlinks = async (newUseSymlink: boolean) => {
|
||||
|
||||
if (window.electron.platform !== "win32" || newUseSymlink === useSymlink) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(newUseSymlink){
|
||||
const res = await modal.openModal(BasicModal, { data: {
|
||||
title: "pages.settings.advanced.use-symlinks.modal.title",
|
||||
body: "pages.settings.advanced.use-symlinks.modal.body",
|
||||
image: BeatConflict,
|
||||
buttons: [
|
||||
{ id: "cancel", text: "misc.cancel", type: "cancel" },
|
||||
{ id: "confirm", text: "pages.settings.advanced.use-symlinks.modal.confirm-btn", type: "error", onClick: () => true }
|
||||
]
|
||||
}});
|
||||
|
||||
if(res.exitCode !== ModalExitCode.COMPLETED || res.data !== "confirm"){ return; }
|
||||
}
|
||||
|
||||
const { error } = await tryit(() => staticConfig.set("use-symlinks", newUseSymlink));
|
||||
|
||||
if(error){
|
||||
notification.notifyError({ title: "notifications.types.error", desc: "pages.settings.advanced.use-symlinks.error-notification.message" });
|
||||
return;
|
||||
}
|
||||
|
||||
setUseSymlink(() => newUseSymlink);
|
||||
}
|
||||
|
||||
const onChangeUseSystemProxy = async (newUseSystemProxy: boolean) => {
|
||||
|
||||
if (window.electron.platform !== "win32" || newUseSystemProxy === useSystemProxy) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { error } = await tryit(() => staticConfig.set("use-system-proxy", newUseSystemProxy));
|
||||
|
||||
if(error){
|
||||
notification.notifyError({ title: "notifications.types.error", desc: "pages.settings.advanced.use-system-proxy.error-notification.message" });
|
||||
return;
|
||||
}
|
||||
|
||||
setUseSystemProxy(() => newUseSystemProxy);
|
||||
}
|
||||
|
||||
const advancedItems: Item[] = [{
|
||||
checked: hardwareAccelerationEnabled,
|
||||
text: t.text("pages.settings.advanced.hardware-acceleration.title"),
|
||||
desc: t.text("pages.settings.advanced.hardware-acceleration.description"),
|
||||
onChange: onChangeHardwareAcceleration
|
||||
}];
|
||||
if (window.electron.platform === "win32") {
|
||||
advancedItems.push({
|
||||
checked: useSymlink,
|
||||
text: t.text("pages.settings.advanced.use-symlinks.title"),
|
||||
desc: t.text("pages.settings.advanced.use-symlinks.description"),
|
||||
onChange: onChangeUseSymlinks
|
||||
});
|
||||
advancedItems.push({
|
||||
checked: useSystemProxy,
|
||||
text: t.text("pages.settings.advanced.use-system-proxy.title"),
|
||||
desc: t.text("pages.settings.advanced.use-system-proxy.description"),
|
||||
onChange: onChangeUseSystemProxy
|
||||
});
|
||||
}
|
||||
|
||||
return <SettingContainer title="pages.settings.advanced.title" description="pages.settings.advanced.description">
|
||||
<SettingToogleSwitchGrid items={advancedItems}/>
|
||||
</SettingContainer>
|
||||
|
||||
}
|
||||
|
||||
|
||||
Vendored
+1
-1
@@ -3,7 +3,7 @@ import { webUtils } from "electron";
|
||||
declare global {
|
||||
interface Window {
|
||||
electron: {
|
||||
platform: "win32"|"linux"|"darwin",
|
||||
platform: "win32" | "linux",
|
||||
ipcRenderer: {
|
||||
sendMessage(channel: string, args: any): void;
|
||||
on(channel: string, func: (...args: any) => void): (() => void) | undefined;
|
||||
|
||||
@@ -31,7 +31,7 @@ export class AutoUpdaterService {
|
||||
this.ipcService = IpcService.getInstance();
|
||||
}
|
||||
|
||||
public isUpdateAvailable(): Promise<boolean> {
|
||||
public async isUpdateAvailable(): Promise<boolean> {
|
||||
return lastValueFrom(this.ipcService.sendV2("check-update")).catch(() => false);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import { ConfigurationService } from "./configuration.service";
|
||||
import { ThemeService } from "./theme.service";
|
||||
import { BsStore } from "shared/models/bs-store.enum";
|
||||
import { ModalExitCode, ModalService } from "./modale.service";
|
||||
import { OriginalOculusVersionBackupModal } from "renderer/components/modal/modal-types/original-oculus-version-backup.modal";
|
||||
import { EnableOculusSideloadedApps } from "renderer/components/modal/modal-types/enable-oculus-sideloaded-apps";
|
||||
import { CustomError } from "shared/models/exceptions/custom-error.class";
|
||||
import { sToMs } from "shared/helpers/time.helpers";
|
||||
import { NeedLaunchAdminModal } from "renderer/components/modal/modal-types/need-launch-admin-modal.component";
|
||||
@@ -47,10 +47,8 @@ export class BSLauncherService {
|
||||
public getLaunchOptions(version: BSVersion): LaunchOption{
|
||||
return {
|
||||
version,
|
||||
oculus: this.config.get(LaunchMods.OCULUS_MOD),
|
||||
desktop: this.config.get(LaunchMods.DESKTOP_MOD),
|
||||
debug: this.config.get(LaunchMods.DEBUG_MOD),
|
||||
additionalArgs: (this.config.get<string>("additionnal-args") || "").split(";").map(arg => arg.trim()).filter(arg => arg.length > 0)
|
||||
launchMods: this.config.get("launch-mods") ?? [],
|
||||
additionalArgs: (this.config.get<string>("additionnal-args") || "").split(";").map(arg => arg.trim()).filter(arg => arg.length > 0),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,6 +80,20 @@ export class BSLauncherService {
|
||||
return true;
|
||||
}
|
||||
|
||||
private async enableSideloadedAppsIfNeeded(): Promise<void> {
|
||||
if(window.electron.platform !== "win32"){ return; }
|
||||
const isSideloadedAppsEnabled = await lastValueFrom(this.ipcService.sendV2("is-oculus-sideloaded-apps-enabled"));
|
||||
if(isSideloadedAppsEnabled){ return; }
|
||||
|
||||
const modalRes = await this.modals.openModal(EnableOculusSideloadedApps);
|
||||
|
||||
if(modalRes.exitCode !== ModalExitCode.COMPLETED){
|
||||
throw new Error("Enable sideloaded apps canceled");
|
||||
}
|
||||
|
||||
await lastValueFrom(this.ipcService.sendV2("enable-oculus-sideloaded-apps"));
|
||||
}
|
||||
|
||||
public doLaunch(launchOptions: LaunchOption): Observable<BSLaunchEventData>{
|
||||
return this.ipcService.sendV2("bs-launch.launch", launchOptions);
|
||||
}
|
||||
@@ -91,10 +103,9 @@ export class BSLauncherService {
|
||||
return new Observable<BSLaunchEventData>(obs => {
|
||||
(async () => {
|
||||
|
||||
if(launchOptions.version.metadata?.store === BsStore.OCULUS && !this.notRewindBackupOculus()){
|
||||
const { exitCode, data: notRewind } = await this.modals.openModal(OriginalOculusVersionBackupModal);
|
||||
if(exitCode !== ModalExitCode.COMPLETED){ return; }
|
||||
this.setNotRewindBackupOculus(notRewind);
|
||||
// If downgraded from oculus and its not the official version
|
||||
if(launchOptions.version.metadata?.store === BsStore.OCULUS && !launchOptions.version.oculus){
|
||||
await this.enableSideloadedAppsIfNeeded();
|
||||
}
|
||||
|
||||
if(launchOptions.version.metadata?.store !== BsStore.OCULUS){
|
||||
@@ -123,9 +134,3 @@ export class BSLauncherService {
|
||||
return lastValueFrom(this.ipcService.sendV2("bs-launch.restore-steamvr"));
|
||||
}
|
||||
}
|
||||
|
||||
export enum LaunchMods {
|
||||
OCULUS_MOD = "LAUNCH_OCULUS_MOD",
|
||||
DESKTOP_MOD = "LAUNCH_DESKTOP_MOD",
|
||||
DEBUG_MOD = "LAUNCH_DEBUG_MOD",
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { Observable, BehaviorSubject, throwError, of } from "rxjs";
|
||||
import { catchError, tap } from "rxjs/operators";
|
||||
import { Observable, BehaviorSubject, throwError, of, lastValueFrom } from "rxjs";
|
||||
import { catchError, map, tap } from "rxjs/operators";
|
||||
import { BSVersion } from "shared/bs-version.interface";
|
||||
import { Mod } from "shared/models/mods";
|
||||
import { IpcService } from "./ipc.service";
|
||||
import { ProgressBarService } from "./progress-bar.service";
|
||||
import { NotificationService } from "./notification.service";
|
||||
import { Progression } from "main/helpers/fs.helpers";
|
||||
import { ProgressionInterface } from "shared/models/progress-bar";
|
||||
|
||||
export class BsModsManagerService {
|
||||
private static instance: BsModsManagerService;
|
||||
@@ -32,11 +33,11 @@ export class BsModsManagerService {
|
||||
}
|
||||
|
||||
public getAvailableMods(version: BSVersion): Observable<Mod[]> {
|
||||
return this.ipcService.sendV2("get-available-mods", version);
|
||||
return this.ipcService.sendV2("bs-mods.get-available-mods", version);
|
||||
}
|
||||
|
||||
public getInstalledMods(version: BSVersion): Observable<Mod[]> {
|
||||
return this.ipcService.sendV2("get-installed-mods", version);
|
||||
return this.ipcService.sendV2("bs-mods.get-installed-mods", version);
|
||||
}
|
||||
|
||||
public installMods(mods: Mod[], version: BSVersion): Observable<Progression> {
|
||||
@@ -46,7 +47,7 @@ export class BsModsManagerService {
|
||||
}
|
||||
|
||||
return new Observable<Progression>(obs => {
|
||||
const install$ = this.ipcService.sendV2("install-mods", { mods, version });
|
||||
const install$ = this.ipcService.sendV2("bs-mods.install-mods", { mods, version });
|
||||
this.progressBar.show(install$.pipe(catchError(() => of({ current: 0, total: 0} as Progression))), { paddingLeft: "190px", paddingRight: "190px", bottom: "20px" });
|
||||
|
||||
const sub = install$.pipe(
|
||||
@@ -77,7 +78,7 @@ export class BsModsManagerService {
|
||||
}
|
||||
|
||||
return new Observable<Progression>(obs => {
|
||||
const uninstall$ = this.ipcService.sendV2("uninstall-mods", { mods: [mod], version });
|
||||
const uninstall$ = this.ipcService.sendV2("bs-mods.uninstall-mods", { mods: [mod], version });
|
||||
this.progressBar.show(uninstall$.pipe(catchError(() => of({ current: 0, total: 0} as Progression))), { paddingLeft: "190px", paddingRight: "190px", bottom: "20px" });
|
||||
|
||||
const sub = uninstall$.pipe(
|
||||
@@ -102,13 +103,57 @@ export class BsModsManagerService {
|
||||
});
|
||||
}
|
||||
|
||||
public async importMods(paths: string[], version: BSVersion): Promise<void> {
|
||||
if (!this.progressBar.require()) {
|
||||
throw new Error("Action already in progress");
|
||||
}
|
||||
|
||||
// TODO: Check for conflicting mod files, and ask the user if they want to overwrite the files
|
||||
|
||||
const import$ = this.ipcService.sendV2("bs-mods.import-mods", {
|
||||
paths, version
|
||||
});
|
||||
|
||||
this.progressBar.show(import$.pipe(
|
||||
catchError(() => of()),
|
||||
map(progress => ({
|
||||
progression: (progress.current / progress.total) * 100,
|
||||
label: progress.data?.name
|
||||
}) as ProgressionInterface)
|
||||
));
|
||||
|
||||
return lastValueFrom(import$)
|
||||
.then(progress => {
|
||||
if (progress.current === 0) {
|
||||
return;
|
||||
}
|
||||
this.notifications.notifySuccess({
|
||||
title: "notifications.mods.import-mod.titles.success",
|
||||
desc: progress.current === progress.total
|
||||
? "notifications.mods.import-mod.msgs.success"
|
||||
: "notifications.mods.import-mod.msgs.some-success",
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
this.notifications.notifyError({
|
||||
title: "notifications.mods.import-mod.titles.error",
|
||||
desc: ["no-dlls"].includes(error?.code)
|
||||
? `notifications.mods.import-mod.msgs.${error.code}`
|
||||
: "misc.unknown",
|
||||
});
|
||||
})
|
||||
.finally(() => {
|
||||
this.progressBar.hide();
|
||||
});
|
||||
}
|
||||
|
||||
public uninstallAllMods(version: BSVersion): Observable<Progression> {
|
||||
if (!this.progressBar.require()) {
|
||||
return throwError(() => new Error("Action already in progress"));
|
||||
}
|
||||
|
||||
return new Observable<Progression>(obs => {
|
||||
const uninstall$ = this.ipcService.sendV2("uninstall-all-mods", version);
|
||||
const uninstall$ = this.ipcService.sendV2("bs-mods.uninstall-all-mods", version);
|
||||
this.progressBar.show(uninstall$.pipe(catchError(() => of({ current: 0, total: 0} as Progression))), { paddingLeft: "190px", paddingRight: "190px", bottom: "20px" });
|
||||
|
||||
const sub = uninstall$.pipe(
|
||||
@@ -133,4 +178,5 @@ export class BsModsManagerService {
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -8,10 +8,9 @@ import { NotificationService } from "../notification.service";
|
||||
import { ModalExitCode, ModalService } from "../modale.service";
|
||||
import { DownloaderServiceInterface } from "./bs-store-downloader.interface";
|
||||
import { AbstractBsDownloaderService } from "./abstract-bs-downloader.service";
|
||||
import { DownloadInfo } from "main/services/bs-version-download/bs-steam-downloader.service";
|
||||
import { MetaAuthErrorCodes, OculusDownloaderErrorCodes } from "shared/models/bs-version-download/oculus-download.model";
|
||||
import { EnterMetaTokenModal } from "renderer/components/modal/modal-types/bs-downgrade/enter-meta-token-modal.component";
|
||||
import { addFilterStringLog } from "renderer";
|
||||
import { LoginToMetaModal } from "renderer/components/modal/modal-types/bs-downgrade/login-to-meta-modal.component";
|
||||
import { OculusDownloadInfo } from "main/services/bs-version-download/bs-oculus-downloader.service";
|
||||
|
||||
export class OculusDownloaderService extends AbstractBsDownloaderService implements DownloaderServiceInterface{
|
||||
|
||||
@@ -69,7 +68,7 @@ export class OculusDownloaderService extends AbstractBsDownloaderService impleme
|
||||
);
|
||||
}
|
||||
|
||||
private startDownloadBsVersion(downloadInfo: DownloadInfo): Observable<Progression<BSVersion>>{
|
||||
private startDownloadBsVersion(downloadInfo: OculusDownloadInfo): Observable<Progression<BSVersion>>{
|
||||
const ignoreCode = [MetaAuthErrorCodes.META_LOGIN_WINDOW_CLOSED_BY_USER, OculusDownloaderErrorCodes.DOWNLOAD_CANCELLED];
|
||||
return this.handleDownload(
|
||||
this.ipc.sendV2("bs-oculus-download", downloadInfo ),
|
||||
@@ -77,25 +76,16 @@ export class OculusDownloaderService extends AbstractBsDownloaderService impleme
|
||||
);
|
||||
}
|
||||
|
||||
private async doDownloadBsVersion(bsVersion: BSVersion, isVerification: boolean): Promise<BSVersion> {
|
||||
private async doDownloadBsVersion(bsVersion: BSVersion, isVerification: boolean): Promise<BSVersion|undefined> {
|
||||
|
||||
const autoDownloadFailed = false;
|
||||
const {exitCode, data: token} = await this.modals.openModal(LoginToMetaModal)
|
||||
|
||||
return (async () => {
|
||||
if(exitCode !== ModalExitCode.COMPLETED){
|
||||
return undefined
|
||||
}
|
||||
|
||||
const tokenRes = await this.modals.openModal(EnterMetaTokenModal);
|
||||
|
||||
if(tokenRes.exitCode !== ModalExitCode.COMPLETED){
|
||||
return false;
|
||||
}
|
||||
|
||||
addFilterStringLog(tokenRes.data);
|
||||
|
||||
return lastValueFrom(this.startDownloadBsVersion({ bsVersion, isVerification, token: tokenRes.data })).then(() => true);
|
||||
|
||||
})().then(res => {
|
||||
|
||||
if(autoDownloadFailed || !res){
|
||||
return lastValueFrom(this.startDownloadBsVersion({ bsVersion, isVerification, token })).then(() => true).then(res => {
|
||||
if(!res){
|
||||
return bsVersion;
|
||||
}
|
||||
|
||||
@@ -121,4 +111,12 @@ export class OculusDownloaderService extends AbstractBsDownloaderService impleme
|
||||
return lastValueFrom(this.ipc.sendV2("bs-oculus-stop-download"));
|
||||
}
|
||||
|
||||
public deleteMetaSession(): Promise<void>{
|
||||
return lastValueFrom(this.ipc.sendV2("delete-meta-session"));
|
||||
}
|
||||
|
||||
public metaSessionExists(): Promise<boolean>{
|
||||
return lastValueFrom(this.ipc.sendV2("meta-session-exists"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user