Compare commits

...

55 Commits

Author SHA1 Message Date
Zagrios 80167a7f3f bumb to v1.5.1 2025-01-31 18:03:52 +01:00
MathieuG-P 91cb80bbe2 Merge pull request #774 from Zagrios/bugfix/bug-duplicate-maps-deletion-modal-does-not-display-translated-text/771
[bugfix-771] Add missing translations
2025-01-31 18:00:52 +01:00
Zagrios df1c7ca9d2 [bugfix-771] Add missing translations 2025-01-31 17:56:32 +01:00
MathieuG-P 0f96db8d29 Merge pull request #772 from silentrald/feat/delete-guard-rails
[feat] added logs when deleting files/folders
2025-01-31 16:24:46 +01:00
MathieuG-P 8042f1b9f6 Merge pull request #769 from silentrald/bugfix/playlist-keep-content
[bugfix] fix issue where playlist gets deleted when linked
2025-01-31 16:00:30 +01:00
Zagrios 757eb1f655 changed undefined to void 2025-01-31 15:53:16 +01:00
silentrald 49fb694aaf [feat] added logs when deleting files/folders 2025-01-31 21:53:39 +08:00
silentrald 508e290a24 [bugfix] fix issue where playlist gets deleted when linked 2025-01-31 08:47:56 +08:00
MathieuG-P 413996f9f3 Merge pull request #760 from Zagrios/chore/update-changelog
[Chore] update changelog
2025-01-30 19:46:58 +01:00
MathieuG-P cda4e000ee Merge pull request #759 from GaetanGrd/master
[Chore]Update Wiki for 1.5
2025-01-30 19:46:23 +01:00
Zagrios 2e9f62d415 bumb to v1.5.0 2025-01-29 22:51:21 +01:00
Zagrios 4ef821adc9 Fix angry TS 2025-01-29 22:40:59 +01:00
Zagrios 21f4eacefb [hotfix] Fix no mods available if an error occurs while resolving installed mods 2025-01-29 22:37:04 +01:00
Zagrios 72738a256b [chore] this error is already logged by request.service 2025-01-29 22:21:34 +01:00
Zagrios a54c146772 [hotfix] show skip steam only if the lauch option is enabled 2025-01-29 22:15:27 +01:00
Zagrios 43a4cdfe45 [hotfix] Fix wrong translation keys in brazilian translation 2025-01-29 21:58:37 +01:00
Zagrios 39d481467a [hotfix] Fix false error when deleting a playlist with no maps installed 2025-01-29 21:43:59 +01:00
Zagrios 3afcf1c12e Merge branch 'master' of https://github.com/Zagrios/bs-manager 2025-01-29 21:34:09 +01:00
Zagrios da140848ba [hotfix] wrong translation keys in brazilian translation 2025-01-29 21:34:08 +01:00
MathieuG-P 288853a6cc Merge pull request #763 from silentrald/hotfix/inject-env-vars
[hotfix] properly inject the env variable to the launch command
2025-01-29 12:14:57 +01:00
MathieuG-P 365e251408 Merge pull request #761 from silentrald/bugfix/uninstall-null-mod
[hotfix] fixed handling for filtering out mods to uninstall
2025-01-29 12:12:52 +01:00
Iluhadesu eaa5eccb98 Add v1.40.1 to bs-versions.json 2025-01-29 11:07:03 +00:00
silentrald eabeae00d6 [hotfix] properly inject the env variable to the launch command 2025-01-29 18:00:52 +08:00
silentrald d209d6c336 [hotfix] fixed handling for filtering out mods to uninstall 2025-01-29 09:31:55 +08:00
Zagrios eff5d5728f mods in pending folders must be loaded first 2025-01-28 22:44:04 +01:00
GaetanGrd 2ee7a904d6 Merge branch 'master' into master 2025-01-28 19:52:54 +01:00
GaetanGrd f477870f74 Update README.md 2025-01-28 19:51:28 +01:00
Shidorien 2ccb3d469f Update readme for 1.5 (no image update) 2025-01-28 19:47:01 +01:00
Zagrios 37ad1d2d19 bumb to v1.5.0-alpha.10 2025-01-28 18:39:53 +01:00
Zagrios 88f3467882 Do not use multiple hashes yet, its broken 2025-01-28 18:38:45 +01:00
GaetanGrd c63b39ff01 Merge pull request #5 from Zagrios/master
update master
2025-01-28 18:17:52 +01:00
Shidorien 4bb01146e6 fix: correct spelling and improve headings in installation guides 2025-01-28 18:14:32 +01:00
Shidorien 35a476f6ab fix: update section title from "Import Maps" to "Import Mods" in installation guide 2025-01-28 18:06:10 +01:00
Shidorien 1744ae1dcc add instructions for importing mods with BSManager 2025-01-28 18:05:46 +01:00
Shidorien 7a38bb22f1 fix: remove manual installation section from the model modification guide 2025-01-28 18:01:06 +01:00
Shidorien b325bf2a39 fix: update headings and add manual installation section in model modification guide 2025-01-28 18:00:57 +01:00
Shidorien 52d1049c98 update shortcut creation instructions for clarity and add visual 2025-01-28 17:39:47 +01:00
Shidorien 7caca61c1e fix: simplify installation instructions by removing redundant steps for downloading playlists, maps, and models 2025-01-21 20:31:39 +01:00
Shidorien 3f5b5ca4a0 fix: reorder download sources for BSManager in installation guides 2025-01-21 20:29:42 +01:00
Shidorien 29e5b1946d fix: streamline instructions for searching and installing playlists and maps in the installation guide 2025-01-20 18:37:02 +01:00
Shidorien 09ffdcae32 enhance installation guide with detailed steps for installing and importing maps using BSManager 2025-01-20 18:35:22 +01:00
Shidorien 7fc6ffdda9 fix: reorder steps for installing playlists in the installation guide 2025-01-20 18:34:35 +01:00
Shidorien 0085d1e52e fix: update link for importing playlists in installation guide 2025-01-20 18:28:17 +01:00
Shidorien ccd36913de enhance installation guide with detailed steps for importing playlists using BSManager 2025-01-20 18:27:47 +01:00
Shidorien 4dcc4e92a2 fix: correct formatting in installation guide for playlist installation steps 2025-01-20 18:20:26 +01:00
Shidorien a4dab93c36 fix: update installation guide to improve clarity on playlist installation steps 2025-01-20 18:20:16 +01:00
Shidorien 39adbc42f0 update installation guide for playlists with BSManager 2025-01-20 18:16:30 +01:00
GaetanGrd 71da1d9484 Merge pull request #4 from Zagrios/master
update master
2025-01-20 18:00:04 +01:00
Shidorien 989b045be7 Fix URL encoding in Linux sidebar links 2025-01-06 18:30:45 +01:00
Shidorien bbe708b767 Remove redundant line in installation guide 2025-01-06 00:22:19 +01:00
Shidorien b6513fb096 Merge branch 'master' of https://github.com/GaetanGrd/bs-manager 2025-01-06 00:21:46 +01:00
Shidorien a4d9069a3d Fix image src 2025-01-06 00:21:45 +01:00
GaetanGrd be49f1e02b Merge pull request #3 from Zagrios/master
update local fork
2025-01-06 00:21:04 +01:00
Shidorien 0417f3d4fa Update Install or import a version for 1.5 2025-01-06 00:20:15 +01:00
Shidorien 3feca3769a Update BSManager installation instructions to include multiple download sources 2025-01-06 00:19:17 +01:00
35 changed files with 445 additions and 357 deletions
+97 -180
View File
@@ -36,7 +36,6 @@
<hr>
</div>
<!--
----------------------------------------
BADGES
@@ -58,7 +57,7 @@
src="https://img.shields.io/badge/-DISCORD-5865f2?style=for-the-badge&logo=discord&logoColor=ffffff"
alt="discord" /></a>
<a href="https://twitter.com/BSManager_"><img
src="https://img.shields.io/badge/-Twitter-black?style=for-the-badge&logo=X" alt="Twitter" /></a>
src="https://img.shields.io/badge/-Twitter-black?style=for-the-badge&logo=X" alt="Twitter" /></a>
<a href="https://www.bsmanager.io">
<img src="https://img.shields.io/badge/-WebSite-00649c?style=for-the-badge&logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDAwIDEwMDAiPgogIDxnIGZpbGw9IiMzYjgyZmYiPgogICAgPHBhdGggZD0ibTYyNi43NSA0ODUuOTEgMjAuODIgMzUxLjk0YTM1LjIxIDM1LjIxIDAgMCAxLTE1LjQ5IDMxLjM2bC0xNDQuNzEgOTYuMTRoLS4wNmEzNS41NiAzNS41NiAwIDAgMS0xMS4zMSA0Ljc3IDM0Ljg1IDM0Ljg1IDAgMCAxLTIzLjUxLTIuODdMMTM2LjU4IDgwOS45YTExNS40NSAxMTUuNDUgMCAwIDEtNjMuNzUtOTYuNDlMNTIgMzYxLjQ2YTM1LjI2IDM1LjI2IDAgMCAxIDE1LjIzLTMxLjIxbDE0NS4wNS05Ni4zNmEzNS43MiAzNS43MiAwIDAgMSAxMS4yNy00LjcgMzQuODcgMzQuODcgMCAwIDEgMjMuNTEgMi44N0w1NjMgMzg5LjQyYTExNS40MSAxMTUuNDEgMCAwIDEgNjMuNzUgOTYuNDlaIi8+CiAgICA8cGF0aCBmaWx0ZXI9ImJyaWdodG5lc3MoMzAlKSIgZD0ibTYxMy43OCA0ODYuNjcgMjAuODIgMzUyYTIyLjM0IDIyLjM0IDAgMCAxLTMyLjI2IDIxLjMxTDI4Ni40IDcwMi41N0ExMDIuNDMgMTAyLjQzIDAgMCAxIDIyOS44MyA2MTdMMjA5IDI2NWEyMi4zNSAyMi4zNSAwIDAgMSAzMi4yNi0yMS4zMkw1NTcuMiA0MDEuMDVhMTAyLjQyIDEwMi40MiAwIDAgMSA1Ni41OCA4NS42MloiLz4KICA8L2c+CiAgPHBhdGggZD0iTTcxOC4yOSA3NzYuNDggNzAwIDgwNC4xOGMtNDAuNjMgMzkuNS04Ny4wNiA2Ny4yNS0xMzMuODEgOTAuNzUtODcuMzIgNDMuOS0yMDMuNjcgNzcuMTktMjQ5IDkwLjM0YTQ4IDQ4IDAgMCAxLTI2LjE3LjE2Yy02NC0xNy43Mi0xODguNjItNzItMjM2LjI4LTEyNS40OWE0LjIzIDQuMjMgMCAwIDEgNS4zLTYuNDVjMzUuNjQgMjAuODcgMTExLjIyIDU3LjY0IDE1Ni4zNyA3NC4yMiA0OS4xOCAxOC4wNiA4MS4yOSA4Ljc0IDkyLjI4LTMuMjZhNC43NyA0Ljc3IDAgMCAxIDcuMjQuMjFjMTkuNTEgMjQgMTA3LjI2IDQuNiAxNjYuNjQtMTMgNjQuMzQtMTkgMTg1Ljg1LTc2LjU2IDIzNS43Mi0xMzUuMThabS0xOTYuMiAxNTcuMzMtMTQ3LjQ1IDU1LjY4YTMuOTIgMy45MiAwIDAgMCAxLjQ0IDcuNThsNTQuNjItLjc3YTM1LjYzIDM1LjYzIDAgMCAwIDE5LTUuNzlsNzYtNDkuNTVhNCA0IDAgMCAwLTMuNjEtNy4xNVpNMjE4LjkyIDE5NS41NmMtMjEuMTQuMDctMzMuNTUgMzUuODYtMzMuMTYgODkuMDguMiAyOCAyLjU4IDkyLjc0IDQuNzkgMTQ3LjczLjIzIDUuNzEtOC4xOCA2LjQ4LTkgLjgxbC0zOC40Mi0yNzAuNzFhNC4zOCA0LjM4IDAgMCAxIDcuMTItNCA2My42MyA2My42MyAwIDAgMCAyMS45MyAxMS44NSA0LjYxIDQuNjEgMCAwIDAgNS45LTMuNzJjNC4yLTI3LjcgMTYuNjMtNTguNzQgMzcuMzUtODguNzRhNC4zNiA0LjM2IDAgMCAxIDcuNTYuNjljMTAuODYgMjMuODcgNDkgMTAzLjggOTMuMzQgMTUyLjc5YTQuNjUgNC42NSAwIDAgMS01LjYyIDcuMjJjLTM2LjU3LTE5LjM4LTgzLjAxLTQzLjAyLTkxLjc5LTQzWm0tMTUuNDcgMzg0LjhhNC41MSA0LjUxIDAgMCAxLTQuNDYtMy45MmwtMTMuODItMTA3LjI5YTQuNSA0LjUgMCAwIDEgOC45My0xLjE1bDEzLjgyIDEwNy4yOWE0LjUgNC41IDAgMCAxLTMuODkgNSAzLjg2IDMuODYgMCAwIDEtLjU4LjA3Wk01NzcuMDggNTQuNThjLTQ2Ljc5IDE1Ljg1LTExNS40NSA0MC41Ni0xNTguNzMgNjIuNzlhMzUuNSAzNS41IDAgMCAwLTE4LjU3IDI0LjUzYy0zLjk1IDE5LjQ5LTExIDU2LjA5LTE5IDEwNy4yNi0uNyA0LjQ4LTcuMzYgMy43OC03LjExLS43NWwxMC42Ni0xOTQuNzNhMy43OCAzLjc4IDAgMCAxIDcuNDYtLjY2bDMuNDkgMTQuODVhMi41NyAyLjU3IDAgMCAwIDQuNjkuNzdjNi4xOC0xMCAyMi4zNy0zMi43OSA1Ny4zMi02NWE0LjEzIDQuMTMgMCAwIDEgNi41NiA0LjcybC0zMi41NCA3Mi44OXM3OC43OC0xOS4zNyAxNDQtMzMuMDljNC4xMS0uODcgNS42OSA1LjA4IDEuNzcgNi40MloiIGZpbGw9IiNmZmYiIHN0cm9rZT0iYmxhY2siLz4KICA8ZyBmaWxsPSIjZjQ0Ij4KICAgIDxwYXRoIGZpbHRlcj0iYnJpZ2h0bmVzcyg2MCUpIHNhdHVyYXRlKDEyMCUpIiBkPSJtOTQ3LjA3IDQxMC4zNS01Ny43NSAzNDcuNzlhMzUuMTggMzUuMTggMCAwIDEtMjIuMDYgMjcuMTVsLTE2Mi40MyA2MS42NWgtLjA2YTM1LjUyIDM1LjUyIDAgMCAxLTEyLjA2IDIuMTEgMzQuODMgMzQuODMgMCAwIDEtMjIuMjktOGwtMjczLjE3LTIyMy41YTExNS40MyAxMTUuNDMgMCAwIDEtNDAuNzYtMTA4LjIybDU3Ljc1LTM0Ny44QTM1LjI1IDM1LjI1IDAgMCAxIDQzNiAxMzQuNDdsMTYyLjgzLTYxLjc5YTM1LjUxIDM1LjUxIDAgMCAxIDEyLTIuMDggMzQuODMgMzQuODMgMCAwIDEgMjIuMjkgOGwyNzMuMTkgMjIzLjUyYTExNS4zOCAxMTUuMzggMCAwIDEgNDAuNzYgMTA4LjIzWiIvPgogICAgPHBhdGggZD0iTTkzNC4yNSA0MDguMjIgODc2LjUgNzU2YTIyLjM0IDIyLjM0IDAgMCAxLTM2LjE4IDEzLjYzbC0yNzMuMTctMjIzLjVhMTAyLjQyIDEwMi40MiAwIDAgMS0zNi4xNy05Nmw1Ny43NS0zNDcuODNhMjIuMzQgMjIuMzQgMCAwIDEgMzYuMTgtMTMuNjNsMjczLjE3IDIyMy41MWExMDIuNDIgMTAyLjQyIDAgMCAxIDM2LjE3IDk2LjA0WiIvPgogIDwvZz4KICA8ZWxsaXBzZSBmaWxsPSIjZmZmIiBzdHJva2U9ImJsYWNrIiBjeD0iNzMyLjYxIiBjeT0iNDI5LjE2IiByeD0iNjIuODMiIHJ5PSIxMTAuNzMiIHRyYW5zZm9ybT0icm90YXRlKC0xNS40NSA3MzIuNzAzIDQyOS4xOTkpIi8+Cjwvc3ZnPg==" alt="Website" /></a>
<a href="https://www.patreon.com/bsmanager"><img
@@ -72,76 +71,64 @@
-->
<div>
<hr>
<details open>
<details open>
<summary><b>Table of Contents</b></summary>
<ul>
<li>
<li>
<details>
<summary><a href="#bsmanager-your-beat-saber-assistant">BSManager: Your Beat Saber Assistant</a></summary>
<ul>
<summary><a href="#bsmanager-your-beat-saber-assistant">BSManager: Your Beat Saber Assistant</a></summary>
<ul>
<li><a href="#what-is-it">What is it?</a></li>
<li><a href="#why-use-it">Why use it?</a></li>
<li><a href="#built-with">Built with</a></li>
<li><a href="#how-to-install">How to install</a></li>
</ul>
<li><a href="#how-to-install">How to install?</a></li>
</ul>
</details>
</li>
<li>
</li>
<li>
<details>
<summary><a href="#version-management">Version Management</a></summary>
<ul>
<li>
<details>
<summary><a href="#downloading-multiple-versions">Downloading multiple versions</a></summary>
<ul>
<li>via Steam account</li>
<li>via Oculus (PCVR) account</li>
</ul>
</details>
</li>
<li><a href="#multi-version-sharing">Multi-version Sharing</a></li>
<li><a href="#importing-an-existing-version-of-beat-saber">Importing an existing version of Beat Saber</a>
</li>
<li><a href="#launch-arguments">Launch arguments</a></li>
<li><a href="#shortcut-creation">Shortcut creation</a></li>
</ul>
<summary><a href="#version-management">Version Management</a></summary>
<ul>
<li><a href="#downloading-multiple-versions">Downloading Multiple Versions</a></li>
<li><a href="#multi-version-sharing">Multi-Version Sharing</a></li>
<li><a href="#importing-an-existing-version-of-beat-saber">Importing an Existing Version of Beat Saber</a></li>
<li><a href="#launch-arguments">Launch Arguments</a></li>
<li><a href="#shortcut-creation">Shortcut Creation</a></li>
</ul>
</details>
</li>
<li>
</li>
<li>
<details>
<summary><a href="#download-and-installation">Download and Installation</a></summary>
<ul>
<li><a href="#maps">Maps</a></li>
<li><a href="#playlists">Playlists</a></li>
<li><a href="#mods">Mods</a></li>
<li><a href="#models">Models</a></li>
</ul>
<summary><a href="#download-and-installation">Download and Installation</a></summary>
<ul>
<li><a href="#features">Features</a></li>
</ul>
</details>
</li>
<li>
</li>
<li>
<details>
<summary><a href="#customization">Customization</a></summary>
<ul>
<summary><a href="#customization">Customization</a></summary>
<ul>
<li><a href="#tool">Tool</a></li>
<li><a href="#beat-saber-instance">Beat Saber Instance</a></li>
</ul>
</ul>
</details>
</li>
<li>
</li>
<li>
<details>
<summary><a href="#more-information">More Information</a></summary>
<ul>
<summary><a href="#more-information">More Information</a></summary>
<ul>
<li><a href="#support">Support</a></li>
<li><a href="#credits">Credits</a></li>
<li><a href="#Contributing">Contributing</a></li>
</ul>
<li><a href="#contributing">Contributing</a></li>
</ul>
</details>
</li>
</li>
</ul>
</details>
<div align="right">
</details>
<div align="right">
[<a href="#readme-bot">Go to bottom</a>]
</div>
</div>
</div>
@@ -155,7 +142,6 @@
<h1><b>BSManager: Your Beat Saber Assistant</b></h1>
</div>
<!--
WHAT IS IT?
----------------------------------------
@@ -166,7 +152,6 @@
Saber</a> experience by centralizing the management of mods, maps, and game versions in one place.</p>
</div>
<!--
WHY USE IT?
----------------------------------------
@@ -189,7 +174,6 @@
<video src="https://github.com/Zagrios/bs-manager/assets/40648115/012b0196-74ef-4ee5-86a4-49e835612f96"/>
</div>
<!--
BUILT WITH
----------------------------------------
@@ -219,7 +203,6 @@
</ul>
</div>
<!--
HOW TO INSTALL
----------------------------------------
@@ -231,8 +214,12 @@
<div>
<h3><b>Windows Installation</b></h3>
<ul>
<li>Download the <a href="https://github.com/Zagrios/bs-manager/releases/latest">latest release</a> from <a
href="https://github.com/Zagrios/bs-manager/releases">Releases</a>.</li>
<li>Download and install <a href="https://www.bsmanager.io">BSManager</a> from one of the following sources:</li>
<ul>
<li><a href="https://github.com/Zagrios/bs-manager/releases/latest">GitHub</a></li>
<li><a href="https://www.bsmanager.io">Official Website</a></li>
<li><a href="https://www.nexusmods.com/beatsaber/mods/18?tab=files">Nexus Mods</a></li>
</ul>
<li>Execute the installer and <a href="https://github.com/Zagrios/bs-manager">BSManager</a> will start itself.</li>
<li>Once it's done, if you want to download a version, select a version and download it! </li>
<li>From the version page you can choose options, launch the version, and more!</li>
@@ -244,8 +231,8 @@
</div>
<div>
<h3><b>Linux installation</b></h3>
<p>Refer to <a href="https://github.com/Zagrios/bs-manager/wiki/Linux">Linux wiki</a></p>
<h3><b>Linux Installation</b></h3>
<p>BSManager is also available on Linux! Follow the step-by-step guide in our <a href="https://github.com/Zagrios/bs-manager/wiki/Linux">Linux installation wiki</a> to set it up properly.</p>
</div>
<!--
@@ -264,48 +251,27 @@
existing ones, provided that you own the game on a Steam or Oculus (PC) account.</p>
</div>
<!--
DOWNLOADING MULTIPLE VERSIONS
----------------------------------------
-->
<div>
<h2>Downloading Multiple Versions</h2>
<p><a href="https://github.com/Zagrios/bs-manager">BSManager</a> provides two authentication methods to access your
Steam account, ensuring safe and flexible access to
different game versions:</p>
</div>
<div align="center">
<img src="https://github.com/Zagrios/bs-manager/assets/40648115/09d579de-8b8e-45d3-8829-21a25cc7daf8"/>
</div>
<div>
<p><a href="https://github.com/Zagrios/bs-manager">BSManager</a> allows you to safely and easily access different versions of Beat Saber, ensuring flexibility for modding or version-specific preferences.</p>
<p>To download a version, you can authenticate using two methods:</p>
<ul>
<li><strong>via Steam</strong>: Use either your credentials, which will not be stored by <a
href="https://github.com/Zagrios/bs-manager">BSManager</a> or
<a href="https://github.com/SteamRE/DepotDownloader">DepotDownloader</a>, or opt for a quick and secure
authentication via QR with the Steam Guard app.</li>
<li><strong>via Steam</strong>: Authenticate securely using your Steam credentials or by scanning a QR code with the Steam Guard app.</li>
<li><strong>via Oculus</strong>: BSManager redirects you to the official Oculus website for authentication to retrieve your token securely. Alternatively, you can follow <a href="https://github.com/Zagrios/bs-manager/wiki/How-to-obtain-your-Oculus-Token">this guide</a> to obtain your token manually and paste it into BSManager.</li>
</ul>
<p>For detailed instructions on installing versions, follow this tutorial:
👉 <a href="https://github.com/Zagrios/bs-manager/wiki/install-or-import-a-version">How to install or import a version</a>
</p>
</div>
<div align="center">
<img src="https://github.com/Zagrios/bs-manager/assets/40648115/26da3a19-a936-4e6b-b79b-a139d7bd55de" />
<img src="https://github.com/Zagrios/bs-manager/assets/40648115/09d579de-8b8e-45d3-8829-21a25cc7daf8" alt=""/>
</div>
<div>
<ul>
<li><strong>via Oculus</strong>: For Oculus users, <a href="https://github.com/Zagrios/bs-manager">BSManager</a>
requires you to retrieve a connection token by following the instructions in this guide: <a href="https://github.com/Zagrios/bs-manager/wiki/How-to-obtain-your-Oculus-Token">How to obtain your Oculus Token</a>. Once obtained, please insert it into the form.</li>
</ul>
</div>
<div align="center">
<img height=450 src="https://github.com/Zagrios/bs-manager/assets/40648115/3674e629-542d-4de3-b8db-b248f25126d7" />
</div>
<!--
MULTI-VERSION SHARING
----------------------------------------
@@ -320,10 +286,9 @@
</div>
<div align="center">
<video src="https://github.com/Zagrios/bs-manager/assets/40648115/ba39aceb-f55f-4e58-8386-51fcfaad4957"/>
<video src="https://github.com/Zagrios/bs-manager/assets/40648115/ba39aceb-f55f-4e58-8386-51fcfaad4957"/>
</div>
<!--
IMPORTING AN EXISTING VERSION OF BEAT SABER
----------------------------------------
@@ -340,49 +305,53 @@
<video src="https://github.com/Zagrios/bs-manager/assets/40648115/cdedb951-d97a-474d-a217-2422be3919f7"/>
</div>
<!--
LAUNCH ARGUMENTS
----------------------------------------
-->
<div>
<h2>Launch Arguments</h2>
<p><a href="https://github.com/Zagrios/bs-manager">BSManager</a> provides efficient management of <a
href="https://beatsaber.com/">Beat Saber</a> launch arguments, including three predefined ones to
enhance your gaming experience:</p>
<p><a href="https://github.com/Zagrios/bs-manager">BSManager</a> provides advanced management of launch arguments for <a href="https://beatsaber.com/">Beat Saber</a>, allowing you to customize your gaming experience.</p>
<h3>Predefined Arguments</h3>
<ul>
<li><strong>Oculus Mode</strong>: Launches the game directly in Oculus mode, avoiding the use of STEAM VR.</li>
<li><strong>FPFC Mode</strong>: Enables "FPFC" mode, allowing you to control the game without using a VR
headset.</li>
<li><strong>Debug Mode</strong>: Activates debugging mode, generating Windows log outputs for IPA.</li>
<li><strong>Oculus Mode</strong>: Allows you to use Oculus's VR composer while bypassing SteamVR, which may improve performance. (Only useful if you launch the game via Steam).</li>
<li><strong>FPFC Mode</strong>: Enables "First Person Flying Controller" mode, allowing you to navigate menus using the keyboard (WASD) and mouse without a VR headset.</li>
<li><strong>Debug Mode</strong>: Activates the IPA debug console, displaying logs used by mods.</li>
<li><strong>Skip Steam</strong>: Prevents Steam from opening automatically with Beat Saber, useful if you use an alternative VR runtime like WIVRn or Monado.</li>
<li><strong>Map Editor</strong>: Launches the official Beat Saber map editor instead of the game.</li>
</ul>
<p>You also have the freedom to add your own custom launch arguments by inserting them into "Advanced Launch" and
separating them with a semicolon (;).</p>
<h3>Custom Arguments</h3>
<p>You can also add your own custom launch arguments by inserting them into "Advanced Launch" and separating them with a semicolon (;).</p>
<h3>Pinning Arguments</h3>
<p>You can now pin your favorite launch arguments for quick access. By default, only <strong>Oculus Mode</strong> and <strong>Debug Mode</strong> are pinned.</p>
</div>
<div align="center">
<img src="https://github.com/Zagrios/bs-manager/assets/40648115/1244d49f-20e1-400c-9818-528f05cfadf5" />
<img src="https://github.com/user-attachments/assets/c247b237-9c49-4e7b-9366-aeb1dd3b7b9a" alt="" />
</div>
<!--
SHORTCUT CREATION
----------------------------------------
-->
<div>
<h2>Shortcut Creation</h2>
<p>Create shortcuts to easily access your different versions of <a href="https://beatsaber.com/">Beat Saber</a> with
<a href="https://github.com/Zagrios/bs-manager">BSManager</a>. After selecting your
version, go to the version settings, click on "Create a Shortcut", and then select your launch options. Thus,
you can quickly and easily launch your preferred version of <a href="https://beatsaber.com/">Beat Saber</a> with the
desired settings.</p>
<p>Create shortcuts to easily access your different versions of <a href="https://beatsaber.com/">Beat Saber</a> with <a href="https://github.com/Zagrios/bs-manager">BSManager</a>.
</p>
<p>To learn how to create a shortcut, follow the tutorial available here:
👉 <a href="https://github.com/Zagrios/bs-manager/wiki/create-a-shortcut">Shortcut Creation Guide</a>
</p>
<p>This allows you to quickly and easily launch your preferred version of <a href="https://beatsaber.com/">Beat Saber</a> with the desired settings.
</p>
</div>
<div align="center">
<video src="https://github.com/Zagrios/bs-manager/assets/40648115/bfa1e97d-6392-4dec-a1fc-bb05048cf6d2" />
</div>
<!--
----------------------------------------
DOWNLOAD AND INSTALLATION
@@ -392,79 +361,30 @@
<hr>
<h1><b>Download and Installation</b></h1>
</div>
<div>
<p><a href="https://github.com/Zagrios/bs-manager">BSManager</a> provides centralized and organized management of
beatmaps, mods, and models, integrating download features and sharing between <a href="https://beatsaber.com/">Beat
Saber</a> versions.</p>
<p><a href="https://github.com/Zagrios/bs-manager">BSManager</a> centralizes and simplifies the management of beatmaps, playlists, mods, and models, offering seamless downloads and sharing between different <a href="https://beatsaber.com/">Beat Saber</a> versions.</p>
</div>
<!--
MAPS
----------------------------------------
-->
<div>
<h2>Maps</h2>
<p><a href="https://github.com/Zagrios/bs-manager">BSManager</a> integrates the <a href="https://beatsaver.com">Beat
Saver</a> API to provide you with a comprehensive list of maps downloadable directly from its interface. Moreover,
the OneClick feature allows you to download maps with one click from various sources such as <a
href="https://beatsaver.com">Beat Saver</a>, <a href="https://bsaber.com">BeastSaber</a>, <a
href="https://scoresaber.com">ScoreSaber</a>, and <a href="https://www.beatleader.xyz">Beat Leader</a>. To add
maps, simply select the desired version or the "shared" tab, then click "ADD".</p>
<h2>Features</h2>
<p>With <a href="https://github.com/Zagrios/bs-manager">BSManager</a>, you can manage your:
</p>
<ul>
<li><b>Maps:</b> Browse and download maps directly from sources like <a href="https://beatsaver.com">Beat Saver</a>, <a href="https://bsaber.com">BeastSaber</a>, <a href="https://scoresaber.com">ScoreSaber</a>, and <a href="https://www.beatleader.xyz">Beat Leader</a>.</li>
<li><b>Playlists:</b> Search for and download playlists directly from BSManager, just like maps. 😉
<li><b>Mods:</b> Manage and install mods via the "Mods" tab, offering the same features as Mod Assistant.</li>
<li><b>Models:</b> Add and manage models easily with the integration of the <a href="https://modelsaber.com">Model Saber</a> API.</li>
</ul>
<p>For detailed instructions on each feature, check the following guides:</p>
<ul>
<li>👉 <a href="https://github.com/Zagrios/bs-manager/wiki/install-any-map">Install Any Map</a></li>
<li>👉 <a href="https://github.com/Zagrios/bs-manager/wiki/install-all-playlists-you-want">Install Playlists</a></li>
<li>👉 <a href="https://github.com/Zagrios/bs-manager/wiki/install-mods-on-your-version">Install Mods</a></li>
<li>👉 <a href="https://github.com/Zagrios/bs-manager/wiki/modify-models-in-your-beat-saber">Manage Models</a></li>
</ul>
</div>
<div align="center">
<video src="https://github.com/Zagrios/bs-manager/assets/40648115/3f9f9968-ac55-465e-bc28-17aebfc787ea"/>
</div>
<!--
PLAYLISTS
----------------------------------------
-->
<div>
<h2>Playlists</h2>
<p>Although the playlist management interface is not yet developed, <a
href="https://github.com/Zagrios/bs-manager">BSManager</a> offers a OneClick feature for playlists from <a
href="https://beatsaver.com">Beat Saver</a>.</p>
</div>
<div align="center">
<video src="https://github.com/Zagrios/bs-manager/assets/40648115/733fe6bb-c157-4c47-8a79-f8062380bcc8" />
</div>
<!--
MODS
----------------------------------------
-->
<div>
<h2>Mods</h2>
<p>Manage your mods easily with <a href="https://github.com/Zagrios/bs-manager">BSManager</a>. After downloading a
version, navigate to the "Mods" tab of the selected version to manage and install mods. <a
href="https://github.com/Zagrios/bs-manager">BSManager</a> operates similarly to Mod Assistant and offers the same
mods as this tool.</p>
</div>
<div align="center">
<video src="https://github.com/Zagrios/bs-manager/assets/40648115/ca8f0727-b2dd-41f3-bbea-9bcaf61a89f1"/>
</div>
<!--
MODELS
----------------------------------------
-->
<div>
<h2>Models</h2>
<p>Add and manage models simply via the corresponding tab on the desired version or by going to the "shared" tab. The
<a href="https://modelsaber.com">Model Saber</a> API is integrated to provide easy and direct access to models
available for <a href="https://beatsaber.com/">Beat Saber</a>.</p>
</div>
<div align="center">
<video src="https://github.com/Zagrios/bs-manager/assets/40648115/d983b34b-9f4c-4274-aee4-4c1c03dd90d0"/>
</div>
<!--
----------------------------------------
CUSTOMIZATION
@@ -495,7 +415,7 @@
</ul>
</div>
<div align="center">
<div align="center">
<video src = "https://github.com/Zagrios/bs-manager/assets/40648115/d2c1b0ec-a2f5-41b5-99d5-c4c2cd34cbd4" />
</div>
@@ -520,7 +440,6 @@
<video src="https://github.com/Zagrios/bs-manager/assets/40648115/f66a2c99-770e-42ca-9a71-b1806946ad0c" />
</div>
<!--
----------------------------------------
MORE INFORMATION
@@ -546,7 +465,6 @@
GitHub</a>.</p>
</div>
<!--
CREDITS
----------------------------------------
@@ -575,12 +493,11 @@
href="https://github.com/Zagrios/bs-manager">BSManager</a>!</p>
<div align="center">
<a href="https://github.com/Zagrios/bs-manager/graphs/contributors">
<img src="https://contrib.rocks/image?repo=Zagrios/bs-manager" />
<img src="https://contrib.rocks/image?repo=Zagrios/bs-manager" alt="Contributor list" />
</a>
</div>
</div>
<!--
----------------------------------------
Others
+9
View File
@@ -813,5 +813,14 @@
"ReleaseImg": "https://clan.fastly.steamstatic.com/images/32055887/65df6b24c3b8a7f415080ac01394917cedabf9fc.png",
"ReleaseDate": "1733846128",
"year": "2024"
},
{
"BSVersion": "1.40.1",
"BSManifest": "8655702238937650396",
"OculusBinaryId": "8526525354113835",
"ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/510696972023234581",
"ReleaseImg": "https://clan.fastly.steamstatic.com/images/32055887/5cc1ca353151ed31da2764716cdcbe89442aeeeb.png",
"ReleaseDate": "1738144954",
"year": "2025"
}
]
+5
View File
@@ -818,6 +818,11 @@
"multiple": "These maps will also be removed from versions using shared maps"
}
}
},
"delete-duplicate-maps": {
"title": "Delete maps?",
"desc": "Only the map \"{map}\" is a duplicate. Are you sure you want to delete it?",
"desc-plural": "{nb} duplicate maps have been found. Are you sure you want to delete them?"
}
},
"link-contents":{
+5
View File
@@ -818,6 +818,11 @@
"multiple": "Queste mappe saranno rimosse anche dalle versioni che usano mappe condivise"
}
}
},
"delete-duplicate-maps": {
"title": "Eliminare le mappe?",
"desc": "Solo la mappa \"{map}\" è un duplicato. Sei sicuro di volerla eliminare?",
"desc-plural": "Sono state trovate {nb} mappe duplicate. Sei sicuro di volerle eliminare?"
}
},
"link-contents":{
+12 -7
View File
@@ -144,7 +144,7 @@
"description": "Descrição",
"dropdown": {
"import-mods": "Importar mods",
"desinstalar-all": "Desinstalar todos",
"uninstall-all": "Desinstalar todos",
"unselect-all": "Desmarcar todos"
}
}
@@ -173,7 +173,7 @@
"verify-files": "Verificar arquivos",
"clone": "Clonar",
"edit": "Editar",
"desinstalar": "Desintalar",
"uninstall": "Desintalar",
"create-shortcut": "Criar um atalho",
"shared-folders": "Pastas compartilhadas"
}
@@ -571,7 +571,7 @@
}
}
},
"desinstalar-mod": {
"uninstall-mod": {
"titles": {
"success": "Mod desinstalado 🎉"
},
@@ -581,7 +581,7 @@
}
}
},
"desinstalar-all-mods": {
"uninstall-all-mods": {
"titles": {
"success": "Mods desinstalados 🎉"
},
@@ -753,7 +753,7 @@
"submit": "Importar uma versão"
}
},
"bs-desinstalar": {
"bs-uninstall": {
"title": "Desinstalar",
"description": "Você tem certeza que quer desinstalar Beat Saber {version}? Você vai ter que baixar ela novamente se quiser jogá-la.",
"buttons": {
@@ -789,12 +789,12 @@
"submit": "Clonar"
}
},
"desinstalar-mod": {
"uninstall-mod": {
"title": "Desinstalar",
"description": "Você tem certeza que quer desinstalar Beat Saber {mod}? Pode fazer com que outros mods parem de funcionar.",
"description-bsipa": "Você tem certeza que quer desinstalar BSIPA? Depois, nenhum mod irá funcionar."
},
"desinstalar-all-mods": {
"uninstall-all-mods": {
"title": "Desinstalando mods",
"description": "Você tem certeza que quer desinstalar todos os mods da versão {version}? Essa operação não pode ser desfeita."
},
@@ -818,6 +818,11 @@
"multiple": "Esses mapas também serão removidos de suas versões utilizando mapas compartilhados"
}
}
},
"delete-duplicate-maps": {
"title": "Excluir mapas?",
"desc": "Apenas o mapa \"{map}\" é um duplicado. Tem certeza de que deseja excluí-lo?",
"desc-plural": "{nb} mapas duplicados foram encontrados. Tem certeza de que deseja excluí-los?"
}
},
"link-contents":{
@@ -1,12 +1,44 @@
## Table of Contents
- [With BSManager](#with-bsmanager)
- [Install playlists](#how-to-install-playlists-with-bsmanager)
- [Import playlists](#how-to-import-playlist-with-bsmanager)
- [Manual Installation](#manual-installation)
- [One-Click Installation](#one-click-installation)
## With BSManager
(Coming with version 1.5.0)
### How to Install Playlists with BSManager
1. Download and install [BSManager](https://www.bsmanager.io) from one of the following sources:
- [GitHub](https://github.com/Zagrios/bs-manager/releases/latest)
- [Official Website](https://www.bsmanager.io)
- [Nexus Mods](https://www.nexusmods.com/beatsaber/mods/18?tab=files)
2. Launch BSManager and select the version of *Beat Saber* where you want to install playlists from the list on the left side of the interface.
3. Go to the `Playlist` section by clicking on the __`Maps`__ tab.
4. Click the __`Add`__ button, then select __`Browse playlist`__ to open the list of online playlists.
5. Use the search bar at the top of the interface to filter playlists or sort the list.
6. Download to install it.
7. Wait for the installation to complete. Once it's finished, you can launch Beat Saber directly from [BSManager](https://www.bsmanager.io) and enjoy your new playlists!
> [!NOTE]
> You can click on a playlist's image to view the maps it contains and download only the maps you want.
### How to Import Playlist with BSManager
1. Download and install [BSManager](https://www.bsmanager.io) from one of the following sources:
- [GitHub](https://github.com/Zagrios/bs-manager/releases/latest)
- [Official Website](https://www.bsmanager.io)
- [Nexus Mods](https://www.nexusmods.com/beatsaber/mods/18?tab=files)
2. Launch BSManager and select the version of *Beat Saber* where you want to install playlists from the list on the left side of the interface.
3. Go to the `playlist` section by clicking on the __`Maps`__ tab.
4. From here, there are two possible methods:
1. Click the __`Add`__ button, then select __`Import a playlist`__ and choose the file to import.
2. Drag and drop your file directly onto the playlist interface in BSManager.
5. Wait for the import to complete. Once finished, you can launch Beat Saber directly from [BSManager](https://www.bsmanager.io) and enjoy your new playlists!
> [!Important]
> The file must be in `.bplist` or `.json` format to be imported.
## Manual Installation
@@ -1,18 +1,41 @@
## Table of Contents
- [With BSManager](#with-bsmanager)
- [Install maps](#how-to-install-maps-with-bsmanager)
- [Import maps](#how-to-import-maps-with-bsmanager)
- [Manual Installation](#manual-installation)
- [One-Click Installation](#one-click-installation)
## With BSManager
1. Download and install [BSManager](https://www.bsmanager.io) from [GitHub](https://github.com/Zagrios/bs-manager/releases/latest).
2. Launch [BSManager](https://www.bsmanager.io) and select the Beat Saber version where you want to install maps from the list on the left of the interface.
3. Go to maps by clicking on the __`Maps`__ tab.
4. Click on the __`Add`__ button to access the list of online maps.
5. Find the maps that you want to install from the provided list. You can use the search bar at the top of the interface to filter maps by __`name`__, __`type`__, __`mods`__, __`tags`__, __`notes per second`__, __`duration`__, and order the list by __`latest`__, __`relevance`__, __`rating`__, or __`curated`__.
6. Double-click on the map or simply click on the download button to install the map.
7. Wait for the installation to complete. Once it's finished, you can launch Beat Saber directly from [BSManager](https://www.bsmanager.io) to enjoy your new maps!
### How to Install Maps with BSManager
1. Download and install [BSManager](https://www.bsmanager.io) from one of the following sources:
- [GitHub](https://github.com/Zagrios/bs-manager/releases/latest)
- [Official Website](https://www.bsmanager.io)
- [Nexus Mods](https://www.nexusmods.com/beatsaber/mods/18?tab=files)
2. Launch BSManager and select the version of *Beat Saber* where you want to install maps from the list on the left side of the interface.
3. Go to the `Maps` section by clicking on the __`Maps`__ tab.
4. Click the __`Add`__ button to access the list of online maps.
5. Use the search bar at the top of the interface to filter maps by __`name`__, __`type`__, __`mods`__, __`tags`__, __`notes per second`__, or __`duration`__, and order the list by __`latest`__, __`relevance`__, __`rating`__, or __`curated`__.
6. Download to install it.
7. Wait for the installation to complete. Once it's finished, you can launch Beat Saber directly from [BSManager](https://www.bsmanager.io) to enjoy your new maps!
### How to Import Maps with BSManager
1. Download and install [BSManager](https://www.bsmanager.io) from one of the following sources:
- [GitHub](https://github.com/Zagrios/bs-manager/releases/latest)
- [Official Website](https://www.bsmanager.io)
- [Nexus Mods](https://www.nexusmods.com/beatsaber/mods/18?tab=files)
2. Launch BSManager and select the version of *Beat Saber* where you want to install maps from the list on the left side of the interface.
3. Go to the `Maps` section by clicking on the __`Maps`__ tab.
4. From here, there are two possible methods:
1. Click the __`Add`__ button, then select __`Import maps`__ and choose the .zip file to import.
2. Drag and drop your .zip file directly onto the maps interface in BSManager.
5. Wait for the import to complete. Once finished, you can launch Beat Saber directly from [BSManager](https://www.bsmanager.io) to enjoy your new maps!
> [!Important]
> The file must be in `.zip` format to be imported.
## Manual Installation
@@ -12,17 +12,20 @@
- [With BSManager](#with-bsmanager)
- [One-Click Installation](#one-click-installation)
# With BSManager
## With BSManager
1. Download and install [BSManager](https://www.bsmanager.io) from [GitHub](https://github.com/Zagrios/bs-manager/releases/latest), [Nexus Mods](https://www.nexusmods.com/beatsaber/mods/18?tab=files), or [Website](https://www.bsmanager.io).
1. Download and install [BSManager](https://www.bsmanager.io) from one of the following sources:
- [GitHub](https://github.com/Zagrios/bs-manager/releases/latest)
- [Official Website](https://www.bsmanager.io)
- [Nexus Mods](https://www.nexusmods.com/beatsaber/mods/18?tab=files)
2. Launch [BSManager](https://www.bsmanager.io) and select the Beat Saber version where you want to install models from the list on the left of the interface.
3. Go to models by clicking on the __`Models`__ tab.
4. Click on the __`Add`__ button to access the list of online models.
5. Find the models that you want to install from the provided list. You can use the search bar at the top of the interface to filter models by __`name`__, __`type`__, and order the list by __`date`__, __`name`__, or __`author`__.
6. Double-click on the model or simply click on the download button to install the model.
6. Download to install the model.
7. Wait for the installation to complete. Once it's finished, you can launch Beat Saber directly from [BSManager](https://www.bsmanager.io) to enjoy your new models!
# One-Click Installation
## One-Click Installation
1. Launch [BSManager](https://www.bsmanager.io) and make sure that the One-Click installation feature is enabled for models.
2. Go to one of the supported websites and search for the models you want to install.
@@ -1,11 +1,18 @@
## Table of Contents
- [With BSManager](#with-bsmanager)
- [Install mods](#how-to-install-mods-with-bsmanager)
- [Import mods](#how-to-import-mods-with-bsmanager)
- [Manual Installation](#manual-installation)
## With BSManager
1. Download and install [BSManager](https://www.bsmanager.io) from [GitHub](https://github.com/Zagrios/bs-manager/releases/latest), [Nexus Mods](https://www.nexusmods.com/beatsaber/mods/18?tab=files), or the [official website](https://www.bsmanager.io).
### How to Install Mods with BSManager
1. Download and install [BSManager](https://www.bsmanager.io) from one of the following sources:
- [GitHub](https://github.com/Zagrios/bs-manager/releases/latest)
- [Nexus Mods](https://www.nexusmods.com/beatsaber/mods/18?tab=files)
- [Official Website](https://www.bsmanager.io)
2. Launch [BSManager](https://www.bsmanager.io) and select the version of Beat Saber that you want to use from the list on the left side of the interface.
3. Go to mods by clicking on the __`Mods`__ tab.
4. Find the mods that you want to install from the provided list. You can use the search bar at the top of the interface to filter mods by name.
@@ -13,6 +20,22 @@
6. Click __`Install or Update`__ at the bottom-right corner of the interface.
7. Wait for the installation to complete. Once it's finished, you can launch Beat Saber directly from [BSManager](https://www.bsmanager.io) to enjoy your new mods!
### How to Import Mods with BSManager
1. Download and install [BSManager](https://www.bsmanager.io) from one of the following sources:
- [GitHub](https://github.com/Zagrios/bs-manager/releases/latest)
- [Official Website](https://www.bsmanager.io)
- [Nexus Mods](https://www.nexusmods.com/beatsaber/mods/18?tab=files)
2. Launch BSManager and select the version of *Beat Saber* where you want to install mods from the list on the left side of the interface.
3. Go to the `Mods` tab.
4. From here, there are two possible methods:
1. Click on __`⁝`__ button, then select __`Import mods`__ and choose the .zip or .dll file to import.
2. Drag and drop your .zip or .dll file directly onto the mods interface in BSManager.
5. Wait for the import to complete. Once finished, you can launch Beat Saber directly from [BSManager](https://www.bsmanager.io) to enjoy your new mods!
> [!Important]
> The file must be in `.zip` or `.dll` format to be imported.
## Manual Installation
> [!IMPORTANT]
Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

@@ -15,6 +15,10 @@
4. Choose your launch options.
5. Click on __`Create a Shortcut`__. It should now appear on your desktop.
<div align="center">
<img src="../wiki/Guides/Installation-and-updates/Create-a-shortcut/BsmShortcutInterface.png" alt="SeeTxtFile.png" />
</div>
### On Taskbar
1. Open Notepad and drag & drop the version shortcut you need.
@@ -31,25 +35,12 @@ start <version link>
## How to create a shortcut on Steam
1. In [BSManager](https://www.bsmanager.io), select the version you want to open in Steam from the list on the left side.
2. Click the gear icon at the top-right corner and select __`Create a Shortcut`__. This will create a shortcut on your desktop for launching the game.
3. Open Notepad (or any other text editor) and drag & drop the shortcut file into the text editor. You will see a string of text similar to this:
```yaml
bsmanager://launch/?version=1.29.1&versionIno=3940649674381032
```
4. Copy this text and remove any spaces.
<div align="center">
<img src="../wiki/Guides/Installation-and-updates/Create-a-shortcut/SeeTxtFile.png" alt="SeeTxtFile.png" />
</div>
5. Open Steam and add [BSManager](https://www.bsmanager.io) as a non-Steam game.
6. In your Steam library, right-click on [BSManager](https://www.bsmanager.io), select __`Properties`__, and add the link from step 4 as a launch parameter.
7. Disable "Desktop Game Theatre" and scroll down to enable "Include in VR Library" so you can launch it from your VR headset.
8. Rename this entry to whatever you want and customize the name and the icon of the shortcut if desired.
1. Launch [BSManager](https://www.bsmanager.io) and select your version.
2. Click on the gear icon at the top-right corner of the version.
3. Click on the __`Create a Shortcut`__ button.
4. Choose your launch options.
5. Click on __`Create a Shortcut`__. It should now appear on your desktop.
<div align="center">
<img src="../wiki/Guides/Installation-and-updates/Create-a-shortcut/SteamShortcut.png" alt="SteamShortcut.png" />
<img src="../wiki/Guides/Installation-and-updates/Create-a-shortcut/SteamShortcutCheck.png" alt="SeeTxtFile.png" />
</div>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 256 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

@@ -10,7 +10,10 @@
## Install a Version with Steam
1. Download and install [BSManager](https://www.bsmanager.io) from [GitHub](https://github.com/Zagrios/bs-manager/releases/latest), [Nexus Mods](https://www.nexusmods.com/beatsaber/mods/18?tab=files), or the [official website](https://www.bsmanager.io).
1. Download and install [BSManager](https://www.bsmanager.io) from one of the following sources:
- [GitHub](https://github.com/Zagrios/bs-manager/releases/latest)
- [Official Website](https://www.bsmanager.io)
- [Nexus Mods](https://www.nexusmods.com/beatsaber/mods/18?tab=files)
2. Open the __`Add Versions`__ interface and select the version you wish to download.
3. Choose __`Steam`__ as your platform.
4. Enter your Steam credentials (ID and password) or scan the QR code using the Steam app on your phone.
@@ -28,15 +31,29 @@
> - Ensure that Beat Saber is in your __Oculus PCVR__ library to enable downgrading.
> - You must run Beat Saber at least once on your computer before attempting to launch retrograde Oculus versions from [BSManager](https://www.bsmanager.io).
1. Download and install [BSManager](https://www.bsmanager.io) from [GitHub](https://github.com/Zagrios/bs-manager/releases/latest), [Nexus Mods](https://www.nexusmods.com/beatsaber/mods/18?tab=files), or the [official website](https://www.bsmanager.io).
1. Download and install [BSManager](https://www.bsmanager.io) from one of the following sources:
- [GitHub](https://github.com/Zagrios/bs-manager/releases/latest)
- [Official Website](https://www.bsmanager.io)
- [Nexus Mods](https://www.nexusmods.com/beatsaber/mods/18?tab=files)
2. Open the __`Add Versions`__ interface and select the version you wish to download.
3. Choose __`Oculus Store`__ as your platform.
4. Follow [this guide](How-to-obtain-your-Oculus-Token) to obtain your Oculus token.
5. Once logged into Oculus, the download will start automatically. Please wait for it to complete.
6. When the download is finished, select the downloaded version and click the __`Launch`__ button to verify that the game launches correctly.
4. Click on __`Connect to Meta`__ or __`Enter my login token manually`__ based on your preferred login method.
### If you selected __`Connect to Meta`__
1. You will be redirected to the Oculus website. Enter your email and follow the instructions to log in.
2. Once logged in, the download will start automatically. Please wait for it to complete.
3. When the download finishes, select the downloaded version and click the __`Launch`__ button to verify that the game launches correctly.
### If you selected __`Enter my login token manually`__
1. Follow [this guide](How-to-obtain-your-Oculus-Token) to obtain your Oculus token.
2. Once the token is entered and validated, the download will start automatically. Please wait for it to complete.
3. When the download finishes, select the downloaded version and click the __`Launch`__ button to verify that the game launches correctly.
<div align="center">
<img src="../wiki/Guides/Installation-and-updates/Install-or-import-a-version/OculusLogin.png" alt="OculusLogin.png" />
<img src="../wiki/Guides/Installation-and-updates/Install-or-import-a-version/OculusLoginToken.png" alt="OculusLoginToken.png" />
</div>
## How to Import a Version
Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

+1 -1
View File
@@ -2,7 +2,7 @@
"name": "bs-manager",
"description": "Manage maps, mods and more for Beat Saber",
"main": "./.erb/dll/main.bundle.dev.js",
"version": "1.5.0-alpha.9",
"version": "1.5.1",
"scripts": {
"build-rust-scripts": "ts-node ./.erb/scripts/build-rust-scripts.js",
"build": "concurrently \"npm run build:main\" \"npm run build:renderer\"",
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "bs-manager",
"version": "1.5.0-alpha.9",
"version": "1.5.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "bs-manager",
"version": "1.5.0-alpha.9",
"version": "1.5.1",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "bs-manager",
"version": "1.5.0-alpha.9",
"version": "1.5.1",
"description": "BSManager",
"main": "./dist/main/main.js",
"author": {
+9
View File
@@ -2,6 +2,15 @@ import { mkdir, pathExistsSync, rm, writeFile } from "fs-extra";
import { getSize } from "main/helpers/fs.helpers";
import path from "path";
jest.mock("electron", () => ({ app: {
getPath: () => "",
getName: () => "",
}}));
jest.mock("electron-log", () => ({
info: jest.fn(),
error: jest.fn(),
}));
const TEST_FOLDER = path.resolve(__dirname, "..", "assets", "fs");
describe("Test fs.helpers getSize", () => {
+68 -32
View File
@@ -1,5 +1,5 @@
import { CopyOptions, MoveOptions, copy, createReadStream, ensureDir, move, pathExists, pathExistsSync, realpath, stat, symlink } from "fs-extra";
import { access, mkdir, rm, readdir, unlink, lstat, readlink } from "fs/promises";
import { CopyOptions, MoveOptions, RmOptions, copy, createReadStream, ensureDir, move, pathExists, pathExistsSync, realpath, rm, stat, symlink, unlink, unlinkSync } from "fs-extra";
import { access, mkdir, readdir, lstat, readlink } from "fs/promises";
import path from "path";
import { Observable, concatMap, from } from "rxjs";
import log from "electron-log";
@@ -28,18 +28,53 @@ export async function ensureFolderExist(path: string): Promise<void> {
.then(() => {});
}
export async function deleteFolder(folderPath: string): Promise<void> {
export async function deleteFile(filepath: string) {
try {
log.info("Deleting file", `"${filepath}"`);
await unlink(filepath);
} catch (error: any) {
log.error("Could not delete file", `"${filepath}"`);
throw CustomError.fromError(error, "generic.fs.delete-file");
}
}
export function deleteFileSync(filepath: string) {
try {
log.info("Deleting file", `"${filepath}"`);
unlinkSync(filepath);
} catch (error: any) {
log.error("Could not delete file", `"${filepath}"`);
throw CustomError.fromError(error, "generic.fs.delete-file");
}
}
export async function deleteFolder(folderPath: string, options?: RmOptions) {
if (!(await pathExist(folderPath))) {
return;
}
return rm(folderPath, { recursive: true, force: true });
try {
options = options || { recursive: true, force: true };
log.info("Deleting folder", `"${folderPath}"`, options);
await rm(folderPath, options);
} catch (error: any) {
log.error("Could not delete folder", `"${folderPath}"`);
throw CustomError.fromError(error, "generic.fs.delete-folder");
}
}
export async function unlinkPath(path: string): Promise<void> {
if (!(await pathExist(path))) {
export function deleteFolderSync(folderPath: string, options?: RmOptions) {
if (!pathExistsSync(folderPath)) {
return;
}
return unlink(path);
try {
options = options || { recursive: true, force: true };
log.info("Deleting folder", `"${folderPath}"`, options);
} catch (error: any) {
log.error("Could not delete folder", `"${folderPath}"`);
throw CustomError.fromError(error, "generic.fs.delete-folder");
}
}
export async function getFoldersInFolder(folderPath: string, opts?: { ignoreSymlinkTargetError?: boolean }): Promise<string[]> {
@@ -97,23 +132,22 @@ export function moveFolderContent(src: string, dest: string, option?: MoveOption
const files = await readdir(src, { encoding: "utf-8", withFileTypes: true });
progress.total = files.length;
for(const file of files){
for (const file of files) {
const srcFullPath = path.join(src, file.name);
const destFullPath = path.join(dest, file.name);
const srcChilds = file.isDirectory() ? await readdir(srcFullPath, { encoding: "utf-8", recursive: true }) : [];
const allChildsAlreadyExist = srcChilds.every(child => pathExistsSync(path.join(destFullPath, child)));
if(file.isFile() || !allChildsAlreadyExist){
if (file.isFile() || !allChildsAlreadyExist) {
const prevSize = await getSize(srcFullPath);
await move(srcFullPath, destFullPath, option);
const afterSize = await getSize(destFullPath);
// The size after moving should be the same or greater than the size before moving but never less
if(afterSize < prevSize){
if (afterSize < prevSize) {
throw new CustomError(`File size mismath. before: ${prevSize}, after: ${afterSize} (${srcFullPath})`, "FILE_SIZE_MISMATCH");
}
} else {
log.info(`Skipping ${srcFullPath} to ${destFullPath}, all child already exist in destination`);
}
@@ -121,7 +155,9 @@ export function moveFolderContent(src: string, dest: string, option?: MoveOption
progress.current++;
subscriber.next(progress);
}
})().catch(err => subscriber.error(CustomError.fromError(err, err?.code))).finally(() => subscriber.complete());
})()
.catch(err => subscriber.error(CustomError.fromError(err, err?.code)))
.finally(() => subscriber.complete());
});
}
@@ -160,7 +196,7 @@ export async function copyDirectoryWithJunctions(src: string, dest: string, opti
await copy(sourcePath, destinationPath, options);
} else if (item.isSymbolicLink()) {
if (options?.overwrite) {
await unlinkPath(destinationPath);
await deleteFile(destinationPath);
}
const symlinkTarget = await readlink(sourcePath);
const relativePath = path.relative(src, symlinkTarget);
@@ -180,8 +216,7 @@ export function hashFile(filePath: string, algorithm = "sha256"): Promise<string
});
}
export async function dirSize(dirPath: string): Promise<number>{
export async function dirSize(dirPath: string): Promise<number> {
const entries = await readdir(dirPath);
const paths = entries.map(async entry => {
@@ -200,11 +235,10 @@ export async function dirSize(dirPath: string): Promise<number>{
return 0;
});
return (await Promise.all(paths)).flat(Infinity).reduce((acc, size ) => acc + size, 0);
return (await Promise.all(paths)).flat(Infinity).reduce((acc, size) => acc + size, 0);
}
export function rxCopy(src: string, dest: string, option?: CopyOptions): Observable<Progression> {
const dirSizePromise = dirSize(src).catch(err => {
log.error("dirSizePromise", err);
return 0;
@@ -216,15 +250,19 @@ export function rxCopy(src: string, dest: string, option?: CopyOptions): Observa
return new Observable<Progression>(sub => {
sub.next(progress);
copy(src, dest, {...option, filter: (src) => {
stat(src).then(stats => {
progress.current += stats.size;
sub.next(progress);
});
return true;
}})
.then(() => sub.complete()).catch(err => sub.error(err))
})
copy(src, dest, {
...option,
filter: src => {
stat(src).then(stats => {
progress.current += stats.size;
sub.next(progress);
});
return true;
},
})
.then(() => sub.complete())
.catch(err => sub.error(err));
});
})
);
}
@@ -257,7 +295,7 @@ export function ensurePathNotAlreadyExistSync(path: string): string {
return destPath;
}
export async function isJunction(path: string): Promise<boolean>{
export async function isJunction(path: string): Promise<boolean> {
const [stats, lstats] = await Promise.all([stat(path), lstat(path)]);
return lstats.isSymbolicLink() && stats.isDirectory();
}
@@ -265,7 +303,7 @@ export async function isJunction(path: string): Promise<boolean>{
export function resolveGUIDPath(guidPath: string): string {
const guidVolume = path.parse(guidPath).root;
const command = `powershell -command "(Get-WmiObject -Class Win32_Volume | Where-Object { $_.DeviceID -like '${guidVolume}' }).DriveLetter"`;
const {result: driveLetter, error} = tryit(() => execSync(command).toString().trim());
const { result: driveLetter, error } = tryit(() => execSync(command).toString().trim());
if (!driveLetter || error) {
throw new Error("Unable to resolve GUID path", error);
}
@@ -292,7 +330,7 @@ export async function getSize(targetPath: string, maxDepth = 5): Promise<number>
const visited = new Set<string>();
const computeSize = async (currentPath: string, depth: number): Promise<number> => {
if (visited.has(currentPath)){
if (visited.has(currentPath)) {
return 0;
}
@@ -309,9 +347,7 @@ export async function getSize(targetPath: string, maxDepth = 5): Promise<number>
}
const entries = await readdir(currentPath);
const sizes = await Promise.all(
entries.map((entry) => computeSize(path.join(currentPath, entry), depth + 1))
);
const sizes = await Promise.all(entries.map(entry => computeSize(path.join(currentPath, entry), depth + 1)));
return sizes.reduce((acc, cur) => acc + cur, 0);
};
+5 -13
View File
@@ -23,9 +23,11 @@ import { LivShortcut } from "./services/liv/liv-shortcut.service";
import { SteamLauncherService } from "./services/bs-launcher/steam-launcher.service";
import { FileAssociationService } from "./services/file-association.service";
import { SongDetailsCacheService } from "./services/additional-content/maps/song-details-cache.service";
import { Dirent, readdirSync, rmSync, unlinkSync } from "fs-extra";
import { Dirent, readdirSync } from "fs-extra";
import { StaticConfigurationService } from "./services/static-configuration.service";
import { configureProxy } from './helpers/proxy.helpers';
import { deleteFileSync, deleteFolderSync } from "./helpers/fs.helpers";
import { tryit } from "shared/helpers/error.helpers";
const isDebug = process.env.NODE_ENV === "development" || process.env.DEBUG_PROD === "true";
const staticConfig = StaticConfigurationService.getInstance();
@@ -238,12 +240,7 @@ function deleteOldLogs(): void {
for (const folder of deleteLogFolders) {
const folderPath = path.join(folder.parentPath, folder.name);
try {
rmSync(folderPath, { recursive: true, force: true });
log.info("Deleted log folder:", folderPath);
} catch (error) {
log.error("Error deleting folder:", folderPath, error);
}
tryit(() => deleteFolderSync(folderPath));
}
}
@@ -255,12 +252,7 @@ function deleteOldestLogs(): void {
for (const file of logs) {
const filepath = path.join(file.parentPath, file.name);
try {
unlinkSync(filepath);
log.info("Deleted log file:", filepath);
} catch (error) {
log.error("Error deleting file:", filepath, error);
}
tryit(() => deleteFileSync(filepath));
}
}
@@ -8,7 +8,7 @@ import path from "path";
import { RequestService } from "../request.service";
import { copyFileSync } from "fs-extra";
import sanitize from "sanitize-filename";
import { Progression, ensureFolderExist, unlinkPath } from "../../helpers/fs.helpers";
import { Progression, deleteFile, ensureFolderExist } from "../../helpers/fs.helpers";
import { MODEL_FILE_EXTENSIONS, MODEL_TYPES, MODEL_TYPE_FOLDERS } from "../../../shared/models/models/constants";
import { InstallationLocationService } from "../installation-location.service";
import { Observable, Subscription, lastValueFrom } from "rxjs";
@@ -105,7 +105,9 @@ export class LocalModelsManagerService {
}
public async oneClickDownloadModel(model: MSModel): Promise<void> {
if (!model) { return; }
if (!model) {
return;
}
const versions = await this.localVersion.getInstalledVersions();
const downloaded = await lastValueFrom(this.downloadModel(model, versions.pop()));
@@ -198,7 +200,7 @@ export class LocalModelsManagerService {
};
for (const model of models) {
await unlinkPath(model.path);
await deleteFile(model.path);
progression.data.push(model);
progression.current = progression.data.length;
subscriber.next(progression);
@@ -11,7 +11,7 @@ import { BPList, DownloadPlaylistProgressionData, PlaylistSong } from "shared/mo
import { readFileSync, Stats } from "fs";
import { BeatSaverService } from "../thrid-party/beat-saver/beat-saver.service";
import { copy, ensureDir, pathExists, pathExistsSync, realpath, writeFileSync } from "fs-extra";
import { Progression, getUniqueFileNamePath, unlinkPath } from "../../helpers/fs.helpers";
import { Progression, deleteFile, getUniqueFileNamePath } from "../../helpers/fs.helpers";
import { FileAssociationService } from "../file-association.service";
import { SongDetailsCacheService } from "./maps/song-details-cache.service";
import { sToMs } from "shared/helpers/time.helpers";
@@ -409,7 +409,7 @@ export class LocalPlaylistsManagerService {
}
public deletePlaylistFile(bpList: LocalBPList): Observable<void>{
return from(unlinkPath(bpList.path));
return from(deleteFile(bpList.path));
}
public exportPlaylists(opt: {version?: BSVersion, bpLists: LocalBPList[], dest: string, playlistsMaps?: BsmLocalMap[]}): Observable<Progression<string>> {
@@ -7,7 +7,7 @@ import { InstallationLocationService } from "../../installation-location.service
import { UtilsService } from "../../utils.service";
import crypto, { BinaryLike } from "crypto";
import { lstatSync } from "fs";
import { copy, createReadStream, ensureDir, pathExists, pathExistsSync, realpath, unlink } from "fs-extra";
import { copy, createReadStream, ensureDir, pathExists, pathExistsSync, realpath } from "fs-extra";
import { RequestService } from "../../request.service";
import sanitize from "sanitize-filename";
import { DeepLinkService } from "../../deep-link.service";
@@ -15,7 +15,7 @@ import log from "electron-log";
import { WindowManagerService } from "../../window-manager.service";
import { Observable, Subject, lastValueFrom } from "rxjs";
import { Archive } from "../../../models/archive.class";
import { Progression, deleteFolder, ensureFolderExist, getFilesInFolder, getFoldersInFolder, pathExist } from "../../../helpers/fs.helpers";
import { Progression, deleteFile, deleteFolder, ensureFolderExist, getFilesInFolder, getFoldersInFolder, pathExist } from "../../../helpers/fs.helpers";
import { readFile } from "fs/promises";
import { FolderLinkerService } from "../../folder-linker.service";
import { allSettled } from "../../../../shared/helpers/promise.helpers";
@@ -280,6 +280,8 @@ export class LocalMapsManagerService {
return new Observable<Progression>(observer => {
const progress: Progression = { total: hashs.length, current: 0 };
observer.next(progress);
(async () => {
const versionMapsPath = await this.getMapsFolderPath(version);
const mapsPaths = await getFoldersInFolder(versionMapsPath);
@@ -447,7 +449,7 @@ export class LocalMapsManagerService {
const zip = await BsmZipExtractor.fromPath(zipPath);
await zip.extract(mapPath);
zip.close();
await unlink(zipPath);
await deleteFile(zipPath);
const localMap = await this.loadMapInfoFromPath(mapPath);
localMap.songDetails = this.songDetailsCache.getSongDetails(localMap.hash);
@@ -139,9 +139,11 @@ export abstract class AbstractLauncherService {
for (const [ key, value ] of Object.entries(parseEnvString(envString))) {
if (key in env) {
log.warn("Ignoring", `${key}=${value}`, "already set env launch command");
} else {
log.info("Injecting", `${key}="${value}"`, "to the env launch command");
continue;
}
log.info("Injecting", `${key}="${value}"`, "to the env launch command");
env[key] = value;
}
launchOptions.command = command.substring(index + this.COMMAND_FORMAT.length);
@@ -89,7 +89,7 @@ export class SteamLauncherService extends AbstractLauncherService implements Sto
obs.next({type: BSLaunchWarning.UNABLE_TO_LAUNCH_STEAM});
});
}
else {
else if(skipSteam) {
obs.next({ type: BSLaunchEvent.SKIPPING_STEAM_LAUNCH});
}
+6 -6
View File
@@ -1,9 +1,9 @@
import { InstallationLocationService } from "./installation-location.service";
import log from "electron-log";
import { deleteFolder, ensureFolderExist, moveFolderContent, pathExist, unlinkPath } from "../helpers/fs.helpers";
import { deleteFile, deleteFileSync, deleteFolder, deleteFolderSync, ensureFolderExist, moveFolderContent, pathExist } from "../helpers/fs.helpers";
import { lstat, symlink } from "fs/promises";
import path from "path";
import { copy, mkdirSync, readlink, rmSync, symlinkSync, unlinkSync } from "fs-extra";
import { copy, mkdirSync, readlink, symlinkSync } from "fs-extra";
import { lastValueFrom } from "rxjs";
import { noop } from "shared/helpers/function.helpers";
import { StaticConfigurationService } from "./static-configuration.service";
@@ -56,8 +56,8 @@ export class FolderLinkerService {
});
tryit(() => {
rmSync(testFolder, { force: true, recursive: true });
unlinkSync(testLink);
deleteFolderSync(testFolder);
deleteFileSync(testLink);
});
if(resLink.error){
@@ -113,7 +113,7 @@ export class FolderLinkerService {
if (isTargetedToSharedPath) {
return;
}
await unlinkPath(folderPath);
await deleteFile(folderPath);
log.info(`Linking ${folderPath} to ${sharedPath}; type: ${this.linkingType}`);
return symlink(sharedPath, folderPath, this.getLinkingType());
@@ -141,7 +141,7 @@ export class FolderLinkerService {
if (!(await this.isFolderSymlink(folderPath))) {
return;
}
await unlinkPath(folderPath);
await deleteFile(folderPath);
const sharedPath = await this.getSharedFolder(folderPath, options?.intermediateFolder);
@@ -3,7 +3,6 @@ import { BbmFullMod, BbmMod, BbmModVersion, BbmPlatform } from "../../../shared/
import { RequestService } from "../request.service";
import { BsStore } from "../../../shared/models/bs-store.enum";
import log from "electron-log"
import { tryit } from "shared/helpers/error.helpers";
export class BeatModsApiService {
private static instance: BeatModsApiService;
@@ -60,42 +59,17 @@ export class BeatModsApiService {
});
}
public async getModByHash<T extends string>(hashs: T[]): Promise<Record<T, BbmModVersion>> {
const getModsFromCache = (hashs: T[]): Record<T, BbmModVersion> => {
const mods = {} as Record<T, BbmModVersion>;
for (const hash of hashs) {
const mod = this.modsHashCache.get(hash);
if(mod){
mods[hash] = mod;
}
}
return mods;
public getModByHash(hash: string): Promise<BbmModVersion|undefined> {
if (this.modsHashCache.has(hash)) {
return Promise.resolve(this.modsHashCache.get(hash));
}
const mods = getModsFromCache(hashs);
const missingHashs = hashs.filter(hash => !mods[hash]);
if(!missingHashs.length){
return mods;
}
const url = new URL(`${this.MODS_REPO_API_URL}/hashlookup`);
missingHashs.forEach(hash => url.searchParams.append("hash", hash));
const res = await tryit(() => this.requestService.getJSON<{ modVersions: BbmModVersion[] }>(url.toString()));
if(res.error){
log.error(`Failed to get mod by hashes`, res.error);
return this.requestService.getJSON<{ modVersions: BbmModVersion[] }>(`${this.MODS_REPO_API_URL}/hashlookup?hash=${hash}`).then(({ data }) => {
this.updateModsHashCache(data?.modVersions ?? []);
return data?.modVersions?.at(0);
}).catch((): undefined => {
log.error(`Failed to get mod by hash: ${hash}`);
return undefined;
}
this.updateModsHashCache(res.result?.data?.modVersions ?? []);
const missingMods = getModsFromCache(missingHashs);
return {...mods, ...missingMods};
});
}
}
@@ -6,11 +6,11 @@ import md5File from "md5-file";
import { RequestService } from "../request.service";
import { BS_EXECUTABLE } from "../../constants";
import log from "electron-log";
import { deleteFolder, pathExist, Progression, unlinkPath } from "../../helpers/fs.helpers";
import { deleteFile, deleteFolder, pathExist, Progression } from "../../helpers/fs.helpers";
import { lastValueFrom, Observable } from "rxjs";
import recursiveReadDir from "recursive-readdir";
import { sToMs } from "../../../shared/helpers/time.helpers";
import { copyFile, ensureDir, pathExistsSync, readdirSync, rm, unlink } from "fs-extra";
import { copyFile, ensureDir, pathExistsSync, readdirSync } from "fs-extra";
import { CustomError } from "shared/models/exceptions/custom-error.class";
import { popElement } from "shared/helpers/array.helpers";
import { LinuxService } from "../linux.service";
@@ -19,7 +19,6 @@ import crypto from "crypto";
import { BsmZipExtractor } from "main/models/bsm-zip-extractor.class";
import { BsmShellLog, bsmSpawn } from "main/helpers/os.helpers";
import { BbmFullMod, BbmModVersion, ExternalMod } from "../../../shared/models/mods/mod.interface";
import { Stats } from "fs";
export class BsModsManagerService {
private static instance: BsModsManagerService;
@@ -29,6 +28,8 @@ export class BsModsManagerService {
private readonly linuxService: LinuxService;
private readonly requestService: RequestService;
private manifestMatches: BbmModVersion[];
public static getInstance(): BsModsManagerService {
if (!BsModsManagerService.instance) {
BsModsManagerService.instance = new BsModsManagerService();
@@ -43,9 +44,14 @@ export class BsModsManagerService {
this.requestService = RequestService.getInstance();
}
private async getModsFromHashes(hashes: string[]): Promise<BbmModVersion[] | undefined> {
const mods = await this.beatModsApi.getModByHash(hashes)
return Object.values(mods).filter(mod => !mod.contentHashes.some(content => content.path.includes("IPA")));
private async getModFromHash(hash: string): Promise<BbmModVersion | undefined> {
const mod = await this.beatModsApi.getModByHash(hash);
if(mod?.contentHashes?.some(content => content.path.includes("IPA.exe"))){
return undefined;
}
return mod;
}
@@ -57,17 +63,47 @@ export class BsModsManagerService {
return [];
}
const ignoreFunc = (file: string, stats: Stats): boolean => {
if(!stats.isFile()) { return true; }
const ext = path.extname(file);
return !(ext === ".dll" || ext === ".exe" || ext === ".manifest");
}
const files = await recursiveReadDir(modsPath);
const files = await recursiveReadDir(modsPath, [ignoreFunc]);
const hashes = await Promise.all(files.map(file => md5File(file)));
const promises = files.map(async filePath => {
const ext = path.extname(filePath);
const mods = await this.getModsFromHashes(hashes);
return mods.filter(Boolean);
if (ext !== ".dll" && ext !== ".exe" && ext !== ".manifest") {
return undefined;
}
const hash = await md5File(filePath);
const mod = await this.getModFromHash(hash);
if (!mod) {
return undefined;
}
if (ext === ".manifest") {
this.manifestMatches.push(mod);
return undefined;
}
if (filePath.toLowerCase().includes("libs")) {
const manifestIndex = this.manifestMatches.findIndex(m => m.id === mod.id);
if (manifestIndex < 0) {
return undefined;
}
this.manifestMatches.splice(manifestIndex, 1);
}
return mod;
});
const results = await Promise.allSettled(promises);
return results.reduce((mods, mod) => {
if (mod?.status === "fulfilled" && mod?.value) {
mods.push(mod.value);
}
return mods;
}, [] as BbmModVersion[]);
}
private async getBsipaInstalled(version: BSVersion): Promise<BbmModVersion> {
@@ -77,7 +113,7 @@ export class BsModsManagerService {
return undefined;
}
const injectorMd5 = await md5File(injectorPath);
return (await this.beatModsApi.getModByHash([injectorMd5]))[injectorMd5];
return this.beatModsApi.getModByHash(injectorMd5);
}
private async downloadZip(zipUrl: string): Promise<BsmZipExtractor> {
@@ -254,11 +290,10 @@ export class BsModsManagerService {
const contentPath = path.join(ipaPath, content.name);
const res = await tryit(() => content.isDirectory() ? (
rm(contentPath, { force: true, recursive: true })
) : (
unlink(contentPath)
));
const res = await tryit(() => content.isDirectory()
? deleteFolder(contentPath)
: deleteFile(contentPath)
);
if(res.error){
log.error("Error while clearing IPA folder content", content.name, res.error);
@@ -282,7 +317,7 @@ export class BsModsManagerService {
const promises = mod.version.contentHashes.map(content => {
const file = content.path.replaceAll("IPA/", "").replaceAll("Data", "Beat Saber_Data");
return unlinkPath(path.join(verionPath, file));
return deleteFile(path.join(verionPath, file));
});
await Promise.all(promises);
@@ -295,7 +330,10 @@ export class BsModsManagerService {
const versionPath = await this.bsLocalService.getVersionPath(version);
const promises = mod.version.contentHashes.map(async content => {
return Promise.all([unlinkPath(path.join(versionPath, content.path)), unlinkPath(path.join(versionPath, "IPA", "Pending", content.path))]);
return Promise.all([
deleteFile(path.join(versionPath, content.path)),
deleteFile(path.join(versionPath, "IPA", "Pending", content.path))
]);
});
await Promise.all(promises);
@@ -308,6 +346,8 @@ export class BsModsManagerService {
}
public async getInstalledMods(version: BSVersion): Promise<BbmModVersion[]> {
this.manifestMatches = [];
const bsipa = await this.getBsipaInstalled(version);
const pluginsMods = await Promise.all([this.getModsInDir(version, ModsInstallFolder.PLUGINS_PENDING), this.getModsInDir(version, ModsInstallFolder.PLUGINS)]);
@@ -499,9 +539,11 @@ export class BsModsManagerService {
const versionMods = await this.getAvailableMods(version);
const installedMods = await this.getInstalledMods(version);
const fullInstalledMods: BbmFullMod[] = installedMods?.map(version => {
return { version, mod: versionMods.find(mod => version.modId === mod.mod.id)?.mod };
}) ?? [];
const fullInstalledMods = (installedMods || []).reduce((mods, version) => {
const mod = versionMods.find(mod => version.modId === mod.mod.id)?.mod;
if (mod) { mods.push({ version, mod }); }
return mods;
}, [] as BbmFullMod[]);
const progress = { current: 0, total: fullInstalledMods.length };
+2 -3
View File
@@ -1,11 +1,10 @@
import { createWriteStream, WriteStream } from 'fs';
import { Progression } from 'main/helpers/fs.helpers';
import { deleteFileSync, Progression } from 'main/helpers/fs.helpers';
import { Observable } from 'rxjs';
import { shareReplay, tap } from 'rxjs/operators';
import log from 'electron-log';
import got from 'got';
import { IncomingHttpHeaders, IncomingMessage } from 'http';
import { unlinkSync } from 'fs-extra';
import { tryit } from 'shared/helpers/error.helpers';
import path from 'path';
import { pipeline } from 'stream/promises';
@@ -84,7 +83,7 @@ export class RequestService {
pipeline(stream, file).catch(err => {
file?.destroy();
tryit(() => unlinkSync(dest));
tryit(() => deleteFileSync(dest));
subscriber.error(err);
});
});
@@ -17,7 +17,7 @@ import { PlaylistsManagerService } from "renderer/services/playlists-manager.ser
import { MapsManagerService } from "renderer/services/maps-manager.service";
import { map } from "rxjs";
export const LinkContentModal: ModalComponent<boolean, {version: BSVersion, contentType: "maps"|"playlists"|"avatars"|"sabers"|"platforms"|"blocks"}> = ({options: { data: { version, contentType } }, resolver }) => {
export const LinkContentModal: ModalComponent<void, {version: BSVersion, contentType: "maps"|"playlists"|"avatars"|"sabers"|"platforms"|"blocks"}> = ({options: { data: { version, contentType } }, resolver }) => {
const { text: t, element: te } = useTranslationV2();
const versionLinker = useService(VersionFolderLinkerService);
@@ -81,7 +81,7 @@ export class ModelsManagerService {
return this.versionFolderLinked.linkVersionFolder({
version,
relativeFolder: MODEL_TYPE_FOLDERS[type],
options: { keepContents: res.data !== false },
options: { keepContents: true },
});
}
@@ -132,7 +132,7 @@ export class PlaylistsManagerService {
return this.linker.linkVersionFolder({
version,
relativeFolder: PlaylistsManagerService.RELATIVE_PLAYLISTS_FOLDER,
options: { keepContents: !!modalRes.data },
options: { keepContents: true },
});
}