diff --git a/.gitignore b/.gitignore index ad436745..acbb16c1 100644 --- a/.gitignore +++ b/.gitignore @@ -29,4 +29,4 @@ npm-debug.log.* *.scss.d.ts # externals -externals/bs-admin-start/target/* +externals/**/target/* diff --git a/assets/jsons/bs-versions.json b/assets/jsons/bs-versions.json index d5c6ab3c..6484682a 100644 --- a/assets/jsons/bs-versions.json +++ b/assets/jsons/bs-versions.json @@ -682,7 +682,7 @@ "ReleaseDate": "1702309893", "year": "2023" }, - { + { "BSVersion": "1.34.5", "BSManifest": "7413405154512822201", "OculusBinaryId": "6639053626194360", @@ -691,7 +691,7 @@ "ReleaseDate": "1707745958", "year": "2024" }, - { + { "BSVersion": "1.34.6", "BSManifest": "4398761375819224126", "OculusBinaryId": "6682206641879058", @@ -699,5 +699,14 @@ "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" } ] diff --git a/assets/jsons/patreons.json b/assets/jsons/patreons.json index b61ed156..873f2d41 100644 --- a/assets/jsons/patreons.json +++ b/assets/jsons/patreons.json @@ -86,5 +86,8 @@ { "username": "Celldweller", "type": "gold" + }, + { + "username": "rhythmshade" } ] diff --git a/assets/jsons/translations/de.json b/assets/jsons/translations/de.json index 746ed508..db50b2b7 100644 --- a/assets/jsons/translations/de.json +++ b/assets/jsons/translations/de.json @@ -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": { @@ -464,7 +463,7 @@ }, "maps": { "one-click-install": { - "success": "Map installation abegschlossen", + "success": "Map installation abgeschlossen", "error": "Ein Fehler ist aufgetreten während der installation der Map" } }, @@ -699,9 +698,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" @@ -741,7 +738,8 @@ "nps" : "Noten Pro Sekunde", "tags": "Stichworte", "specificities": "Allgemein", - "requirements": "Anforderungen" + "requirements": "Anforderungen", + "exclude": "ausschließen" }, "map-types": { "accuracy": "Präzision", @@ -797,6 +795,9 @@ "verified": "verifiziert", "fullSpread": "full spread" }, + "map-excludes": { + "installed": "installiert" + }, "difficulties": { "Easy": "einfach", "Normal": "normal", diff --git a/assets/jsons/translations/en.json b/assets/jsons/translations/en.json index 03a77889..adb9940f 100644 --- a/assets/jsons/translations/en.json +++ b/assets/jsons/translations/en.json @@ -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": { @@ -699,9 +698,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" @@ -741,7 +738,8 @@ "nps" : "Notes Per Second", "tags": "tags", "specificities": "general", - "requirements": "requirements" + "requirements": "requirements", + "exclude": "exclude" }, "map-types": { "accuracy": "accuracy", @@ -797,6 +795,9 @@ "verified": "verified", "fullSpread": "full spread" }, + "map-excludes": { + "installed": "installed" + }, "difficulties": { "Easy": "easy", "Normal": "normal", diff --git a/assets/jsons/translations/es.json b/assets/jsons/translations/es.json index bbade95a..a730b071 100644 --- a/assets/jsons/translations/es.json +++ b/assets/jsons/translations/es.json @@ -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": { @@ -699,9 +698,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" @@ -741,7 +738,8 @@ "nps" : "Notas Por Segundo", "tags": "tags", "specificities": "general", - "requirements": "requisitos" + "requirements": "requisitos", + "exclude": "excluir" }, "map-types": { "accuracy": "precisión", @@ -797,6 +795,9 @@ "verified": "verificado", "fullSpread": "panel completo" }, + "map-excludes": { + "installed": "instalado" + }, "difficulties": { "Easy": "fácil", "Normal": "normal", diff --git a/assets/jsons/translations/fr.json b/assets/jsons/translations/fr.json index c63c26b4..e7b63bb1 100644 --- a/assets/jsons/translations/fr.json +++ b/assets/jsons/translations/fr.json @@ -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": { @@ -699,9 +698,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" @@ -741,7 +738,8 @@ "nps" : "Notes Par Seconde", "tags": "tags", "specificities": "général", - "requirements": "requis" + "requirements": "requis", + "exclude": "exclure" }, "map-types": { "accuracy": "précision", @@ -797,6 +795,9 @@ "verified": "vérifiée", "fullSpread": "panel complet" }, + "map-excludes": { + "installed": "installé" + }, "difficulties": { "Easy": "facile", "Normal": "normal", diff --git a/assets/jsons/translations/ja.json b/assets/jsons/translations/ja.json index 750ff767..28c7f206 100644 --- a/assets/jsons/translations/ja.json +++ b/assets/jsons/translations/ja.json @@ -94,8 +94,7 @@ "edit": "編集", "uninstall": "アンインストール", "create-shortcut": "ショートカットを作成", - "shared-folders": "共有フォルダー", - "restore-oculus-folder": "フォルダーの復元" + "shared-folders": "共有フォルダー" } }, "available-versions": { @@ -699,9 +698,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": "分かった" @@ -741,7 +738,8 @@ "nps" : "秒間ノート数", "tags": "タグ", "specificities": "一般", - "requirements": "要Mod" + "requirements": "要Mod", + "exclude": "除外する" }, "map-types": { "accuracy": "正確", @@ -797,6 +795,9 @@ "verified": "認証済み", "fullSpread": "フルスプレッド" }, + "map-excludes": { + "installed": "インストール済み" + }, "difficulties": { "Easy": "easy", "Normal": "normal", diff --git a/assets/jsons/translations/ru.json b/assets/jsons/translations/ru.json index 2f36a28e..b983dfc8 100644 --- a/assets/jsons/translations/ru.json +++ b/assets/jsons/translations/ru.json @@ -94,8 +94,7 @@ "edit": "Редактировать", "uninstall": "Удалить", "create-shortcut": "Создать ярлык", - "shared-folders": "Общие папки", - "restore-oculus-folder": "Восстановить папку" + "shared-folders": "Общие папки" } }, "available-versions": { @@ -699,9 +698,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": "Понял" @@ -740,7 +737,8 @@ "nps" : "Нот в Секунду", "tags": "тэги", "specificities": "основное", - "requirements": "требуемые моды" + "requirements": "требуемые моды", + "exclude": "исключить" }, "map-types": { "accuracy": "точность", @@ -796,6 +794,9 @@ "verified": "проверенные авторы", "fullSpread": "полный набор" }, + "map-excludes": { + "installed": "установленный" + }, "difficulties": { "Easy": "легко", "Normal": "нормально", diff --git a/assets/jsons/translations/zh-tw.json b/assets/jsons/translations/zh-tw.json index a8f46018..34954213 100644 --- a/assets/jsons/translations/zh-tw.json +++ b/assets/jsons/translations/zh-tw.json @@ -94,8 +94,7 @@ "edit": "編輯", "uninstall": "移除", "create-shortcut": "創建捷徑", - "shared-folders": "Shared Folders", - "restore-oculus-folder": "Restore the folder" + "shared-folders": "Shared Folders" } }, "available-versions": { @@ -699,9 +698,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": "明白了" @@ -741,7 +738,8 @@ "nps" : "每秒音符數", "tags": "標籤", "specificities": "general", - "requirements": "要求" + "requirements": "要求", + "exclude": "排除" }, "map-types": { "accuracy": "精確度", @@ -797,6 +795,9 @@ "verified": "Verified", "fullSpread": "Full spread" }, + "map-excludes": { + "installed": "已安裝" + }, "difficulties": { "Easy": "Easy", "Normal": "Normal", diff --git a/assets/jsons/translations/zh.json b/assets/jsons/translations/zh.json index e0574f62..0460ab54 100644 --- a/assets/jsons/translations/zh.json +++ b/assets/jsons/translations/zh.json @@ -94,8 +94,7 @@ "edit": "编辑", "uninstall": "卸载", "create-shortcut": "创建快捷方式", - "shared-folders": "Shared Folders", - "restore-oculus-folder": "Restore the folder" + "shared-folders": "Shared Folders" } }, "available-versions": { @@ -699,9 +698,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": "明白了" @@ -741,7 +738,8 @@ "nps" : "每秒音符数", "tags": "标签", "specificities": "general", - "requirements": "要求" + "requirements": "要求", + "exclude": "排除" }, "map-types": { "accuracy": "精确度", @@ -797,6 +795,9 @@ "verified": "verified", "fullSpread": "full spread" }, + "map-excludes": { + "installed": "已安装" + }, "difficulties": { "Easy": "easy", "Normal": "normal", diff --git a/assets/scripts/oculus_symlink_cleaner.exe b/assets/scripts/oculus_symlink_cleaner.exe new file mode 100644 index 00000000..05659aeb Binary files /dev/null and b/assets/scripts/oculus_symlink_cleaner.exe differ diff --git a/externals/oculus-symlink-cleaner/.cargo/config.toml b/externals/oculus-symlink-cleaner/.cargo/config.toml new file mode 100644 index 00000000..0c17df09 --- /dev/null +++ b/externals/oculus-symlink-cleaner/.cargo/config.toml @@ -0,0 +1,2 @@ +[target.x86_64-pc-windows-msvc] +rustflags = ["-C", "target-feature=+crt-static"] \ No newline at end of file diff --git a/externals/oculus-symlink-cleaner/Cargo.lock b/externals/oculus-symlink-cleaner/Cargo.lock new file mode 100644 index 00000000..233feb34 --- /dev/null +++ b/externals/oculus-symlink-cleaner/Cargo.lock @@ -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", +] diff --git a/externals/oculus-symlink-cleaner/Cargo.toml b/externals/oculus-symlink-cleaner/Cargo.toml new file mode 100644 index 00000000..f7dcb7e0 --- /dev/null +++ b/externals/oculus-symlink-cleaner/Cargo.toml @@ -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" diff --git a/externals/oculus-symlink-cleaner/build.rs b/externals/oculus-symlink-cleaner/build.rs new file mode 100644 index 00000000..ac40e20c --- /dev/null +++ b/externals/oculus-symlink-cleaner/build.rs @@ -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(); + } +} diff --git a/externals/oculus-symlink-cleaner/icon.ico b/externals/oculus-symlink-cleaner/icon.ico new file mode 100644 index 00000000..660923e2 Binary files /dev/null and b/externals/oculus-symlink-cleaner/icon.ico differ diff --git a/externals/oculus-symlink-cleaner/src/main.rs b/externals/oculus-symlink-cleaner/src/main.rs new file mode 100644 index 00000000..5d3a39ed --- /dev/null +++ b/externals/oculus-symlink-cleaner/src/main.rs @@ -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 = env::args().collect(); + if args.len() != 3 { + eprintln!("Usage: {} ", 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>{ + 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> { + 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()) + } +} + diff --git a/src/main/ipcs/bs-launcher-ipcs.ts b/src/main/ipcs/bs-launcher-ipcs.ts index 53614fb1..8f70af62 100644 --- a/src/main/ipcs/bs-launcher-ipcs.ts +++ b/src/main/ipcs/bs-launcher-ipcs.ts @@ -36,10 +36,3 @@ ipc.on("bs-launch.restore-steamvr", (_, reply) => { const steamLauncher = SteamLauncherService.getInstance(); reply(from(steamLauncher.restoreSteamVR())); }); - -ipc.on("restore-original-oculus-folder", (_, reply) => { - const oculusLauncher = OculusLauncherService.getInstance(); - reply(from( - oculusLauncher.deleteBsSymlinks().then(() => oculusLauncher.restoreOriginalBeatSaber()) - )); -}); diff --git a/src/main/services/bs-launcher/abstract-launcher.service.ts b/src/main/services/bs-launcher/abstract-launcher.service.ts index 86c0c0c3..b5827999 100644 --- a/src/main/services/bs-launcher/abstract-launcher.service.ts +++ b/src/main/services/bs-launcher/abstract-launcher.service.ts @@ -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 { - return new Promise((resolve, reject) => { - const bsProcess = this.launchBSProcess(bsExePath, args, options); + protected launchBs(bsExePath: string, args: string[], options?: SpawnOptionsWithoutStdio): {process: ChildProcessWithoutNullStreams, exit: Promise} { + 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((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 }; } -} \ No newline at end of file +} diff --git a/src/main/services/bs-launcher/oculus-launcher.service.ts b/src/main/services/bs-launcher/oculus-launcher.service.ts index d6edc3ed..89678809 100644 --- a/src/main/services/bs-launcher/oculus-launcher.service.ts +++ b/src/main/services/bs-launcher/oculus-launcher.service.ts @@ -12,6 +12,9 @@ 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 { @@ -26,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(); @@ -33,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){ @@ -100,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 { const prepareOriginalVersion: () => Promise = async () => { @@ -157,7 +171,16 @@ export class OculusLauncherService extends AbstractLauncherService implements St obs.next({type: BSLaunchEvent.BS_LAUNCHING}); // Launch Beat Saber - return this.launchBs(exePath, this.buildBsLaunchArgs(launchOptions)).catch(err => { + 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); }); diff --git a/src/main/services/bs-launcher/steam-launcher.service.ts b/src/main/services/bs-launcher/steam-launcher.service.ts index 538993e4..a9d1c76c 100644 --- a/src/main/services/bs-launcher/steam-launcher.service.ts +++ b/src/main/services/bs-launcher/steam-launcher.service.ts @@ -107,7 +107,7 @@ export class SteamLauncherService extends AbstractLauncherService implements Sto obs.next({type: BSLaunchEvent.BS_LAUNCHING}); const launchPromise = !launchOptions.admin ? ( - this.launchBs(exePath, launchArgs, { env: {...process.env, "SteamAppId": BS_APP_ID} }) + this.launchBs(exePath, launchArgs, { env: {...process.env, "SteamAppId": BS_APP_ID} }).exit ) : ( new Promise(resolve => { const adminProcess = exec(`"${this.getStartBsAsAdminExePath()}" "${exePath}" ${launchArgs.join(" ")}`, { env: {...process.env, "SteamAppId": BS_APP_ID} }); diff --git a/src/main/services/installation-location.service.ts b/src/main/services/installation-location.service.ts index 7445ba2a..d227922b 100644 --- a/src/main/services/installation-location.service.ts +++ b/src/main/services/installation-location.service.ts @@ -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"); } diff --git a/src/renderer/components/maps-mangement-components/filter-panel.component.tsx b/src/renderer/components/maps-mangement-components/filter-panel.component.tsx index 7962b23e..0459f19b 100644 --- a/src/renderer/components/maps-mangement-components/filter-panel.component.tsx +++ b/src/renderer/components/maps-mangement-components/filter-panel.component.tsx @@ -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; 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 = { [k in keyof T]: T[k] extends boolean ? k : never }[keyof T]; const handleCheckbox = (key: BooleanKeys) => { @@ -189,6 +195,17 @@ export function FilterPanel({ className, ref, playlist = false, filter, onChange {requirement} ))} + { !localData && ( + <> +

{t("maps.map-filter-panel.exclude")}

+ {MAP_EXCLUDES.map(exclude => ( +
handleCheckbox(exclude)}> + handleCheckbox(exclude)} /> + {translateMapExclude(exclude)} +
+ ))} + + )}

{t("maps.map-filter-panel.tags")}

diff --git a/src/renderer/components/modal/modal-types/download-maps-modal.component.tsx b/src/renderer/components/modal/modal-types/download-maps-modal.component.tsx index 04fa513b..c03baafb 100644 --- a/src/renderer/components/modal/modal-types/download-maps-modal.component.tsx +++ b/src/renderer/components/modal/modal-types/download-maps-modal.component.tsx @@ -76,12 +76,48 @@ export const DownloadMapsModal: ModalComponent { + 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 => { @@ -146,7 +182,7 @@ export const DownloadMapsModal: ModalComponent
- filterContainerRef.current.close()} /> + filterContainerRef.current.close()} /> setQuery(e.target.value)} /> = (

{t("modals.original-version-backup-oculus.title")}

- +

{t("modals.original-version-backup-oculus.body.must-be-installed-once")}

-

{t("modals.original-version-backup-oculus.body.need-backup")}

-

{t("modals.original-version-backup-oculus.body.can-restore-later")}

-

{t("modals.original-version-backup-oculus.body.tips-launch-oculus")}

+

{t("modals.original-version-backup-oculus.body.will-backup")}

diff --git a/src/renderer/pages/version-viewer.component.tsx b/src/renderer/pages/version-viewer.component.tsx index fdefa9f0..2b3f1cf8 100644 --- a/src/renderer/pages/version-viewer.component.tsx +++ b/src/renderer/pages/version-viewer.component.tsx @@ -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 ( <> @@ -131,7 +117,6 @@ export function VersionViewer() {