Compare commits

...

53 Commits

Author SHA1 Message Date
MathieuG-P 499956d3b3 update to 1.4.8 2024-08-01 00:23:32 +02:00
MathieuG-P c313685f6f [chore] filter potential oculus token in logs 2024-08-01 00:20:02 +02:00
MathieuG-P 88a1691d4b bumb to 1.4.7 2024-03-13 15:04:45 +01:00
MathieuG-P cdd25b458c Merge pull request #436 from Zagrios/bugfix/downloading-models-with-bsm-no-longer-works
[bugfix] fix downloading models not working
2024-03-13 14:55:42 +01:00
MathieuG-P 30a53c013f [bugfix] fix downloading models not working
Add useragent to requests to not being rejected
2024-03-13 14:51:51 +01:00
MathieuG-P 1db3db7bdd Merge branch 'master' of https://github.com/Zagrios/bs-manager 2024-03-07 18:09:18 +01:00
MathieuG-P e0e647b22a add version 1.35.0 2024-03-07 18:08:15 +01:00
MathieuG-P 69914e3063 Merge pull request #435 from fllppi/bugfix/fixed-typo-in-german-translation/434
fixed typo in german translation for map installation
2024-03-07 05:59:44 +01:00
Flippi 0c2624ed37 fixed typo in german translation for map installation 2024-03-06 21:58:06 +01:00
MathieuG-P cf11277541 bumb to 1.4.6 2024-03-06 12:30:26 +01:00
MathieuG-P d028e144fc [chore] Oculus backup is now automatically restored when Beat Saber closes 2024-03-06 10:06:27 +01:00
MathieuG-P 59c624d58d Merge pull request #432 from Zagrios/bugfix/oculus-versions-being-deleted-by-oculus-auto-updates
[bugfix] Added a Beat Saber ends watcher to delete created symlink to avoid Oculus version getting wiped by Oculus auto updates
2024-03-05 13:54:32 +01:00
MathieuG-P 2d67876ee1 [bugfix] Added a Beat Saber ends watcher to delete created symlink to avoid Oculus version getting wiped by Oculus auto updates 2024-03-05 13:33:04 +01:00
MathieuG-P 6b30d74628 Merge pull request #431 from Zagrios/bugfix/handle-unable-to-get-documents-path
[bugfix] handle unable to get documents path error
2024-03-04 13:35:26 +01:00
MathieuG-P eb76bb5df0 [bugfix] handle unable to get documents path error 2024-03-04 13:29:23 +01:00
MathieuG-P 590daf3be2 add patreon 2024-02-27 18:26:27 +01:00
MathieuG-P ecf18b9967 Update translations and disable "exclude installed" filters in list of installed maps 2024-02-26 13:44:04 +01:00
MathieuG-P bc26f329a5 Merge pull request #426 from Liborsaf/feature/exclude-installed
[feature] Implement exclude of installed maps as filter
2024-02-26 13:05:01 +01:00
Libor Šafář 030df21619 Implement exclude of installed maps as filter 2024-02-24 22:49:25 +01:00
MathieuG-P d61588c09f add patreon 2024-02-18 22:22:41 +01:00
MathieuG-P 7ed2b5c820 add OculusBinaryId 2024-02-16 16:07:20 +01:00
Iluhadesu 239a68821e Add 1.34.6 2024-02-16 15:53:59 +01:00
MathieuG-P fc512091e4 add patreon 2024-02-14 10:27:42 +01:00
MathieuG-P f71c29da58 bumb to 1.4.5 2024-02-12 16:35:04 +01:00
Iluhadesu 60c656b45b Add 1.34.5 2024-02-12 16:04:28 +01:00
MathieuG-P bec8e1f571 Merge pull request #417 from slinkstr/bugfix/fpfc-restore/416
[bugfix-416] Wait for SteamVR folder rename before exit
2024-02-12 15:22:57 +01:00
MathieuG-P 20d0cd7ee6 Merge pull request #421 from Zagrios/bugfix/unable-to-launch-oculus-version-when-backup-already-exists
[bugfix] Fixed unable to launch downgraded oculus version when backup already exist
2024-02-12 14:30:55 +01:00
MathieuG-P 3f37c2ee34 [bugfix] Fixed unable to launch downgraded oculus version when backup already exists 2024-02-12 14:19:59 +01:00
slinky 0b24a5ceb2 [bugfix-416] Wait for SteamVR folder rename before exit 2024-02-08 11:55:57 -08:00
MathieuG-P 318a6b5afa add patreon 2024-02-06 22:30:08 +01:00
MathieuG-P c80685065e Merge pull request #415 from Zagrios/bugfix/cannot-launch-bs-steam-is-elevated-error
[bugfix] handle errors when cheking if steam is running as admin
2024-02-02 20:23:19 +01:00
MathieuG-P cdc589f602 [bugfix] handle error when cheking if steam is running as admin 2024-02-02 20:18:33 +01:00
MathieuG-P ee6acb7980 bumb to 1.4.4 2024-02-01 10:48:28 +01:00
MathieuG-P 515ef06589 add patreon 2024-01-27 20:12:10 +01:00
MathieuG-P 344cce9461 Merge pull request #412 from Zagrios/bugfix/regedit-rs-broke-linux-support/411
[bugfix-411] dynamically import regedit-rs depending on the os
2024-01-27 13:53:05 +01:00
MathieuG-P 536690dc55 [bugfix-411] dynamically import regedit-rs depending on the os 2024-01-27 13:46:44 +01:00
MathieuG-P 4fc5a87c13 [chore] add checks and args whitelist for launching BS as admin 2024-01-25 21:44:58 +01:00
MathieuG-P 263061e7ce bumb to 1.4.4-alpha.2 2024-01-25 00:39:31 +01:00
MathieuG-P 0b7cad82d5 Merge pull request #408 from Zagrios/feature/admin-confirm-modal
[feature] Added admin launch information modal
2024-01-24 23:16:17 +01:00
MathieuG-P 63d35f678d [feature] Added admin launch information modal 2024-01-24 23:07:49 +01:00
MathieuG-P 16b9ca79f2 bumb version to 1.4.4-alpha.1 2024-01-21 16:55:26 +01:00
MathieuG-P 792ad9d777 [chore] fix lint error 2024-01-21 16:54:53 +01:00
MathieuG-P b6ecaa3934 bumb version to 1.4.4 2024-01-21 16:15:50 +01:00
MathieuG-P bb4ce5505a [chore] add @Log decorator + add logs for LivShortcuts 2024-01-21 15:22:09 +01:00
MathieuG-P d6aad2690c Merge pull request #405 from Zagrios/bugfix/launch-oculus-to-avoid-bs-crashs
[bugfix] Launch Oculus when launching Oculus BS version to avoid crashs
2024-01-20 16:49:02 +01:00
MathieuG-P cfb2b35bf7 [bugfix] Fix reject error message in OculusService 2024-01-20 16:38:52 +01:00
MathieuG-P 40f6803b90 [bugfix] Launch Oculus when launching Oculus BS version to avoid crashs 2024-01-20 16:36:59 +01:00
MathieuG-P d2c1d815a1 Merge pull request #404 from Zagrios/bugfix/bs-crash-when-steam-run-as-admin
[bugfix] Start BS as Admin when Steam is running as admin but BSM is not
2024-01-20 15:55:18 +01:00
MathieuG-P 4c8617a419 [bugfix] Start BS as Admin when Steam is running as admin but BSM is not 2024-01-20 15:42:30 +01:00
MathieuG-P 444db56d90 indent_size to 4 2024-01-14 02:33:09 +01:00
MathieuG-P 4b94b11b53 add patreon 2024-01-14 02:31:58 +01:00
MathieuG-P 1a9ed04cc2 Merge pull request #400 from Zagrios/bugfix/update-regedit-rs-for-static-link-dll
[bugfix] update regedit-rs for static link dll
2024-01-13 14:57:22 +01:00
MathieuG-P f3d4c64118 [bugfix] update regedit-rs for static link dll 2024-01-13 14:55:26 +01:00
60 changed files with 1908 additions and 215 deletions
+1 -1
View File
@@ -2,7 +2,7 @@ root = true
[*]
indent_style = space
indent_size = 2
indent_size = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
+6 -1
View File
@@ -46,7 +46,12 @@ module.exports = {
"react/prop-types": "off",
"import/no-cycle": "off",
"prefer-promise-reject-errors": "off",
"react/jsx-no-target-blank": "off"
"react/jsx-no-target-blank": "off",
"@typescript-eslint/ban-types": ["error", {
types: {
Function: false,
}
}]
},
parserOptions: {
ecmaVersion: 2020,
+3
View File
@@ -27,3 +27,6 @@ npm-debug.log.*
*.css.d.ts
*.sass.d.ts
*.scss.d.ts
# externals
externals/**/target/*
+28 -1
View File
@@ -681,5 +681,32 @@
"ReleaseImg": "https://clan.akamai.steamstatic.com/images/32055887/ae11d195bbe41a0f244d7b4d055a0ab89f88e5bb.png",
"ReleaseDate": "1702309893",
"year": "2023"
},
{
"BSVersion": "1.34.5",
"BSManifest": "7413405154512822201",
"OculusBinaryId": "6639053626194360",
"ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/4036983137766828694",
"ReleaseImg": "https://clan.cloudflare.steamstatic.com/images/32055887/925e60f14c6815f7617fde11a894ed20f54a0846.png",
"ReleaseDate": "1707745958",
"year": "2024"
},
{
"BSVersion": "1.34.6",
"BSManifest": "4398761375819224126",
"OculusBinaryId": "6682206641879058",
"ReleaseURL": "https://store.steampowered.com/news/app/620980/view/7595953477644376720",
"ReleaseImg": "https://clan.cloudflare.steamstatic.com/images/32055887/e7c75bdfc76d00713adc3fbdf221c9ce8ab3620d.png",
"ReleaseDate": "1708091473",
"year": "2024"
},
{
"BSVersion": "1.35.0",
"BSManifest": "1490986193481243578",
"OculusBinaryId": "6720809361352119",
"ReleaseURL": "https://store.steampowered.com/news/app/620980/view/4142820264819707009",
"ReleaseImg": "https://clan.cloudflare.steamstatic.com/images//32055887/9a141be5c44ca6fc81551a2c7e7f28407df413ed.png",
"ReleaseDate": "1709831174",
"year": "2024"
}
]
]
+23 -1
View File
@@ -67,5 +67,27 @@
"username": "Lumberjack462",
"type": "diamond",
"link": "https://www.youtube.com/@lumberjack462"
},
{
"username": "Xero"
},
{
"username": "Minescence"
},
{
"username": "Finlay"
},
{
"username": "mereknom"
},
{
"username": "Jascha"
},
{
"username": "Celldweller",
"type": "gold"
},
{
"username": "rhythmshade"
}
]
]
+18 -7
View File
@@ -94,8 +94,7 @@
"edit": "Bearbeiten",
"uninstall": "Entfernen",
"create-shortcut": "Verknüpfung erstellen",
"shared-folders": "Geteilte Ordner",
"restore-oculus-folder": "Ordner wiederherstellen"
"shared-folders": "Geteilte Ordner"
}
},
"available-versions": {
@@ -460,7 +459,7 @@
},
"maps": {
"one-click-install": {
"success": "Map installation abegschlossen",
"success": "Map installation abgeschlossen",
"error": "Ein Fehler ist aufgetreten während der installation der Map"
}
},
@@ -695,9 +694,7 @@
"title": "Achtung",
"body": {
"must-be-installed-once": "Sie müssen Beat Saber mindestens einmal aus dem Oculus Store installiert haben, sonst könnte sich Beat Saber nach dem Start automatisch schließen.",
"need-backup": "Um diese Version zu starten, wird der Installationsordner von Beat Saber in Ihrer Oculus-Bibliothek umbenannt.",
"can-restore-later": "Bei Bedarf können Sie ihn vom BSManager aus wiederherstellen, indem Sie zu den Optionen der originalen Oculus-Version gehen und auf 'Ordner wiederherstellen' klicken",
"tips-launch-oculus": "Tipp: Sie können diese Version direkt über Oculus starten, solange die Originalversion nicht wiederhergestellt ist."
"will-backup": "Um diese Version zu starten, wird der ursprüngliche Installationsordner von Beat Saber in Ihrer Oculus-Bibliothek umbenannt und beim Schließen von Beat Saber automatisch wiederhergestellt."
},
"not-remind-me": "Nicht mehr erinnern",
"understood": "Verstanden"
@@ -719,6 +716,16 @@
"enter-oculus-token": "Ein Oculus-Token eingeben"
},
"valid-btn": "Validieren"
},
"launch-as-admin": {
"title": "Administratorberechtigungen",
"body": {
"info": "Es wurde festgestellt, dass Steam mit Administratorrechten ausgeführt wird. Um mit Steam zu kommunizieren, muss auch Beat Saber als Administrator gestartet werden. Andernfalls könnte Beat Saber Probleme haben und nach dem Starten schließen.",
"info-2": "Das Starten von Beat Saber im Administratormodus gibt auch den installierten Mods Administratorrechte. Daher wird empfohlen, Steam ohne Administratorrechte neu zu starten.",
"info-3": "Beachten Sie, dass es nicht ratsam ist, Steam Administratorrechte zu geben, da dies auch die installierten Spiele und Mods betrifft und ein Sicherheitsrisiko darstellt."
},
"launch-as-admin": "Als Administrator starten",
"not-remind-me": "Nicht mehr erinnern"
}
},
"maps": {
@@ -727,7 +734,8 @@
"nps" : "Noten Pro Sekunde",
"tags": "Stichworte",
"specificities": "Allgemein",
"requirements": "Anforderungen"
"requirements": "Anforderungen",
"exclude": "ausschließen"
},
"map-types": {
"accuracy": "Präzision",
@@ -783,6 +791,9 @@
"verified": "verifiziert",
"fullSpread": "full spread"
},
"map-excludes": {
"installed": "installiert"
},
"difficulties": {
"Easy": "einfach",
"Normal": "normal",
+17 -6
View File
@@ -94,8 +94,7 @@
"edit": "Edit",
"uninstall": "Uninstall",
"create-shortcut": "Create a shortcut",
"shared-folders": "Shared Folders",
"restore-oculus-folder": "Restore the folder"
"shared-folders": "Shared Folders"
}
},
"available-versions": {
@@ -695,9 +694,7 @@
"title": "Warning",
"body": {
"must-be-installed-once": "You must have installed Beat Saber at least once from the Oculus Store, otherwise Beat Saber might automatically close after launching.",
"need-backup": "To launch this version, the Beat Saber installation folder located in your Oculus library will be renamed.",
"can-restore-later": "If needed, you can restore it from BSManager by going to the options of the original Oculus version and clicking on 'Restore the folder'",
"tips-launch-oculus": "Tip: You can launch this version directly from Oculus, as long as the original version is not restored."
"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",
"understood": "Understood"
@@ -719,6 +716,16 @@
"enter-oculus-token": "Enter an Oculus token"
},
"valid-btn": "Validate"
},
"launch-as-admin": {
"title": "Administrator Permissions",
"body": {
"info": "Steam has been detected as running with administrator permissions. To communicate with Steam, Beat Saber must also be launched as an administrator. Otherwise, Beat Saber might experience problems and close after launching.",
"info-2": "Launching Beat Saber in administrator mode will also give administrator rights to the installed mods. Therefore, it is recommended to restart Steam without administrator permissions.",
"info-3": "Note that it is advised against giving administrator rights to Steam, as this affects the installed games and mods, presenting a security risk."
},
"launch-as-admin": "Launch as Administrator",
"not-remind-me": "Do not remind me"
}
},
"maps": {
@@ -727,7 +734,8 @@
"nps" : "Notes Per Second",
"tags": "tags",
"specificities": "general",
"requirements": "requirements"
"requirements": "requirements",
"exclude": "exclude"
},
"map-types": {
"accuracy": "accuracy",
@@ -783,6 +791,9 @@
"verified": "verified",
"fullSpread": "full spread"
},
"map-excludes": {
"installed": "installed"
},
"difficulties": {
"Easy": "easy",
"Normal": "normal",
+17 -6
View File
@@ -94,8 +94,7 @@
"edit": "Editar",
"uninstall": "Desinstalar",
"create-shortcut": "Crear un atajo",
"shared-folders": "Carpetas Compartidas",
"restore-oculus-folder": "Restaurar la carpeta"
"shared-folders": "Carpetas Compartidas"
}
},
"available-versions": {
@@ -695,9 +694,7 @@
"title": "Atención",
"body": {
"must-be-installed-once": "Debes haber instalado Beat Saber al menos una vez desde la Oculus Store, de lo contrario, Beat Saber podría cerrarse automáticamente después de iniciar.",
"need-backup": "Para iniciar esta versión, la carpeta de instalación de Beat Saber que se encuentra en tu biblioteca de Oculus será renombrada.",
"can-restore-later": "Si es necesario, podrás restaurarla desde el BSManager accediendo a las opciones de la versión original de Oculus y haciendo clic en 'Restaurar la carpeta'",
"tips-launch-oculus": "Consejo: Podrás iniciar esta versión directamente desde Oculus, siempre que la versión original no haya sido restaurada."
"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",
"understood": "Entendido"
@@ -719,6 +716,16 @@
"enter-oculus-token": "Introducir un token de Oculus"
},
"valid-btn": "Validar"
},
"launch-as-admin": {
"title": "Permisos Administrador",
"body": {
"info": "Se ha detectado que Steam está funcionando con permisos de administrador. Para comunicarse con Steam, Beat Saber también debe iniciarse como administrador. De lo contrario, Beat Saber podría experimentar problemas y cerrarse después de iniciar.",
"info-2": "Iniciar Beat Saber en modo administrador también otorgará derechos de administrador a los mods instalados. Por lo tanto, se recomienda reiniciar Steam sin permisos de administrador.",
"info-3": "Tenga en cuenta que se desaconseja otorgar derechos de administrador a Steam, ya que esto afecta a los juegos y mods instalados, presentando un riesgo de seguridad."
},
"launch-as-admin": "Iniciar como Administrador",
"not-remind-me": "No volver a recordármelo"
}
},
"maps": {
@@ -727,7 +734,8 @@
"nps" : "Notas Por Segundo",
"tags": "tags",
"specificities": "general",
"requirements": "requisitos"
"requirements": "requisitos",
"exclude": "excluir"
},
"map-types": {
"accuracy": "precisión",
@@ -783,6 +791,9 @@
"verified": "verificado",
"fullSpread": "panel completo"
},
"map-excludes": {
"installed": "instalado"
},
"difficulties": {
"Easy": "fácil",
"Normal": "normal",
+17 -6
View File
@@ -94,8 +94,7 @@
"edit": "Modifier",
"uninstall": "Désinstaller",
"create-shortcut": "Créer un raccourci",
"shared-folders": "Dossiers partagés",
"restore-oculus-folder": "Restaurer le dossier"
"shared-folders": "Dossiers partagés"
}
},
"available-versions": {
@@ -695,9 +694,7 @@
"title": "Attention",
"body": {
"must-be-installed-once": "Vous devez avoir installé au moins une fois Beat Saber depuis l'Oculus Store, sinon Beat Saber pourrait se fermer automatiquement après son lancement.",
"need-backup": "Pour lancer cette version, le dossier d'installation de Beat Saber se trouvant dans votre librairie Oculus va être renommé.",
"can-restore-later": "Au besoin, vous pourrez le restaurer depuis BSManager en vous rendant dans les options de la version originale Oculus et en cliquant sur \"Restaurer le dossier\"",
"tips-launch-oculus": "Astuce : Vous pourrez lancer cette version directement depuis Oculus, tant que la version originale n'est pas restaurée."
"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",
"understood": "J'ai compris"
@@ -719,6 +716,16 @@
"enter-oculus-token": "Entrer un token Oculus"
},
"valid-btn": "Valider"
},
"launch-as-admin": {
"title": "Permissions Administrateur",
"body": {
"info": "Steam a été détecté comme fonctionnant avec des permissions administrateur. Afin communiquer avec Steam, Beat Saber doit également être lancé en tant qu'administrateur. Dans le cas contraire, Beat Saber pourrait rencontrer des problèmes et se fermer après son lancement.",
"info-2": "Lancer Beat Saber en mode administrateur donnera également les droits administrateur aux mods installés. Il est donc plutôt recommandé de redémarrer Steam sans permissions administrateur.",
"info-3": "Notez qu'il est déconseillé de donner des droits d'administrateur à Steam, car cela affecte également les jeux et mods installés, présentant un risque pour la sécurité."
},
"launch-as-admin": "Lancer en administateur",
"not-remind-me": "Ne plus me rappeler"
}
},
"maps": {
@@ -727,7 +734,8 @@
"nps" : "Notes Par Seconde",
"tags": "tags",
"specificities": "général",
"requirements": "requis"
"requirements": "requis",
"exclude": "exclure"
},
"map-types": {
"accuracy": "précision",
@@ -783,6 +791,9 @@
"verified": "vérifiée",
"fullSpread": "panel complet"
},
"map-excludes": {
"installed": "installé"
},
"difficulties": {
"Easy": "facile",
"Normal": "normal",
+17 -6
View File
@@ -94,8 +94,7 @@
"edit": "編集",
"uninstall": "アンインストール",
"create-shortcut": "ショートカットを作成",
"shared-folders": "共有フォルダー",
"restore-oculus-folder": "フォルダーの復元"
"shared-folders": "共有フォルダー"
}
},
"available-versions": {
@@ -695,9 +694,7 @@
"title": "注意",
"body": {
"must-be-installed-once": "少なくとも一度はOculus StoreからBeat Saberをインストールしている必要があります。そうしないとBeat Saberは起動後に自動的に終了する可能性があります。",
"need-backup": "このバージョンを起動するに、OculusライブラリにあるBeat Saberインストールフォルダ名前変更ます。",
"can-restore-later": "必要であれば、BSManagerから元のOculusバージョンのオプションに行き、'フォルダを復元'をクリックして復元することができます。",
"tips-launch-oculus": "ヒント: 元のバージョンが復元されない限り、Oculusから直接このバージョンを起動することができます。"
"will-backup": "このバージョンを起動するために、OculusライブラリにあるBeat Saberの元のインストールフォルダ名前変更され、Beat Saberの終了時に自動的に復元されます。"
},
"not-remind-me": "二度と表示しないでください",
"understood": "分かった"
@@ -719,6 +716,16 @@
"enter-oculus-token": "オキュラストークンを入力する"
},
"valid-btn": "確認"
},
"launch-as-admin": {
"title": "管理者権限",
"body": {
"info": "Steamが管理者権限で動作していることが検出されました。Steamと通信するためには、Beat Saberも管理者として起動する必要があります。そうでない場合、Beat Saberは問題が発生し、起動後に終了する可能性があります。",
"info-2": "管理者モードでBeat Saberを起動すると、インストールされたMODにも管理者権限が与えられます。そのため、管理者権限なしでSteamを再起動することをお勧めします。",
"info-3": "Steamに管理者権限を与えることはお勧めできません。これはインストールされたゲームやMODにも影響を与え、セキュリティリスクをもたらすからです。"
},
"launch-as-admin": "管理者として起動",
"not-remind-me": "Больше не напоминать"
}
},
"maps": {
@@ -727,7 +734,8 @@
"nps" : "秒間ノート数",
"tags": "タグ",
"specificities": "一般",
"requirements": "要Mod"
"requirements": "要Mod",
"exclude": "除外する"
},
"map-types": {
"accuracy": "正確",
@@ -783,6 +791,9 @@
"verified": "認証済み",
"fullSpread": "フルスプレッド"
},
"map-excludes": {
"installed": "インストール済み"
},
"difficulties": {
"Easy": "easy",
"Normal": "normal",
+16 -6
View File
@@ -94,8 +94,7 @@
"edit": "Редактировать",
"uninstall": "Удалить",
"create-shortcut": "Создать ярлык",
"shared-folders": "Общие папки",
"restore-oculus-folder": "Восстановить папку"
"shared-folders": "Общие папки"
}
},
"available-versions": {
@@ -695,9 +694,7 @@
"title": "Внимание",
"body": {
"must-be-installed-once": "Вы должны были установить Beat Saber хотя бы один раз из Oculus Store, иначе Beat Saber может автоматически закрыться после запуска.",
"need-backup": "Для запуска этой версии папка установки Beat Saber в вашей библиотеке Oculus будет переименована.",
"can-restore-later": "При необходимости вы можете восстановить её через BSManager, перейдя в опции оригинальной версии Oculus и нажав на 'Восстановить папку'",
"tips-launch-oculus": "Совет: Вы можете запустить эту версию напрямую через Oculus, пока оригинальная версия не восстановлена."
"will-backup": "Для запуска этой версии исходная папка установки Beat Saber, находящаяся в вашей библиотеке Oculus, будет переименована и автоматически восстановлена при выходе из Beat Saber."
},
"not-remind-me": "Больше не напоминать",
"understood": "Понял"
@@ -719,6 +716,15 @@
"enter-oculus-token": "Введите токен Oculus"
},
"valid-btn": "Подтвердить"
},
"launch-as-admin": {
"title": "Права Администратора",
"body": {
"info": "Обнаружено, что Steam работает с правами администратора. Чтобы связаться со Steam, Beat Saber также должен быть запущен от имени администратора. В противном случае Beat Saber может столкнуться с проблемами и закрыться после запуска.",
"info-2": "Запуск Beat Saber в режиме администратора также даст права администратора установленным модам. Поэтому рекомендуется перезапустить Steam без прав администратора.",
"info-3": "Обратите внимание, что не рекомендуется предоставлять права администратора Steam, так как это также влияет на установленные игры и моды, представляя угрозу безопасности."
},
"launch-as-admin": "Запустить от имени администратора"
}
},
"maps": {
@@ -727,7 +733,8 @@
"nps" : "Нот в Секунду",
"tags": "тэги",
"specificities": "основное",
"requirements": "требуемые моды"
"requirements": "требуемые моды",
"exclude": "исключить"
},
"map-types": {
"accuracy": "точность",
@@ -783,6 +790,9 @@
"verified": "проверенные авторы",
"fullSpread": "полный набор"
},
"map-excludes": {
"installed": "установленный"
},
"difficulties": {
"Easy": "легко",
"Normal": "нормально",
+17 -6
View File
@@ -94,8 +94,7 @@
"edit": "編輯",
"uninstall": "移除",
"create-shortcut": "創建捷徑",
"shared-folders": "Shared Folders",
"restore-oculus-folder": "Restore the folder"
"shared-folders": "Shared Folders"
}
},
"available-versions": {
@@ -695,9 +694,7 @@
"title": "警告",
"body": {
"must-be-installed-once": "你必須至少從 Oculus 商店安裝過一次 BeatSaber,否則 BeatSaber 可能會在啟動後自動關閉。",
"need-backup": "要啟動該版本,位於你的 Oculus 庫中的 BeatSaber 安裝文件夾將被重命名。",
"can-restore-later": "如果需要,你可以通過前往 BSManager 的原始 Oculus 版本選項並點擊'恢復文件夾'來恢復它。",
"tips-launch-oculus": "提示:只要原始版本沒有被恢復,你就可以直接從 Oculus 啟動該版本。"
"will-backup": "為了啟動這個版本,位於您的Oculus庫中的Beat Saber的原始安裝文件夾將被重命名,並且在Beat Saber關閉時會自動恢復。"
},
"not-remind-me": "不再提醒我",
"understood": "明白了"
@@ -719,6 +716,16 @@
"enter-oculus-token": "輸入Oculus令牌"
},
"valid-btn": "驗證"
},
"launch-as-admin": {
"title": "管理員權限",
"body": {
"info": "檢測到Steam正在以管理員權限運行。為了與Steam通信,Beat Saber也必須以管理員身份啟動。否則,Beat Saber可能會遇到問題並在啟動後關閉。",
"info-2": "以管理員模式啟動Beat Saber還會將管理員權限賦予已安裝的模組。因此,建議重啟Steam,不使用管理員權限。",
"info-3": "請注意,不建議給Steam管理員權限,因為這也會影響已安裝的遊戲和模組,帶來安全風險。"
},
"launch-as-admin": "以管理員身份啟動",
"not-remind-me": "不再提醒我"
}
},
"maps": {
@@ -727,7 +734,8 @@
"nps" : "每秒音符數",
"tags": "標籤",
"specificities": "general",
"requirements": "要求"
"requirements": "要求",
"exclude": "排除"
},
"map-types": {
"accuracy": "精確度",
@@ -783,6 +791,9 @@
"verified": "Verified",
"fullSpread": "Full spread"
},
"map-excludes": {
"installed": "已安裝"
},
"difficulties": {
"Easy": "Easy",
"Normal": "Normal",
+17 -6
View File
@@ -94,8 +94,7 @@
"edit": "编辑",
"uninstall": "卸载",
"create-shortcut": "创建快捷方式",
"shared-folders": "Shared Folders",
"restore-oculus-folder": "Restore the folder"
"shared-folders": "Shared Folders"
}
},
"available-versions": {
@@ -695,9 +694,7 @@
"title": "警告",
"body": {
"must-be-installed-once": "你必须至少从 Oculus 商店安装过一次 BeatSaber,否则 BeatSaber 可能会在启动后自动关闭。",
"need-backup": "要启动该版本,位于你的 Oculus 库中的 BeatSaber 安装文件夹将被重命名。",
"can-restore-later": "如果需要,你可以通过前往 BSManager 的原始 Oculus 版本选项并点击'恢复文件夹'来恢复它。",
"tips-launch-oculus": "提示:只要原始版本没有被恢复,你就可以直接从 Oculus 启动该版本。"
"will-backup": "为了启动这个版本,位于您的Oculus库中的Beat Saber的原始安装文件夹将被重命名,并且在Beat Saber关闭时会自动恢复。"
},
"not-remind-me": "不再提醒我",
"understood": "明白了"
@@ -719,6 +716,16 @@
"enter-oculus-token": "输入Oculus令牌"
},
"valid-btn": "验证"
},
"launch-as-admin": {
"title": "管理员权限",
"body": {
"info": "检测到Steam正在以管理员权限运行。为了与Steam通信,Beat Saber也必须以管理员身份启动。否则,Beat Saber可能会遇到问题并在启动后关闭。",
"info-2": "以管理员模式启动Beat Saber还会将管理员权限赋予已安装的模组。因此,建议重启Steam,不使用管理员权限。",
"info-3": "请注意,不建议给Steam管理员权限,因为这也会影响已安装的游戏和模组,带来安全风险。"
},
"launch-as-admin": "以管理员身份启动",
"not-remind-me": "不再提醒我"
}
},
"maps": {
@@ -727,7 +734,8 @@
"nps" : "每秒音符数",
"tags": "标签",
"specificities": "general",
"requirements": "要求"
"requirements": "要求",
"exclude": "排除"
},
"map-types": {
"accuracy": "精确度",
@@ -783,6 +791,9 @@
"verified": "verified",
"fullSpread": "full spread"
},
"map-excludes": {
"installed": "已安装"
},
"difficulties": {
"Easy": "easy",
"Normal": "normal",
Binary file not shown.
Binary file not shown.
+2
View File
@@ -0,0 +1,2 @@
[target.x86_64-pc-windows-msvc]
rustflags = ["-C", "target-feature=+crt-static"]
+83
View File
@@ -0,0 +1,83 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "proc-macro2"
version = "1.0.76"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c"
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 = "serde"
version = "1.0.195"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.195"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "start_beat_saber_admin"
version = "0.1.0"
dependencies = [
"winres",
]
[[package]]
name = "syn"
version = "2.0.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
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.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "winres"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b68db261ef59e9e52806f688020631e987592bd83619edccda9c47d42cde4f6c"
dependencies = [
"toml",
]
+18
View File
@@ -0,0 +1,18 @@
[package]
name = "start_beat_saber_admin"
version = "0.1.0"
edition = "2021"
build = "build.rs"
[build-dependencies]
winres = "0.1.12"
[profile.release]
codegen-units = 1
lto = true
opt-level = "z"
[package.metadata.winres]
FileDescription = "Start Beat Saber as Admin"
LegalCopyright = "Copyright © 2024 Zagrios"
CompanyName = "Zagrios"
+20
View File
@@ -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();
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

+47
View File
@@ -0,0 +1,47 @@
use std::{process::Command, path::Path};
const EXECUTABLE_NAME: &str = "Beat Saber.exe";
const AUTORIZED_ARGS: [&str; 8] = [
"fpfc",
"-vrmode",
"oculus",
"--verbose",
"--no-yeet",
"--disable-autoupdate",
"--combine-logs",
"editor"
];
fn main() {
let args: Vec<String> = std::env::args().collect();
let executable_path = Path::new(&args[1]);
if executable_path.file_name().unwrap() != EXECUTABLE_NAME {
eprintln!("Executable path is not Beat Saber.exe.");
return;
}
if !executable_path.exists() || !executable_path.is_file() {
eprintln!("Executable path is not valid.");
return;
}
let mut command = Command::new(executable_path);
if let Some(parent_dir) = executable_path.parent() {
command.current_dir(parent_dir);
}
for arg in args.iter().skip(2) {
if !AUTORIZED_ARGS.contains(&arg.as_str()) {
continue;
}
command.arg(arg);
}
command.env("SteamAppId", "620980");
let _ = command.spawn();
}
+2
View File
@@ -0,0 +1,2 @@
[target.x86_64-pc-windows-msvc]
rustflags = ["-C", "target-feature=+crt-static"]
+281
View File
@@ -0,0 +1,281 @@
# 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",
]
+22
View File
@@ -0,0 +1,22 @@
[package]
name = "oculus_symlink_cleaner"
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"
[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"
+9
View File
@@ -0,0 +1,9 @@
extern crate winres;
fn main() {
if cfg!(target_os = "windows") {
let mut res = winres::WindowsResource::new();
res.set_icon("./icon.ico");
res.compile().unwrap();
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

+78
View File
@@ -0,0 +1,78 @@
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())
}
}
+282 -12
View File
@@ -24,6 +24,7 @@
"framer-motion": "^10.16.16",
"fs-extra": "^11.2.0",
"history": "^5.3.0",
"is-elevated": "^3.0.0",
"jszip": "^3.10.1",
"md5-file": "^5.0.0",
"node-abi": "^3.47.0",
@@ -7047,7 +7048,6 @@
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
"integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
"dev": true,
"dependencies": {
"once": "^1.4.0"
}
@@ -10291,6 +10291,125 @@
"node": ">= 10"
}
},
"node_modules/is-admin": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-admin/-/is-admin-3.0.0.tgz",
"integrity": "sha512-wOa3CXFJAu8BZ2BDtG9xYOOrsq6oiSvc2jFPy4X/HINx5bmJUcW8e+apItVbU2E7GIfBVaFVO7Zit4oAWtTJcw==",
"dependencies": {
"execa": "^1.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/is-admin/node_modules/cross-spawn": {
"version": "6.0.5",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
"integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==",
"dependencies": {
"nice-try": "^1.0.4",
"path-key": "^2.0.1",
"semver": "^5.5.0",
"shebang-command": "^1.2.0",
"which": "^1.2.9"
},
"engines": {
"node": ">=4.8"
}
},
"node_modules/is-admin/node_modules/execa": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz",
"integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==",
"dependencies": {
"cross-spawn": "^6.0.0",
"get-stream": "^4.0.0",
"is-stream": "^1.1.0",
"npm-run-path": "^2.0.0",
"p-finally": "^1.0.0",
"signal-exit": "^3.0.0",
"strip-eof": "^1.0.0"
},
"engines": {
"node": ">=6"
}
},
"node_modules/is-admin/node_modules/get-stream": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz",
"integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==",
"dependencies": {
"pump": "^3.0.0"
},
"engines": {
"node": ">=6"
}
},
"node_modules/is-admin/node_modules/is-stream": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
"integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/is-admin/node_modules/npm-run-path": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
"integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==",
"dependencies": {
"path-key": "^2.0.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/is-admin/node_modules/path-key": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz",
"integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==",
"engines": {
"node": ">=4"
}
},
"node_modules/is-admin/node_modules/semver": {
"version": "5.7.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
"integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
"bin": {
"semver": "bin/semver"
}
},
"node_modules/is-admin/node_modules/shebang-command": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
"integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==",
"dependencies": {
"shebang-regex": "^1.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/is-admin/node_modules/shebang-regex": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz",
"integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/is-admin/node_modules/which": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
"integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
"dependencies": {
"isexe": "^2.0.0"
},
"bin": {
"which": "bin/which"
}
},
"node_modules/is-arguments": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz",
@@ -10431,6 +10550,18 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-elevated": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-elevated/-/is-elevated-3.0.0.tgz",
"integrity": "sha512-wjcp6RkouU9jpg55zERl+BglvV5j4jx5c/EMvQ+d12j/+nIEenNWPu+qc0tCg3JkLodbKZMg1qhJzEwG4qjclg==",
"dependencies": {
"is-admin": "^3.0.0",
"is-root": "^2.1.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
@@ -10595,6 +10726,14 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-root": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-root/-/is-root-2.1.0.tgz",
"integrity": "sha512-AGOriNp96vNBd3HtU+RzFEc75FfR5ymiYv8E553I71SCeXBiMsVDUtdio1OEFvrPyLIQ9tVR5RxXIFe5PUFjMg==",
"engines": {
"node": ">=6"
}
},
"node_modules/is-set": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz",
@@ -10760,8 +10899,7 @@
"node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"dev": true
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="
},
"node_modules/isobject": {
"version": "3.0.1",
@@ -13572,6 +13710,11 @@
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
"dev": true
},
"node_modules/nice-try": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
"integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ=="
},
"node_modules/no-case": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz",
@@ -14080,6 +14223,14 @@
"node": ">=8"
}
},
"node_modules/p-finally": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
"integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==",
"engines": {
"node": ">=4"
}
},
"node_modules/p-limit": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
@@ -15389,7 +15540,6 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
"integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
"dev": true,
"dependencies": {
"end-of-stream": "^1.1.0",
"once": "^1.3.1"
@@ -16629,8 +16779,7 @@
"node_modules/signal-exit": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
"dev": true
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="
},
"node_modules/simple-swizzle": {
"version": "0.2.2",
@@ -17091,6 +17240,14 @@
"node": ">=4"
}
},
"node_modules/strip-eof": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
"integrity": "sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/strip-final-newline": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
@@ -24610,7 +24767,6 @@
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
"integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
"dev": true,
"requires": {
"once": "^1.4.0"
}
@@ -26978,6 +27134,94 @@
"integrity": "sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng==",
"dev": true
},
"is-admin": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-admin/-/is-admin-3.0.0.tgz",
"integrity": "sha512-wOa3CXFJAu8BZ2BDtG9xYOOrsq6oiSvc2jFPy4X/HINx5bmJUcW8e+apItVbU2E7GIfBVaFVO7Zit4oAWtTJcw==",
"requires": {
"execa": "^1.0.0"
},
"dependencies": {
"cross-spawn": {
"version": "6.0.5",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
"integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==",
"requires": {
"nice-try": "^1.0.4",
"path-key": "^2.0.1",
"semver": "^5.5.0",
"shebang-command": "^1.2.0",
"which": "^1.2.9"
}
},
"execa": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz",
"integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==",
"requires": {
"cross-spawn": "^6.0.0",
"get-stream": "^4.0.0",
"is-stream": "^1.1.0",
"npm-run-path": "^2.0.0",
"p-finally": "^1.0.0",
"signal-exit": "^3.0.0",
"strip-eof": "^1.0.0"
}
},
"get-stream": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz",
"integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==",
"requires": {
"pump": "^3.0.0"
}
},
"is-stream": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
"integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ=="
},
"npm-run-path": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
"integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==",
"requires": {
"path-key": "^2.0.0"
}
},
"path-key": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz",
"integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw=="
},
"semver": {
"version": "5.7.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
"integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g=="
},
"shebang-command": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
"integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==",
"requires": {
"shebang-regex": "^1.0.0"
}
},
"shebang-regex": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz",
"integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ=="
},
"which": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
"integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
"requires": {
"isexe": "^2.0.0"
}
}
}
},
"is-arguments": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz",
@@ -27073,6 +27317,15 @@
"integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==",
"dev": true
},
"is-elevated": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-elevated/-/is-elevated-3.0.0.tgz",
"integrity": "sha512-wjcp6RkouU9jpg55zERl+BglvV5j4jx5c/EMvQ+d12j/+nIEenNWPu+qc0tCg3JkLodbKZMg1qhJzEwG4qjclg==",
"requires": {
"is-admin": "^3.0.0",
"is-root": "^2.1.0"
}
},
"is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
@@ -27183,6 +27436,11 @@
"has-tostringtag": "^1.0.0"
}
},
"is-root": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-root/-/is-root-2.1.0.tgz",
"integrity": "sha512-AGOriNp96vNBd3HtU+RzFEc75FfR5ymiYv8E553I71SCeXBiMsVDUtdio1OEFvrPyLIQ9tVR5RxXIFe5PUFjMg=="
},
"is-set": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz",
@@ -27294,8 +27552,7 @@
"isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"dev": true
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="
},
"isobject": {
"version": "3.0.1",
@@ -29583,6 +29840,11 @@
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
"dev": true
},
"nice-try": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
"integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ=="
},
"no-case": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz",
@@ -29954,6 +30216,11 @@
"integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==",
"dev": true
},
"p-finally": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
"integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow=="
},
"p-limit": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
@@ -30844,7 +31111,6 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
"integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
"dev": true,
"requires": {
"end-of-stream": "^1.1.0",
"once": "^1.3.1"
@@ -31787,8 +32053,7 @@
"signal-exit": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
"dev": true
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="
},
"simple-swizzle": {
"version": "0.2.2",
@@ -32152,6 +32417,11 @@
"integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==",
"dev": true
},
"strip-eof": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
"integrity": "sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q=="
},
"strip-final-newline": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
+1
View File
@@ -250,6 +250,7 @@
"framer-motion": "^10.16.16",
"fs-extra": "^11.2.0",
"history": "^5.3.0",
"is-elevated": "^3.0.0",
"jszip": "^3.10.1",
"md5-file": "^5.0.0",
"node-abi": "^3.47.0",
+258 -33
View File
@@ -1,17 +1,18 @@
{
"name": "bs-manager",
"version": "1.4.2",
"version": "1.4.6",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "bs-manager",
"version": "1.4.2",
"version": "1.4.6",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"ps-list": "^7.2.0",
"regedit-rs": "^1.0.1",
"query-process": "^0.0.3",
"regedit-rs": "^1.0.2",
"sharp": "^0.32.6"
}
},
@@ -344,6 +345,160 @@
"once": "^1.3.1"
}
},
"node_modules/query-process": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/query-process/-/query-process-0.0.3.tgz",
"integrity": "sha512-6WRue0P+c84abP0o8MkDvigoS/gkkJzsSBt10PsOoBkP7Yodo4DducFsn9XyS4S+LuVYPAE5Hr4+kVNR5Onykg==",
"engines": {
"node": ">= 10"
},
"optionalDependencies": {
"query-process-android-arm-eabi": "0.0.3",
"query-process-android-arm64": "0.0.3",
"query-process-linux-arm-gnueabihf": "0.0.3",
"query-process-linux-arm64-gnu": "0.0.3",
"query-process-linux-arm64-musl": "0.0.3",
"query-process-linux-x64-gnu": "0.0.3",
"query-process-linux-x64-musl": "0.0.3",
"query-process-win32-arm64-msvc": "0.0.3",
"query-process-win32-x64-msvc": "0.0.3"
}
},
"node_modules/query-process-android-arm-eabi": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/query-process-android-arm-eabi/-/query-process-android-arm-eabi-0.0.3.tgz",
"integrity": "sha512-NB+9T+/poBcygDiG+7d2lJSeioP5ZyLT0HwUBdnLgNWvNCpzbJxydup1SLpnt5G2NF6nlTgbUZlH4hvxY7t/bw==",
"cpu": [
"arm"
],
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/query-process-android-arm64": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/query-process-android-arm64/-/query-process-android-arm64-0.0.3.tgz",
"integrity": "sha512-TiAw2yO62vQzM0s02471LToHyUs/PRkLGGlA715z10BPIM5NckzIe245AI45CDyH6tE/3nVHUd0LX/dfjQtTcw==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/query-process-linux-arm-gnueabihf": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/query-process-linux-arm-gnueabihf/-/query-process-linux-arm-gnueabihf-0.0.3.tgz",
"integrity": "sha512-w7NLHBKt7qselCDoh/PVWyeuEb2NiiDDf1A9UB4K3Ns/H24hK3NbfMMBm8mCFxUnFUqoVX8O+VMs7dD/FNlTxA==",
"cpu": [
"arm"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/query-process-linux-arm64-gnu": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/query-process-linux-arm64-gnu/-/query-process-linux-arm64-gnu-0.0.3.tgz",
"integrity": "sha512-nrPHjnSqCBuuN7IZkZWOnBpiJjyKOjfpT0Xt2EjSGp+I7C0CHbnhCL/vG1hBzaWZn0mxJ5nf0c5N0Fja7krnpQ==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/query-process-linux-arm64-musl": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/query-process-linux-arm64-musl/-/query-process-linux-arm64-musl-0.0.3.tgz",
"integrity": "sha512-vk8xMYTnS1PRKhwJgqLKyBAGIFP2CYZW2BsaKuNgQKUrDv7NnBaG7yukZT8niHNO1ycSrFpR/xqeJ8R23hO07A==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/query-process-linux-x64-gnu": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/query-process-linux-x64-gnu/-/query-process-linux-x64-gnu-0.0.3.tgz",
"integrity": "sha512-j1tGcNnGyVCABJ1PHZD1gKuyuomNzsPow3NDuZzUGjPqvKu67RsmKJJJOhMyT1SvUkcqdLFdtS1KTD20I4GS5Q==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/query-process-linux-x64-musl": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/query-process-linux-x64-musl/-/query-process-linux-x64-musl-0.0.3.tgz",
"integrity": "sha512-AOGrrI/Qcb30iKFstiW60zNG/HQevCY0Q38eMHGd7LrBZ5sEN7NzZOjzKp43KeNHejuZBDMxf0gdGAqDznOAng==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/query-process-win32-arm64-msvc": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/query-process-win32-arm64-msvc/-/query-process-win32-arm64-msvc-0.0.3.tgz",
"integrity": "sha512-LHvxbzMFwPPUXHe4bDCAqs1bYbp8j0bMW8XyJGPZnIYrnwHz6g8VE5WQEtXQxSznsbfVxAKJXUtn088EjDVazw==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/query-process-win32-x64-msvc": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/query-process-win32-x64-msvc/-/query-process-win32-x64-msvc-0.0.3.tgz",
"integrity": "sha512-LfHAKmfqyKCgl9K+sTz0KyNwMwB79VcXZUy/Et9iQKLuZt7PDYpHWcFdhrfHw2qqIdKWTED8KmnmsOLhUn/2OA==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/queue-tick": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz",
@@ -377,22 +532,22 @@
}
},
"node_modules/regedit-rs": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/regedit-rs/-/regedit-rs-1.0.1.tgz",
"integrity": "sha512-wsvoGP5JDevIcx3q52aATW1c1dY86bQx7/tJ32gqV+L98OHRJykobKkjYqGzROtz9c2Pr+wGJZr0glFyc2dpvQ==",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/regedit-rs/-/regedit-rs-1.0.2.tgz",
"integrity": "sha512-4vEgiZNO1FCG8z/Zx3v/6PU1+eZ+ELe6R0ca+VB96Vw+Mi3M0IVHAjtMFbl97lUSX11dJqpyousX/wY8QcI1lA==",
"engines": {
"node": ">= 10"
},
"optionalDependencies": {
"regedit-rs-win32-arm64-msvc": "1.0.1",
"regedit-rs-win32-ia32-msvc": "1.0.1",
"regedit-rs-win32-x64-msvc": "1.0.1"
"regedit-rs-win32-arm64-msvc": "1.0.2",
"regedit-rs-win32-ia32-msvc": "1.0.2",
"regedit-rs-win32-x64-msvc": "1.0.2"
}
},
"node_modules/regedit-rs-win32-arm64-msvc": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/regedit-rs-win32-arm64-msvc/-/regedit-rs-win32-arm64-msvc-1.0.1.tgz",
"integrity": "sha512-QA46d/wiUHlW2h0ZlHKrtDhnwoxMpVle+EbFziEbLYC7dimtAiWCeXu4zNymaFClDimYSifQkWgza5GBmD8etg==",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/regedit-rs-win32-arm64-msvc/-/regedit-rs-win32-arm64-msvc-1.0.2.tgz",
"integrity": "sha512-hM1sxazOJWKmiC9DM8QXW9Iqm50Mh/Y9G4/rRYQpWXjMzq7lTqjwVZRkAoBrHliFS6d1Lt4qkm5+Ybt6GkbDpw==",
"cpu": [
"arm64"
],
@@ -405,9 +560,9 @@
}
},
"node_modules/regedit-rs-win32-ia32-msvc": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/regedit-rs-win32-ia32-msvc/-/regedit-rs-win32-ia32-msvc-1.0.1.tgz",
"integrity": "sha512-V2NisvicQqiozl0yMas9tjDYmBwHWf48M/dMJUWMQxxkJaDCNoHW8PljL59A/h8ew2NBFsJK5RYkYfxW1o+OyQ==",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/regedit-rs-win32-ia32-msvc/-/regedit-rs-win32-ia32-msvc-1.0.2.tgz",
"integrity": "sha512-FLINrCJ30wm6NYw7skQUDET8NP1N46kH77dqesCiU+/FjWzzPE5luZYY+j4uf+hKjPY6/MCj2CB9l9VdPhaBVQ==",
"cpu": [
"ia32"
],
@@ -420,9 +575,9 @@
}
},
"node_modules/regedit-rs-win32-x64-msvc": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/regedit-rs-win32-x64-msvc/-/regedit-rs-win32-x64-msvc-1.0.1.tgz",
"integrity": "sha512-qhQawjYPzXGrz0xODJiviwrzw0eytGP9+quuQJUQAiLEw2P1tQ0LCyID3jOvNltLH+tqJChSV4JaQcPVleohZg==",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/regedit-rs-win32-x64-msvc/-/regedit-rs-win32-x64-msvc-1.0.2.tgz",
"integrity": "sha512-ccCSyd5vWBKVWftBKLKzegqwwPMWcQtIW0ub66dCFFuv2s+x2EcZZWGdD9dVXX2Z6V9DU2JRPKgWUNjVPaj6Xg==",
"cpu": [
"x64"
],
@@ -851,6 +1006,76 @@
"once": "^1.3.1"
}
},
"query-process": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/query-process/-/query-process-0.0.3.tgz",
"integrity": "sha512-6WRue0P+c84abP0o8MkDvigoS/gkkJzsSBt10PsOoBkP7Yodo4DducFsn9XyS4S+LuVYPAE5Hr4+kVNR5Onykg==",
"requires": {
"query-process-android-arm-eabi": "0.0.3",
"query-process-android-arm64": "0.0.3",
"query-process-linux-arm-gnueabihf": "0.0.3",
"query-process-linux-arm64-gnu": "0.0.3",
"query-process-linux-arm64-musl": "0.0.3",
"query-process-linux-x64-gnu": "0.0.3",
"query-process-linux-x64-musl": "0.0.3",
"query-process-win32-arm64-msvc": "0.0.3",
"query-process-win32-x64-msvc": "0.0.3"
}
},
"query-process-android-arm-eabi": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/query-process-android-arm-eabi/-/query-process-android-arm-eabi-0.0.3.tgz",
"integrity": "sha512-NB+9T+/poBcygDiG+7d2lJSeioP5ZyLT0HwUBdnLgNWvNCpzbJxydup1SLpnt5G2NF6nlTgbUZlH4hvxY7t/bw==",
"optional": true
},
"query-process-android-arm64": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/query-process-android-arm64/-/query-process-android-arm64-0.0.3.tgz",
"integrity": "sha512-TiAw2yO62vQzM0s02471LToHyUs/PRkLGGlA715z10BPIM5NckzIe245AI45CDyH6tE/3nVHUd0LX/dfjQtTcw==",
"optional": true
},
"query-process-linux-arm-gnueabihf": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/query-process-linux-arm-gnueabihf/-/query-process-linux-arm-gnueabihf-0.0.3.tgz",
"integrity": "sha512-w7NLHBKt7qselCDoh/PVWyeuEb2NiiDDf1A9UB4K3Ns/H24hK3NbfMMBm8mCFxUnFUqoVX8O+VMs7dD/FNlTxA==",
"optional": true
},
"query-process-linux-arm64-gnu": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/query-process-linux-arm64-gnu/-/query-process-linux-arm64-gnu-0.0.3.tgz",
"integrity": "sha512-nrPHjnSqCBuuN7IZkZWOnBpiJjyKOjfpT0Xt2EjSGp+I7C0CHbnhCL/vG1hBzaWZn0mxJ5nf0c5N0Fja7krnpQ==",
"optional": true
},
"query-process-linux-arm64-musl": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/query-process-linux-arm64-musl/-/query-process-linux-arm64-musl-0.0.3.tgz",
"integrity": "sha512-vk8xMYTnS1PRKhwJgqLKyBAGIFP2CYZW2BsaKuNgQKUrDv7NnBaG7yukZT8niHNO1ycSrFpR/xqeJ8R23hO07A==",
"optional": true
},
"query-process-linux-x64-gnu": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/query-process-linux-x64-gnu/-/query-process-linux-x64-gnu-0.0.3.tgz",
"integrity": "sha512-j1tGcNnGyVCABJ1PHZD1gKuyuomNzsPow3NDuZzUGjPqvKu67RsmKJJJOhMyT1SvUkcqdLFdtS1KTD20I4GS5Q==",
"optional": true
},
"query-process-linux-x64-musl": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/query-process-linux-x64-musl/-/query-process-linux-x64-musl-0.0.3.tgz",
"integrity": "sha512-AOGrrI/Qcb30iKFstiW60zNG/HQevCY0Q38eMHGd7LrBZ5sEN7NzZOjzKp43KeNHejuZBDMxf0gdGAqDznOAng==",
"optional": true
},
"query-process-win32-arm64-msvc": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/query-process-win32-arm64-msvc/-/query-process-win32-arm64-msvc-0.0.3.tgz",
"integrity": "sha512-LHvxbzMFwPPUXHe4bDCAqs1bYbp8j0bMW8XyJGPZnIYrnwHz6g8VE5WQEtXQxSznsbfVxAKJXUtn088EjDVazw==",
"optional": true
},
"query-process-win32-x64-msvc": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/query-process-win32-x64-msvc/-/query-process-win32-x64-msvc-0.0.3.tgz",
"integrity": "sha512-LfHAKmfqyKCgl9K+sTz0KyNwMwB79VcXZUy/Et9iQKLuZt7PDYpHWcFdhrfHw2qqIdKWTED8KmnmsOLhUn/2OA==",
"optional": true
},
"queue-tick": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz",
@@ -878,31 +1103,31 @@
}
},
"regedit-rs": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/regedit-rs/-/regedit-rs-1.0.1.tgz",
"integrity": "sha512-wsvoGP5JDevIcx3q52aATW1c1dY86bQx7/tJ32gqV+L98OHRJykobKkjYqGzROtz9c2Pr+wGJZr0glFyc2dpvQ==",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/regedit-rs/-/regedit-rs-1.0.2.tgz",
"integrity": "sha512-4vEgiZNO1FCG8z/Zx3v/6PU1+eZ+ELe6R0ca+VB96Vw+Mi3M0IVHAjtMFbl97lUSX11dJqpyousX/wY8QcI1lA==",
"requires": {
"regedit-rs-win32-arm64-msvc": "1.0.1",
"regedit-rs-win32-ia32-msvc": "1.0.1",
"regedit-rs-win32-x64-msvc": "1.0.1"
"regedit-rs-win32-arm64-msvc": "1.0.2",
"regedit-rs-win32-ia32-msvc": "1.0.2",
"regedit-rs-win32-x64-msvc": "1.0.2"
}
},
"regedit-rs-win32-arm64-msvc": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/regedit-rs-win32-arm64-msvc/-/regedit-rs-win32-arm64-msvc-1.0.1.tgz",
"integrity": "sha512-QA46d/wiUHlW2h0ZlHKrtDhnwoxMpVle+EbFziEbLYC7dimtAiWCeXu4zNymaFClDimYSifQkWgza5GBmD8etg==",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/regedit-rs-win32-arm64-msvc/-/regedit-rs-win32-arm64-msvc-1.0.2.tgz",
"integrity": "sha512-hM1sxazOJWKmiC9DM8QXW9Iqm50Mh/Y9G4/rRYQpWXjMzq7lTqjwVZRkAoBrHliFS6d1Lt4qkm5+Ybt6GkbDpw==",
"optional": true
},
"regedit-rs-win32-ia32-msvc": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/regedit-rs-win32-ia32-msvc/-/regedit-rs-win32-ia32-msvc-1.0.1.tgz",
"integrity": "sha512-V2NisvicQqiozl0yMas9tjDYmBwHWf48M/dMJUWMQxxkJaDCNoHW8PljL59A/h8ew2NBFsJK5RYkYfxW1o+OyQ==",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/regedit-rs-win32-ia32-msvc/-/regedit-rs-win32-ia32-msvc-1.0.2.tgz",
"integrity": "sha512-FLINrCJ30wm6NYw7skQUDET8NP1N46kH77dqesCiU+/FjWzzPE5luZYY+j4uf+hKjPY6/MCj2CB9l9VdPhaBVQ==",
"optional": true
},
"regedit-rs-win32-x64-msvc": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/regedit-rs-win32-x64-msvc/-/regedit-rs-win32-x64-msvc-1.0.1.tgz",
"integrity": "sha512-qhQawjYPzXGrz0xODJiviwrzw0eytGP9+quuQJUQAiLEw2P1tQ0LCyID3jOvNltLH+tqJChSV4JaQcPVleohZg==",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/regedit-rs-win32-x64-msvc/-/regedit-rs-win32-x64-msvc-1.0.2.tgz",
"integrity": "sha512-ccCSyd5vWBKVWftBKLKzegqwwPMWcQtIW0ub66dCFFuv2s+x2EcZZWGdD9dVXX2Z6V9DU2JRPKgWUNjVPaj6Xg==",
"optional": true
},
"safe-buffer": {
+3 -2
View File
@@ -1,6 +1,6 @@
{
"name": "bs-manager",
"version": "1.4.3",
"version": "1.4.8",
"description": "BSManager",
"main": "./dist/main/main.js",
"author": {
@@ -15,7 +15,8 @@
},
"dependencies": {
"ps-list": "^7.2.0",
"regedit-rs": "^1.0.1",
"query-process": "^0.0.3",
"regedit-rs": "^1.0.2",
"sharp": "^0.32.6"
},
"license": "MIT"
+67
View File
@@ -0,0 +1,67 @@
import log from "electron-log";
import { isPromise } from "../../shared/helpers/promise.helpers";
import { tryit } from "../../shared/helpers/error.helpers";
type LogOptions = {
logInput?: boolean;
logOutput?: boolean;
logArgs?: boolean;
};
const stringifyArgs = (args: unknown[]) => {
return args?.map(a => JSON.stringify(a)).join(', ');
};
const logInfo = (message: string, propertyKey: string, args: unknown[], logArgs: boolean, result: unknown) => {
log.info(`[${message}] ${propertyKey}(${logArgs ? stringifyArgs(args) : ''})`, result);
};
const logError = (propertyKey: string, args: unknown[], logArgs: boolean, error: unknown) => {
log.error(`[ERROR] ${propertyKey}(${logArgs ? stringifyArgs(args) : ''})`, error);
throw error;
};
export function Log(options?: LogOptions){
const logInput = options?.logInput ?? false;
const logOutput = options?.logOutput ?? true;
const logArgs = options?.logArgs ?? true;
return (target: unknown, propertyKey: string, descriptor: PropertyDescriptor) => {
const originalMethod = descriptor.value;
descriptor.value = function(...args: unknown[]) {
if (logInput) {
logInfo('INPUT', propertyKey, args, logArgs, null);
}
const outcome = tryit(() => originalMethod.apply(this, args));
if (isPromise(outcome)) {
return outcome.then(({ result, error }) => {
if (error) {
logError(propertyKey, args, logArgs, error);
}
if (logOutput) {
logInfo('OUTPUT', propertyKey, args, logArgs, result);
}
return result;
});
}
const { result, error } = outcome;
if (error) {
logError(propertyKey, args, logArgs, error);
}
if (logOutput) {
logInfo('OUTPUT', propertyKey, args, logArgs, result);
}
return result;
};
};
}
+13 -1
View File
@@ -10,4 +10,16 @@ export async function taskRunning(task: string): Promise<boolean> {
log.error(error);
return false;
}
}
}
export async function getProcessPid(task: string): Promise<number> {
try {
const processes = await psList();
const process = processes.find(process => process.name?.includes(task) || process.cmd?.includes(task));
return process?.pid;
}
catch(error){
log.error(error);
return null;
}
}
+14 -7
View File
@@ -5,6 +5,9 @@ import { IpcService } from '../services/ipc.service';
import { from } from "rxjs";
import { SteamLauncherService } from "../services/bs-launcher/steam-launcher.service";
import { OculusLauncherService } from "../services/bs-launcher/oculus-launcher.service";
import { SteamService } from "../services/steam.service";
import log from "electron-log";
import isElevated from "is-elevated";
const ipc = IpcService.getInstance();
@@ -13,6 +16,17 @@ ipc.on<LaunchOption>('bs-launch.launch', (req, reply) => {
reply(bsLauncher.launch(req.args));
});
ipc.on<boolean>("bs-launch.need-start-as-admin", (_, reply) => {
const steam = SteamService.getInstance();
reply(from(isElevated().then(elevated => {
if(elevated){ return false; }
return steam.isElevated().catch(e => {
log.error("Error while checking if Steam is running as admin", e);
return false;
});
})));
});
ipc.on<LaunchOption>("create-launch-shortcut", (req, reply) => {
const bsLauncher = BSLauncherService.getInstance();
reply(from(bsLauncher.createLaunchShortcut(req.args)));
@@ -23,10 +37,3 @@ ipc.on<void>("bs-launch.restore-steamvr", (_, reply) => {
const steamLauncher = SteamLauncherService.getInstance();
reply(from(steamLauncher.restoreSteamVR()));
});
ipc.on<void>("restore-original-oculus-folder", (_, reply) => {
const oculusLauncher = OculusLauncherService.getInstance();
reply(from(
oculusLauncher.deleteBsSymlinks().then(() => oculusLauncher.restoreOriginalBeatSaber())
));
})
+134 -11
View File
@@ -21,16 +21,20 @@ import { BSLauncherService } from "./services/bs-launcher/bs-launcher.service";
import { IpcRequest } from "shared/models/ipc";
import { LivShortcut } from "./services/liv/liv-shortcut.service";
import { SteamLauncherService } from "./services/bs-launcher/steam-launcher.service";
import { readdirSync, statSync, unlinkSync } from "fs-extra";
export const filterStrings = new Set<string>();
export const filterPatterns = new Set<RegExp>();
const isDebug = process.env.NODE_ENV === "development" || process.env.DEBUG_PROD === "true";
log.transports.file.level = "info";
log.transports.file.resolvePath = () => {
const now = new Date();
return path.join(app.getPath("logs"), `${now.getFullYear()}-${now.getMonth() + 1}-${now.getDate()}-v${app.getVersion()}.log`);
};
// Filter all occulus tokens
filterPatterns.add(/FRL\S{10,}/g);
initLogger();
deleteOlestLogs();
deleteOldLogs();
log.catchErrors();
if (process.env.NODE_ENV === "production") {
const sourceMapSupport = require("source-map-support");
@@ -90,11 +94,11 @@ if (!gotTheLock) {
})
app.whenReady().then(() => {
app.setAppUserModelId(APP_NAME);
initServicesMustBeInitialized();
const deepLink = process.argv.find(arg => DeepLinkService.getInstance().isDeepLink(arg));
if (!deepLink) {
@@ -102,13 +106,132 @@ if (!gotTheLock) {
} else {
DeepLinkService.getInstance().dispatchLinkOpened(deepLink);
}
SteamLauncherService.getInstance().restoreSteamVR();
// Log renderer errors
ipcMain.on("log-error", (_, args: IpcRequest<unknown>) => {
log.error(args?.args);
});
ipcMain.on("add-filter-string", (_, args: IpcRequest<string>) => {
filterStrings.add(args?.args);
});
ipcMain.on("add-filter-pattern", (_, args: IpcRequest<string>) => {
filterPatterns.add(new RegExp(args?.args));
});
}).catch(log.error);
}
function initLogger(){
log.transports.file.level = "info";
log.transports.file.resolvePath = () => {
const now = new Date();
return path.join(app.getPath("logs"), `${now.getFullYear()}-${now.getMonth() + 1}-${now.getDate()}-v${app.getVersion()}.log`);
};
log.hooks.push((message) => {
const filterMessage = (filter: string|RegExp, ...param: unknown[]): unknown[] => {
return param.map(data => {
if(typeof data === "string"){
return data.replaceAll(filter, "****");
}
if(data instanceof Error){
data.message = data.message?.replaceAll(filter, "****");
data.stack = data.stack?.replaceAll(filter, "****");
}
if(data instanceof Array){
return filterMessage(filter, ...data);
}
return data;
});
}
filterStrings.forEach(filter => {
if(filter && message.data.length){
message.data = filterMessage(filter, ...message.data);
}
});
filterPatterns.forEach(filter => {
if(filter && message.data.length){
message.data = filterMessage(filter, ...message.data);
}
});
return message;
});
log.catchErrors();
}
function getLogFilesEntries() {
try {
const logsFolder = app.getPath("logs");
let logs = readdirSync(logsFolder, { withFileTypes: true });
logs = logs.filter(file => file.isFile() && path.extname(file.name) === ".log");
logs.sort((a, b) => {
const aStat = statSync(path.join(logsFolder, a.name));
const bStat = statSync(path.join(logsFolder, b.name));
return bStat.mtime.getTime() - aStat.mtime.getTime();
});
return logs.map(file => {
const filePath = path.join(logsFolder, file.name);
const stat = statSync(filePath);
return {
path: filePath,
name: file.name,
stats: stat
};
});
} catch (err) {
log.error('Error while retrieving log files entries:', err);
return [];
}
}
// keep only the last 5 logs
function deleteOldLogs(): void{
try {
let logs = getLogFilesEntries();
logs = logs.slice(5);
logs.forEach(file => {
try {
unlinkSync(file.path);
log.info(`Deleted log file: ${file.path}`);
} catch (err) {
log.error(`Error deleting file ${file.path}:`, err);
}
});
} catch (err) {
log.error("Error while deleting old logs:", err);
}
}
// Temporary function to delete logs before 2024-07-31
function deleteOlestLogs(): void{
// delete all logs before 2024-07-31
const date = new Date(2024, 6, 31); // month is 0-based
const logs = getLogFilesEntries().filter(file => file.stats.mtime.getTime() < date.getTime());
logs.forEach(file => {
try {
unlinkSync(file.path);
log.info(`Deleted log file: ${file.path}`);
} catch (err) {
log.error(`Error deleting file ${file.path}:`, err);
}
});
}
@@ -5,9 +5,9 @@ import path from "path";
import log from "electron-log";
export abstract class AbstractLauncherService {
protected readonly localVersions = BSLocalVersionService.getInstance();
constructor(){
this.localVersions = BSLocalVersionService.getInstance();
}
@@ -46,18 +46,20 @@ export abstract class AbstractLauncherService {
}
protected launchBs(bsExePath: string, args: string[], options?: SpawnOptionsWithoutStdio): Promise<number> {
return new Promise<number>((resolve, reject) => {
const bsProcess = this.launchBSProcess(bsExePath, args, options);
protected launchBs(bsExePath: string, args: string[], options?: SpawnOptionsWithoutStdio): {process: ChildProcessWithoutNullStreams, exit: Promise<number>} {
const process = this.launchBSProcess(bsExePath, args, options);
bsProcess.on("error", reject);
bsProcess.on("exit", resolve);
setTimeout(() => {
bsProcess.removeAllListeners("error");
bsProcess.removeAllListeners("exit");
resolve(-1);
}, 30_000);
const exit = new Promise<number>((resolve, reject) => {
process.on("error", (err) => {
log.error(`Error while launching BS`, err);
reject(err);
});
process.on("exit", (code) => {
log.info(`BS process exit with code ${code}`);
resolve(code);
});
});
return { process, exit };
}
}
}
@@ -11,6 +11,10 @@ import { AbstractLauncherService } from "./abstract-launcher.service";
import { taskRunning } 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 {
@@ -25,6 +29,7 @@ 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>();
@@ -32,6 +37,7 @@ export class OculusLauncherService extends AbstractLauncherService implements St
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){
@@ -61,13 +67,13 @@ export class OculusLauncherService extends AbstractLauncherService implements St
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);
@@ -86,7 +92,7 @@ export class OculusLauncherService extends AbstractLauncherService implements St
private async backupOriginalBeatSaber(): Promise<void>{
const bsFolder = await this.oculus.getGameFolder(OCULUS_BS_DIR);
if(!bsFolder){ return; }
const backupPath = path.join(bsFolder, "..", OCULUS_BS_BACKUP_DIR);
const backupPath = await ensurePathNotAlreadyExist(path.join(bsFolder, "..", OCULUS_BS_BACKUP_DIR));
log.info("Backing up original Beat Saber", bsFolder, backupPath);
return rename(bsFolder, backupPath);
}
@@ -99,6 +105,15 @@ export class OculusLauncherService extends AbstractLauncherService implements St
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 () => {
@@ -138,21 +153,34 @@ export class OculusLauncherService extends AbstractLauncherService implements St
if(bsRunning){
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());
// Launch Beat Saber
const exePath = path.join(bsPath, BS_EXECUTABLE);
if(!(await pathExists(exePath))){
throw CustomError.fromError(new Error(`BS Path not exist ${bsPath}`), BSLaunchError.BS_NOT_FOUND);
}
// Make sure Oculus is running
await this.oculus.startOculus().catch(err => log.error("Error while starting Oculus", err));
obs.next({type: BSLaunchEvent.BS_LAUNCHING});
return this.launchBs(exePath, this.buildBsLaunchArgs(launchOptions)).catch(err => {
// 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);
});
@@ -170,4 +198,4 @@ export class OculusLauncherService extends AbstractLauncherService implements St
});
}
}
}
@@ -8,6 +8,9 @@ import { BS_APP_ID, BS_EXECUTABLE, STEAMVR_APP_ID } from "../../constants";
import log from "electron-log";
import { AbstractLauncherService } from "./abstract-launcher.service";
import { CustomError } from "../../../shared/models/exceptions/custom-error.class";
import isElevated from "is-elevated";
import { UtilsService } from "../utils.service";
import { exec } from "child_process";
export class SteamLauncherService extends AbstractLauncherService implements StoreLauncherInterface{
@@ -21,10 +24,12 @@ export class SteamLauncherService extends AbstractLauncherService implements Sto
}
private readonly steam: SteamService;
private readonly util: UtilsService;
private constructor(){
super();
this.steam = SteamService.getInstance();
this.util = UtilsService.getInstance();
}
private getSteamVRPath(): Promise<string> {
@@ -41,6 +46,17 @@ export class SteamLauncherService extends AbstractLauncherService implements Sto
});
}
private needStartBsAsAdmin(): Promise<boolean> {
return isElevated().then(elevated => {
if(elevated){ return false; }
return this.steam.isElevated();
})
}
private getStartBsAsAdminExePath(): string {
return path.join(this.util.getAssetsScriptsPath(), "start_beat_saber_admin.exe");
}
public async restoreSteamVR(): Promise<void> {
const steamVrFolder = await this.getSteamVRPath();
const steamVrBackup = `${steamVrFolder}.bak`;
@@ -53,7 +69,7 @@ export class SteamLauncherService extends AbstractLauncherService implements Sto
log.error("Error while restoring SteamVR", err);
});
}
public launch(launchOptions: LaunchOption): Observable<BSLaunchEventData>{
return new Observable<BSLaunchEventData>(obs => {(async () => {
@@ -90,13 +106,33 @@ export class SteamLauncherService extends AbstractLauncherService implements Sto
obs.next({type: BSLaunchEvent.BS_LAUNCHING});
await this.launchBs(exePath, launchArgs, { env: {...process.env, "SteamAppId": BS_APP_ID} }).then(exitCode => {
const launchPromise = !launchOptions.admin ? (
this.launchBs(exePath, launchArgs, { env: {...process.env, "SteamAppId": BS_APP_ID} }).exit
) : (
new Promise<number>(resolve => {
const adminProcess = exec(`"${this.getStartBsAsAdminExePath()}" "${exePath}" ${launchArgs.join(" ")}`, { env: {...process.env, "SteamAppId": BS_APP_ID} });
adminProcess.on("error", err => {
log.error("Error while starting BS as Admin", err);
resolve(-1)
});
setTimeout(() => {
adminProcess.removeAllListeners("error");
resolve(-1);
}, 35_000);
})
);
try {
const exitCode = await launchPromise;
log.info("BS process exit code", exitCode);
}).catch(err => {
}
catch(err: any) {
throw CustomError.fromError(err, BSLaunchError.BS_EXIT_ERROR);
}).finally(() => {
this.restoreSteamVR().catch(log.error);
});
}
finally {
await this.restoreSteamVR().catch(log.error);
}
})().then(() => {
obs.complete();
@@ -109,4 +145,4 @@ export class SteamLauncherService extends AbstractLauncherService implements Sto
})});
}
}
}
@@ -2,6 +2,8 @@ import path from "path";
import { app } from "electron";
import ElectronStore from "electron-store";
import { copyDirectoryWithJunctions, deleteFolder, ensureFolderExist, pathExist } from "../helpers/fs.helpers";
import { tryit } from "../../shared/helpers/error.helpers";
import { pathExistsSync } from "fs-extra";
export class InstallationLocationService {
private static instance: InstallationLocationService;
@@ -67,8 +69,8 @@ export class InstallationLocationService {
return this.installPathConfig.get(this.STORE_INSTALLATION_PATH_KEY) as string;
}
const oldPath = path.join(app.getPath("documents"), this.INSTALLATION_FOLDER);
if(await pathExist(oldPath)){
const { result: oldPath } = tryit(() => path.join(app.getPath("documents"), this.INSTALLATION_FOLDER));
if(oldPath && pathExistsSync(oldPath)){
return app.getPath("documents");
}
+9 -2
View File
@@ -1,6 +1,8 @@
import { execOnOs } from "../../helpers/env.helpers";
import { list, createKey, putValue, deleteKey, RegSzValue } from "regedit-rs";
import path from "path";
import { Log } from "../../decorators/log.decorator";
const { list, createKey, putValue, deleteKey, RegSzValue } = (execOnOs({ win32: () => require("regedit-rs") }, true) ?? {}) as typeof import("regedit-rs");
export class LivService {
@@ -20,6 +22,7 @@ export class LivService {
}
@Log()
public async isLivInstalled(): Promise<boolean> {
return execOnOs({
win32: async () => {
@@ -29,6 +32,7 @@ export class LivService {
}, true);
}
@Log()
public async createLivShortcut(entry: LivEntry): Promise<void> {
return execOnOs({
win32: async () => {
@@ -46,6 +50,7 @@ export class LivService {
});
}
@Log()
public async deleteLivShortcuts(ids: string[]): Promise<void> {
return execOnOs({
win32: async () => {
@@ -55,11 +60,13 @@ export class LivService {
})
}
@Log()
public getLivShortcuts(): Promise<LivEntry[]> {
return execOnOs({
win32: async () => {
const regRes = await list(this.livExternalAppsRegeditKey).then(res => res[this.livExternalAppsRegeditKey]);
if(!regRes.exists){
return [];
}
+33 -5
View File
@@ -1,9 +1,14 @@
import { list } from "regedit-rs";
import path from "path";
import { pathExist, resolveGUIDPath } from "../helpers/fs.helpers";
import log from "electron-log";
import { lstat } from "fs-extra";
import { tryit } from "../../shared/helpers/error.helpers";
import { shell } from "electron";
import { taskRunning } from "../helpers/os.helpers";
import { sToMs } from "../../shared/helpers/time.helpers";
import { execOnOs } from "../helpers/env.helpers";
const { list } = (execOnOs({ win32: () => require("regedit-rs") }, true) ?? {}) as typeof import("regedit-rs");
export class OculusService {
private static instance: OculusService;
@@ -42,11 +47,11 @@ export class OculusService {
const libsPath: OculusLibrary[] = (
await Promise.all(libsRegData.keys.map(async key => {
const originalPath = await list([`${oculusLibsRegKey}\\${key}`]).then(res => res[`${oculusLibsRegKey}\\${key}`]);
if (originalPath.values?.OriginalPath) {
return { id: key, path: originalPath.values.OriginalPath.value, isDefault: defaultLibraryId === key } as OculusLibrary;
}
}
if(originalPath.values?.Path) {
const { result } = tryit(() => resolveGUIDPath(originalPath.values.Path.value as string));
return result ? { id: key, path: result, isDefault: defaultLibraryId === key } as OculusLibrary : null;
@@ -82,7 +87,7 @@ export class OculusService {
/**
* Return the first game folder found in the list
* @param {string[]} gameFolders
* @param {string[]} gameFolders
*/
public async tryGetGameFolder(gameFolders: string[]): Promise<string> {
for(const gameFolder of gameFolders){
@@ -92,6 +97,29 @@ export class OculusService {
return null;
}
public oculusRunning(): Promise<boolean> {
return taskRunning("OculusClient");
}
public async startOculus(): Promise<void>{
if(await this.oculusRunning()){ return; }
await shell.openPath("oculus://view/homepage");
return new Promise((resolve, reject) => {
const interval = setInterval(async () => {
if(!(await this.oculusRunning())){ return; }
clearInterval(interval);
resolve();
}, sToMs(3));
setTimeout(() => {
clearInterval(interval);
reject(new Error("Unable to open Oculus"));
}, sToMs(30));
});
}
}
export interface OculusLibrary {
+26 -7
View File
@@ -1,9 +1,11 @@
import { Agent, get } from "https";
import { Agent, RequestOptions, get } from "https";
import { createWriteStream, unlink } from "fs";
import { Progression } from "main/helpers/fs.helpers";
import { Observable, shareReplay, tap } from "rxjs";
import log from "electron-log";
import fetch, { RequestInfo, RequestInit } from "node-fetch";
import { app } from "electron";
import os from "os";
export class RequestService {
private static instance: RequestService;
@@ -15,16 +17,33 @@ export class RequestService {
return RequestService.instance;
}
private constructor() {}
private readonly defaultRequestInit: RequestInit;
private get ipv4Agent(){
return new Agent({ family: 4 });
private constructor() {
this.defaultRequestInit = {
headers: {
"User-Agent": `BSManager/${app.getVersion()} (${os.type()} ${os.release()})`
},
agent: new Agent({ family: 4 }),
};
}
private getInitWithOptions(options?: RequestInit): RequestInit {
return { ...this.defaultRequestInit, ...(options || {}) };
}
private requestOptionsFromDefaultInit(): RequestOptions {
return {
headers: this.defaultRequestInit.headers as Record<string, string>,
agent: this.defaultRequestInit.agent as Agent,
};
}
public async getJSON<T = unknown>(url: RequestInfo, options?: RequestInit): Promise<T> {
try {
const response = await fetch(url, {...options, agent: this.ipv4Agent});
const response = await fetch(url, this.getInitWithOptions(options));
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status} ${url}`);
@@ -50,7 +69,7 @@ export class RequestService {
});
file.on("error", err => unlink(dest, () => subscriber.error(err)));
const req = get(url, { agent: this.ipv4Agent }, res => {
const req = get(url, this.requestOptionsFromDefaultInit(), res => {
progress.total = parseInt(res.headers?.["content-length"] || "0", 10);
res.on("data", chunk => {
@@ -77,7 +96,7 @@ export class RequestService {
const allChunks: Buffer[] = [];
const req = get(url, { agent: this.ipv4Agent }, res => {
const req = get(url, this.requestOptionsFromDefaultInit(), res => {
progress.total = parseInt(res.headers?.["content-length"] || "0", 10);
res.on("data", chunk => {
+31 -6
View File
@@ -1,15 +1,22 @@
import { list, RegDwordValue } from "regedit-rs"
import { RegDwordValue } from "regedit-rs"
import path from "path";
import { parse } from "@node-steam/vdf";
import { readFile } from "fs/promises";
import { pathExist } from "../helpers/fs.helpers";
import log from "electron-log";
import { app, shell } from "electron";
import { taskRunning } from "../helpers/os.helpers";
import { getProcessPid, taskRunning } from "../helpers/os.helpers";
import { isElevated } from "query-process";
import { execOnOs } from "../helpers/env.helpers";
const { list } = (execOnOs({ win32: () => require("regedit-rs") }, true) ?? {}) as typeof import("regedit-rs");
export class SteamService {
private static readonly PROCESS_NAME = "steam";
private static instance: SteamService;
private steamPath: string = '';
private constructor(){}
@@ -29,12 +36,30 @@ export class SteamService {
}
public async steamRunning(): Promise<boolean>{
const steamProcessRunning = await taskRunning("steam");
const steamProcessRunning = await taskRunning(SteamService.PROCESS_NAME);
if(process.platform === "linux") { return steamProcessRunning; }
const activeUser = await this.getActiveUser().catch(err => log.error(err));
return steamProcessRunning && !!activeUser;
}
public async getSteamPid(): Promise<number>{
return getProcessPid(SteamService.PROCESS_NAME);
}
/**
* Check if the Steam process is running as administrator
* @throws Can throw an error if the Steam process is running as admin
* @returns true if the Steam process is running as administrator
*/
public async isElevated(): Promise<boolean>{
if(process.platform === "linux"){ return true; }
const steamPid = await this.getSteamPid();
if(!steamPid){ return false; }
return isElevated(steamPid);
}
public async getSteamPath(): Promise<string>{
if(this.steamPath){ return this.steamPath; }
@@ -68,7 +93,7 @@ export class SteamService {
let libraryFolders: any = path.join(steamPath, "steamapps", "libraryfolders.vdf");
if (!(await pathExist(libraryFolders))) { return null; }
libraryFolders = parse(await readFile(libraryFolders, { encoding: "utf-8" }));
if (!libraryFolders.libraryfolders) { return null; }
@@ -83,7 +108,7 @@ export class SteamService {
}
return null;
} catch (e) {
log.error(e);
return null;
@@ -91,7 +116,7 @@ export class SteamService {
}
public async openSteam(): Promise<void> {
await shell.openPath("steam://open/games");
return new Promise((resolve, reject) => {
@@ -11,6 +11,7 @@ import { useTranslation } from "renderer/hooks/use-translation.hook";
import { MAP_SPECIFICITIES } from "renderer/partials/maps/map-general/map-specificity";
import { MAP_REQUIREMENTS } from "renderer/partials/maps/map-requirements/map-requirements";
import { MAP_DIFFICULTIES_COLORS } from "renderer/partials/maps/map-difficulties/map-difficulties-colors";
import { MapExclude, MAP_EXCLUDES } from "renderer/partials/maps/map-excludes/map-excludes";
import { BsmButton } from "../shared/bsm-button.component";
import equal from "fast-deep-equal/es6";
import clone from "rfdc";
@@ -21,12 +22,13 @@ export type Props = {
ref?: MutableRefObject<undefined>;
playlist?: boolean;
filter: MapFilter;
localData?: boolean;
onChange?: (filter: MapFilter) => void;
onApply?: (filter: MapFilter) => void;
onClose?: (filter: MapFilter) => void;
};
export function FilterPanel({ className, ref, playlist = false, filter, onChange, onApply, onClose }: Props) {
export function FilterPanel({ className, ref, playlist = false, filter, localData = true, onChange, onApply, onClose }: Props) {
const t = useTranslation();
const [haveChanged, setHaveChanged] = useState(false);
@@ -147,6 +149,10 @@ export function FilterPanel({ className, ref, playlist = false, filter, onChange
return t(`maps.map-specificities.${specificity}`);
};
const translateMapExclude = (exclude: MapExclude): string => {
return t(`maps.map-excludes.${exclude}`);
};
type BooleanKeys<T> = { [k in keyof T]: T[k] extends boolean ? k : never }[keyof T];
const handleCheckbox = (key: BooleanKeys<MapFilter>) => {
@@ -189,6 +195,17 @@ export function FilterPanel({ className, ref, playlist = false, filter, onChange
<span className="grow capitalize">{requirement}</span>
</div>
))}
{ !localData && (
<>
<h2 className="my-1 uppercase text-sm">{t("maps.map-filter-panel.exclude")}</h2>
{MAP_EXCLUDES.map(exclude => (
<div key={exclude} className="flex justify-start items-center h-[22px] z-20 relative py-0.5 cursor-pointer" onClick={() => handleCheckbox(exclude)}>
<BsmCheckbox className="h-full aspect-square relative bg-inherit mr-1" checked={filter?.[exclude]} onChange={() => handleCheckbox(exclude)} />
<span className="grow capitalize">{translateMapExclude(exclude)}</span>
</div>
))}
</>
)}
</section>
<section className="grow capitalize">
<h2 className="uppercase text-sm mb-1">{t("maps.map-filter-panel.tags")}</h2>
@@ -30,7 +30,7 @@ export const EnterMetaTokenModal: ModalComponent<string> = ({resolver}) => {
const cancel = () => {
resolver({exitCode: ModalExitCode.CANCELED});
}
return (
<form className="flex flex-col w-80 gap-4">
<h1 className="text-3xl uppercase tracking-wide w-full text-center">{t("modals.enter-meta-token.title")}</h1>
@@ -205,7 +205,7 @@ const PasswordInput = ({onChange, value}: {onChange: (value : {password: string,
<input className="grow px-1 py-[2px] outline-none bg-transparent" onChange={e => onChange({password: e.target.value, valid: isPasswordValid(e.target.value)})} value={value} type={showPassword ? "text" : "password"} name="password" id="password" placeholder={t("modals.enter-meta-token.body.password")} />
<BsmButton className="shrink-0 m-1 rounded-md p-0.5 !bg-light-main-color-3 dark:!bg-main-color-3" icon={showPassword ? "eye-cross" : "eye"} withBar={false} onClick={() => setShowPassword(prev => !prev)} />
</div>
</>
</>
)
}
@@ -76,12 +76,48 @@ export const DownloadMapsModal: ModalComponent<void, { version: BSVersion; owned
};
}, []);
const loadMaps = (params: SearchParams) => {
const applyInstalledFilter = (maps: BsvMapDetail[]): BsvMapDetail[] => {
return maps.filter(map => {
if (!filter.installed) {
return true;
}
return !map.versions.some(version => ownedMapHashs.includes(version.hash));
});
}
const loadMaps = (params: SearchParams, tryToLoad = 5) => {
setLoading(() => true);
beatSaver
.searchMaps(params)
.then(maps => setMaps(prev => [...prev, ...maps]))
.finally(() => setLoading(() => false));
const searchResult = beatSaver.searchMaps(params);
if (!filter.installed) {
searchResult
.then(maps => setMaps(prev => [...prev, ...maps]))
.finally(() => setLoading(() => false));
return;
}
searchResult
.then(maps => {
if (!maps.length) {
setLoading(() => false);
return;
}
maps = applyInstalledFilter(maps);
setMaps(prev => [...prev, ...maps])
if (maps.length < tryToLoad) {
handleLoadMore();
return;
}
setLoading(() => false);
})
};
const extractMapDiffs = (map: BsvMapDetail): Map<BsvMapCharacteristic, ParsedMapDiff[]> => {
@@ -146,7 +182,7 @@ export const DownloadMapsModal: ModalComponent<void, { version: BSVersion; owned
>
<div className="flex h-9 gap-2 shrink-0">
<BsmDropdownButton ref={filterContainerRef} className="shrink-0 h-full relative z-[1] flex justify-start" buttonClassName="flex items-center justify-center h-full rounded-full px-2 py-1 !bg-light-main-color-1 dark:!bg-main-color-1" icon="filter" text="pages.version-viewer.maps.search-bar.filters-btn" withBar={false}>
<FilterPanel className="absolute top-[calc(100%+3px)] origin-top w-[450px] h-fit p-2 rounded-md shadow-md shadow-black" filter={filter} onChange={setFilter} onApply={handleSearch} onClose={() => filterContainerRef.current.close()} />
<FilterPanel className="absolute top-[calc(100%+3px)] origin-top w-[450px] h-fit p-2 rounded-md shadow-md shadow-black" localData={false} filter={filter} onChange={setFilter} onApply={handleSearch} onClose={() => filterContainerRef.current.close()} />
</BsmDropdownButton>
<input className="h-full bg-light-main-color-1 dark:bg-main-color-1 rounded-full px-2 grow pb-0.5" type="text" name="" id="" placeholder={t("pages.version-viewer.maps.search-bar.search-placeholder")} value={query} onChange={e => setQuery(e.target.value)} />
<BsmButton
@@ -0,0 +1,34 @@
import { BsmButton } from "renderer/components/shared/bsm-button.component"
import { BsmImage } from "renderer/components/shared/bsm-image.component"
import { ModalComponent, ModalExitCode } from "renderer/services/modale.service"
import BeatConflict from "../../../../../assets/images/apngs/beat-conflict.png";
import { useTranslation } from "renderer/hooks/use-translation.hook";
import { BsmCheckbox } from "renderer/components/shared/bsm-checkbox.component";
import { useState } from "react";
export const NeedLaunchAdminModal: ModalComponent<boolean, void> = ({resolver}) => {
const t = useTranslation();
const [dontShowAgain, setDontShowAgain] = useState(false);
return (
<form className="text-gray-800 dark:text-gray-200 flex flex-col min-w-[350px]">
<h1 className="text-3xl uppercase tracking-wide w-full text-center">{t("modals.launch-as-admin.title")}</h1>
<BsmImage className="mx-auto h-24" image={BeatConflict} />
<div className="flex flex-col gap-3">
<p className="w-0 min-w-full">{t("modals.launch-as-admin.body.info")}</p>
<p className="w-0 min-w-full">{t("modals.launch-as-admin.body.info-2")}</p>
<p className="w-0 min-w-full text-sm italic">{t("modals.launch-as-admin.body.info-3")}</p>
</div>
<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.launch-as-admin.not-remind-me")}</span>
</div>
<div className="grid grid-flow-col grid-cols-2 gap-4">
<BsmButton typeColor="cancel" className="rounded-md text-center flex items-center justify-center transition-all h-8" onClick={() => resolver({ exitCode: ModalExitCode.CANCELED })} withBar={false} text="misc.cancel" />
<BsmButton typeColor="primary" className="rounded-md text-center flex items-center justify-center transition-all h-8" onClick={() => resolver({ exitCode: ModalExitCode.COMPLETED, data: dontShowAgain })} withBar={false} text="modals.launch-as-admin.launch-as-admin" />
</div>
</form>
)
}
@@ -19,11 +19,9 @@ export const OriginalOculusVersionBackupModal: ModalComponent<boolean, void> = (
<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} />
<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.need-backup")}</p>
<p className="mb-4">{t("modals.original-version-backup-oculus.body.can-restore-later")}</p>
<p className="text-sm italic mb-4">{t("modals.original-version-backup-oculus.body.tips-launch-oculus")}</p>
<p className="mb-4">{t("modals.original-version-backup-oculus.body.will-backup")}</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} />
+8
View File
@@ -62,4 +62,12 @@ export function logRenderError(...params: unknown[]){
ipc.sendLazy("log-error", { args: params });
}
export function addFilterStringLog(str: string){
ipc.sendLazy("add-filter-string", { args: str });
}
export function addFilterPatternLog(pattern: string){
ipc.sendLazy("add-filter-pattern", { args: pattern });
}
@@ -99,20 +99,6 @@ export function VersionViewer() {
});
}
const restoreOriginalOculusFolder = () => {
lastValueFrom(ipcService.sendV2("restore-original-oculus-folder")).then(() => {
notification.notifySuccess({
title: "Restauration réussie",
desc: "Le dossier a été restauré avec succès"
});
}).catch(() => {
notification.notifyError({
title: "Erreur lors de la restauration",
desc: "Le dossier n'a pas pu être restauré"
});
});
}
return (
<>
<BsmImage className="absolute w-full h-full top-0 left-0 object-cover" image={state.ReleaseImg || DefautVersionImage} errorImage={DefautVersionImage} />
@@ -131,7 +117,6 @@ export function VersionViewer() {
</div>
<BsmDropdownButton className="absolute top-3 right-4 h-9 w-9 bg-light-main-color-2 dark:bg-main-color-2 rounded-md" items={[
{ text: "pages.version-viewer.dropdown.open-folder", icon: "folder", onClick: openFolder },
state.oculus && { text: "pages.version-viewer.dropdown.restore-oculus-folder", icon: "backup-restore", onClick: restoreOriginalOculusFolder},
{ text: "pages.version-viewer.dropdown.verify-files", icon: "task", onClick: verifyFiles },
!state.steam && !state.oculus && { text: "pages.version-viewer.dropdown.edit", icon: "edit", onClick: edit },
!state.oculus && { text: "pages.version-viewer.dropdown.clone", icon: "copy", onClick: clone },
@@ -0,0 +1,3 @@
export type MapExclude = "installed";
export const MAP_EXCLUDES: MapExclude[] = ["installed"];
+20 -5
View File
@@ -10,6 +10,7 @@ import { ModalExitCode, ModalService } from "./modale.service";
import { OriginalOculusVersionBackupModal } from "renderer/components/modal/modal-types/original-oculus-version-backup.modal";
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";
export class BSLauncherService {
private static instance: BSLauncherService;
@@ -21,7 +22,7 @@ export class BSLauncherService {
private readonly modals: ModalService;
public readonly versionRunning$: BehaviorSubject<BSVersion> = new BehaviorSubject(null);
public static getInstance(){
if(!BSLauncherService.instance){ BSLauncherService.instance = new BSLauncherService(); }
return BSLauncherService.instance;
@@ -70,7 +71,17 @@ export class BSLauncherService {
}
}))
}
private async doMustStartAsAdmin(): Promise<boolean> {
const needAdmin = await lastValueFrom(this.ipcService.sendV2<boolean, void>("bs-launch.need-start-as-admin"));
if(!needAdmin){ return false; }
if(this.config.get("dont-remind-admin")){ return true; }
const modalRes = await this.modals.openModal(NeedLaunchAdminModal);
if(modalRes.exitCode !== ModalExitCode.COMPLETED){ throw new Error("Admin launch canceled"); }
this.config.set("dont-remind-admin", modalRes.data);
return true;
}
public doLaunch(launchOptions: LaunchOption): Observable<BSLaunchEventData>{
return this.ipcService.sendV2<BSLaunchEventData, LaunchOption>("bs-launch.launch", {args: launchOptions});
}
@@ -79,17 +90,21 @@ 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(launchOptions.version.metadata?.store !== BsStore.OCULUS){
launchOptions.admin = await this.doMustStartAsAdmin();
}
const launch$ = this.handleLaunchEvents(this.doLaunch(launchOptions));
await lastValueFrom(launch$);
})().then(() => {
obs.complete();
}).catch(err => {
@@ -11,6 +11,7 @@ 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";
export class OculusDownloaderService extends AbstractBsDownloaderService implements DownloaderServiceInterface{
@@ -88,6 +89,8 @@ export class OculusDownloaderService extends AbstractBsDownloaderService impleme
return false;
}
addFilterStringLog(tokenRes.data);
return lastValueFrom(this.startDownloadBsVersion({ bsVersion, isVerification, token: tokenRes.data })).then(() => true);
})().then(res => {
@@ -126,4 +129,4 @@ export class OculusDownloaderService extends AbstractBsDownloaderService impleme
return lastValueFrom(this.ipc.sendV2<void>("bs-oculus-clear-auth-token"));
}
}
}
@@ -13,9 +13,10 @@ import { DepotDownloaderErrorEvent, DepotDownloaderEvent, DepotDownloaderEventTy
import { SteamMobileApproveModal } from "renderer/components/modal/modal-types/bs-downgrade/steam-mobile-approve-modal.component";
import { DownloaderServiceInterface } from "./bs-store-downloader.interface";
import { AbstractBsDownloaderService } from "./abstract-bs-downloader.service";
import { addFilterStringLog } from "renderer";
export class SteamDownloaderService extends AbstractBsDownloaderService implements DownloaderServiceInterface{
private static instance: SteamDownloaderService;
public static getInstance(): SteamDownloaderService {
@@ -128,7 +129,7 @@ export class SteamDownloaderService extends AbstractBsDownloaderService implemen
return this.notificationService.notifySuccess({title: "notifications.bs-download.success.titles.verification-finished"});
}
return this.notificationService.notifySuccess({title: "notifications.bs-download.success.titles.download-success"});
}));
}));
return subs;
}
@@ -182,7 +183,7 @@ export class SteamDownloaderService extends AbstractBsDownloaderService implemen
}),
share({connector: () => new ReplaySubject(1)})
);
}
private tryAutoDownloadBsVersion(downloadInfo: DownloadSteamInfo){
@@ -217,7 +218,7 @@ export class SteamDownloaderService extends AbstractBsDownloaderService implemen
}
this.progressBarService.show(this.downloadProgress$, true);
const downloadPromise = (async () => {
const haveDotNet = await this.isDotNet6Installed().catch(() => false);
@@ -227,9 +228,9 @@ export class SteamDownloaderService extends AbstractBsDownloaderService implemen
}
const downloadInfo: DownloadSteamInfo = {bsVersion, isVerification}
const autoDownload = await lastValueFrom(this.tryAutoDownloadBsVersion(downloadInfo)).then(() => true).catch(() => false);
if(autoDownload){ return Promise.resolve(); }
const qrCodeDownload$ = this.startQrCodeDownload(downloadInfo);
@@ -242,6 +243,10 @@ export class SteamDownloaderService extends AbstractBsDownloaderService implemen
return Promise.resolve();
}
if(loginRes?.data?.password){
addFilterStringLog(loginRes.data.password);
}
if(loginRes.data.stay){
this.setSteamSession(loginRes.data.username);
}
+19 -4
View File
@@ -1,7 +1,22 @@
export function tryit<Return>(func: () => Return): {error: Error, result: Return} {
import { isPromise } from "./promise.helpers";
type TryitReturn<Return> = Return extends Promise<any> ? Promise<{ error: Error | null, result: Awaited<Return> | null }> : { error: Error | null, result: Return | null };
export function tryit<Return>(func: () => Return): TryitReturn<Return> {
try {
return { error: null, result: func() };
const result = func();
if(isPromise(result)){
return result
.then((value) => ({ error: null, result: value }))
.catch((err) => ({ error: err instanceof Error ? err : new Error(`${err}`), result: null })) as Return extends Promise<any>
? Promise<{error: Error, result: undefined} | {error: undefined, result: Awaited<Return>}>
: {error: Error, result: undefined} | {error: undefined, result: Return};
}
return { error: undefined, result } as TryitReturn<Return>;
} catch (err) {
return { error: err instanceof Error ? err : new Error(`${err}`), result: null }
return { error: err, result: undefined } as TryitReturn<Return>;
}
}
}
+3
View File
@@ -0,0 +1,3 @@
export function isFunction(value: any): value is Function {
return !!(value && value.constructor && value.call && value.apply)
}
+9
View File
@@ -1,3 +1,5 @@
import { isFunction } from "./function.helpers";
export type AllSettledHelperOptions = {
keepStructure?: boolean;
removeFalsy?: boolean;
@@ -19,3 +21,10 @@ export async function allSettled<T>(promises: Promise<T>[], options?: AllSettled
return acc;
}, []);
}
export function isPromise(value: any): value is Promise<unknown> {
if(!value) { return false; }
if(!value.then) { return false; }
if(!isFunction(value.then)) { return false; }
return true;
}
@@ -5,5 +5,6 @@ export interface LaunchOption {
oculus?: boolean,
desktop?: boolean,
debug?: boolean,
additionalArgs?: string[]
additionalArgs?: string[],
admin?: boolean,
}
@@ -162,6 +162,7 @@ export interface MapFilter {
to?: number;
fullSpread?: boolean;
ranked?: boolean;
installed?: boolean;
minDuration?: number;
maxDuration?: number;
minNps?: number;
+2 -1
View File
@@ -20,7 +20,8 @@
"resolveJsonModule": true,
"allowJs": true,
"outDir": "release/app/dist",
"strictNullChecks": false
"strictNullChecks": false,
"experimentalDecorators": true
},
"exclude": ["test", "release/build", "release/app/dist", ".erb/dll"]
}