Files
plugin-helper/README.md
T

9.9 KiB

plugin-helper

plugin-helper is an early Python CLI for managing Beat Saber plugins in BSManager installs.

The first implementation focuses on safe local workflows:

  • discover BSManager instances
  • scan existing Plugins/ and Libs/ files
  • read the checked-in plugin catalog and per-version locks
  • generate a machine-readable install plan from local release assets
  • apply exactly that plan and record managed install state
  • uninstall only files recorded in managed install state

Default BSManager instance root:

/home/pleb/.local/share/BSManager/BSInstances

Default plugin-helper state directory:

$XDG_STATE_HOME/plugin-helper

If XDG_STATE_HOME is not set, the state directory defaults to:

~/.local/state/plugin-helper

Override the instance root with --instances-root, PLUGIN_HELPER_INSTANCES_ROOT, or plugin-helper.local.toml. To search multiple explicit roots, separate them with : on Linux/macOS or ; on Windows.

Override the state directory with --state-dir, PLUGIN_HELPER_STATE_DIR, or plugin-helper.local.toml.

Local Configuration

This checkout is intended to manage the local Linux BSManager install. If you also manage a Windows install, use a separate clone on that partition and point both clones at the same state directory only when you intentionally want one shared source of truth.

Copy the example config and adjust paths if needed:

cp plugin-helper.toml.example plugin-helper.local.toml

plugin-helper.local.toml is ignored by git and uses top-level fields:

instances_root = "~/.local/share/BSManager/BSInstances"
state_dir = "~/.local/state/plugin-helper"

For repo-local state, set:

state_dir = ".state"

For a shared Windows-partition state directory, set the same state_dir in both clones, for example:

state_dir = "~/Windows/Users/pleb/ops/plugin-helper/.state"

CLI flags override environment variables, environment variables override local config, and local config overrides built-in defaults.

Native Windows

For a separate checkout on a Windows partition, copy the Windows example config and adjust paths if needed:

Copy-Item plugin-helper.windows.toml.example plugin-helper.windows.toml

plugin-helper.windows.toml is ignored by git. It uses profile entries:

[[profiles]]
id = "windows"
label = "Native Windows BSManager"
instances_root = "~/BSManager/BSInstances"
state_dir = ".state"

Run commands with --config and --profile so native Windows state stays in that checkout's .state/ directory. If plugin-helper.windows.toml has only one [[profiles]] entry, that profile is selected automatically when --profile is omitted:

py -m venv .venv
.\.venv\Scripts\Activate.ps1
py -m pip install -e .
py -m plugin_helper
py -m plugin_helper --config plugin-helper.windows.toml --profile windows installed --instance 1.44.1

On native Windows, bootstrap runs IPA.exe "Beat Saber.exe" -n directly instead of through Proton. bootstrap-check accepts a recorded native bootstrap without requiring Logs/_latest.log when IPA.exe "Beat Saber.exe" -n completed successfully.

When no config file is present on Windows, defaults are ~/BSManager/BSInstances and %LOCALAPPDATA%/plugin-helper.

Building plugins

PC BSIPA plugins target .NET Framework (net48). On native Windows, install the reference assemblies before dotnet build:

winget install -e --id Microsoft.DotNet.Framework.DeveloperPack_4

Point BeatSaberDir at your BSManager instance and pass -p:DisableCopyToPlugins=True so builds do not mutate the live install. See .agents/skills/beatsaber-plugin-builder/ for the full workflow.

Commands

For normal use, run the Textual menu from the repo root:

PYTHONPATH=src python -m plugin_helper

That is equivalent to PYTHONPATH=src python -m plugin_helper menu when run from an interactive terminal.

The menu reads plugin-helper.local.toml when present, shows each discovered Beat Saber install with its resolved state directory, and lists plugins from that instance's version lock. Managed install state decides whether each row is currently checked or unchecked. In the plugin table, use d to disable all currently enabled plugins (except bsipa) and e to enable all currently unchecked plugins. With a single Beat Saber installation, the menu opens the plugin table directly. Re-enabling a plugin auto-bootstraps BSIPA when needed.

Use s to save the set of currently enabled plugins as that instance's known-good set, and g to restore it later: plugins outside the saved set are disabled and any saved plugins that are currently disabled/missing are re-enabled from local assets. This is handy after a newly added mod pulls in extra dependencies you don't actually want — press g to snap back to the combination you know works. Only one known-good set is kept per instance; saving again overwrites it. The equivalent automation commands are save-known-good and restore-known-good.

The individual subcommands are mostly for automation and debugging. If you use them, pass --state-dir directly only when you intentionally want to override the configured state directory for one command.

Install assets are currently expected to already exist locally, usually under:

<state-dir>/cache/downloads/<instance>/<plugin-id>/

Lock entries may include download_url, the exact public zip or DLL URL for the locked artifact. The URL is currently audit/recovery metadata; plan, check, and apply still use local assets and do not fetch missing downloads automatically.

Composite local assets should have an explicit reconstruction recipe checked in with the helper. For example, Cinema's external Windows downloader tools are a small deterministic bundle rebuilt from upstream executables with:

PYTHONPATH=src python -m plugin_helper.recipes.cinema_tools --instance 1.44.1

Use updates to audit the version lock against obvious public update sources:

PYTHONPATH=src python -m plugin_helper --state-dir .state updates --instance 1.44.1

The command checks GitHub release assets for ordinary repository-backed locks and BeatMods verified metadata for beatmods-* locks. Patreon and Discord paid/private sources are skipped, and local builds, PR builds, failed compatibility trials, and manual installs are marked for review instead of being treated as routine updates.

Beat Saber Data Backups

backup-userdata copies the mounted Windows UserData folder and Beat Saber Windows app data into the adjacent ../backups repo. With the Windows mount at ~/Windows, the helper infers Beat Saber's Windows app data as:

/home/pleb/Windows/Users/pleb/AppData/LocalLow/Hyperbolic Magnetism/Beat Saber

Example manual backup after mounting Windows:

PYTHONPATH=src python -m plugin_helper \
  --instances-root /home/pleb/Windows/Users/pleb/BSManager/BSInstances \
  backup-userdata \
  --instance 1.44.1

By default the backup repo receives plain copied files under ../backups/beat-saber/UserData and ../backups/beat-saber/AppData, plus ../backups/beat-saber/backup-descriptor.json describing the source paths from the latest backup run. Use --appdata-path <path> if the Windows profile path ever differs, --no-appdata for a UserData-only sync, or --backup-root <path> to choose a different destination.

restore-userdata copies those backup trees back into a target instance. It moves the current UserData and AppData trees aside first as <name>.pre-restore-<timestamp> snapshots. On Linux BSManager installs, AppData is restored into the BSManager SharedContent Proton prefix unless --appdata-path is provided. If backup-descriptor.json is present, its instance field must match --instance.

Example restore into the local Linux instance:

PYTHONPATH=src python -m plugin_helper \
  --instances-root /home/pleb/.local/share/BSManager/BSInstances \
  restore-userdata \
  --instance 1.44.1

The backup intentionally omits bulky/generated data:

  • UserData/BeatLeader/Replays
  • UserData/BeatLeader/ReplayerCache
  • UserData/BeatLeader/LeaderboardsCache
  • UserData/BeatLeader/ReplayHeadersCache
  • UserData/ScoreSaber/Replays
  • UserData/BeatSaberPlus/Cache
  • UserData/BeatSaverNotifier.json
  • UserData/Accsaber/PlayerScoreCache.json
  • UserData/NalulunaAvatars/cache
  • UserData/SongDetailsCache.proto
  • AppData/com.unity.addressables
  • *.log

Other large folders seen in the 1.44.1 UserData tree are Custom Campaigns, SongCore, AssetBundleLoadingTools, NalulunaMenu, and NalulunaSkybox. Those are not skipped by default because they can contain custom content or non-obvious user choices rather than pure cache data.

Operational notes

  • BSManager can inherit launch arguments configured in Steam for Beat Saber. Check both places before debugging black screens or startup hangs. Duplicating arguments such as --no-yeet fpfc can make the game fail command-line parsing after BSIPA and plugins have already loaded.
  • BSIPA is managed as a first-class bootstrap phase. The bootstrap command applies the locked bsipa root archive, runs IPA.exe "Beat Saber.exe" -n (natively on Windows or through Proton on Linux), and records every bootstrap-relevant file under root IPA.exe*, winhttp.dll, Libs/, and IPA/, including backups created during patching.
  • If an instance version lock includes bsipa, ordinary plugin plans require a recorded bootstrap state plus a Logs/_latest.log that shows BSIPA startup. Use bootstrap-check before planning a batch when you want a quick gate.
  • Use docs/SMOKETEST.md after installing or removing a plugin batch. It documents the short Proton/BSManager launch loop, IPA log checks, and teardown commands.
  • The 1.44.1 migration tracker lives in docs/notes/install-and-verify-plugins-1.44.1.md.