diff --git a/README.md b/README.md
index b16c3cd..08a8ced 100644
--- a/README.md
+++ b/README.md
@@ -1,140 +1,118 @@
# Slipstream
-**Slipstream** is a standalone, cross-platform app that launches the Epic Games version of Rocket League without the Epic Games Launcher.
+Slipstream is a standalone, cross-platform app that launches the Epic Games version of Rocket League **without the Epic Games Launcher**.
-This project would not exist without the work of **LittleScripterBoy** on the original [RocketLeagueLauncher](https://github.com/LittleScripterBoy/RocketLeagueLauncher). All credit for the authentication flow and the original concept belongs entirely to him. Slipstream is a rewrite that builds upon the foundation he created, aiming to make the tool more accessible and straightforward.
+This project builds upon the original [RocketLeagueLauncher](https://github.com/LittleScripterBoy/RocketLeagueLauncher) by **LittleScripterBoy**.
### Key Benefits
-* **Integrate Anywhere**: Add Slipstream to Steam for its overlay and controller support, run it standalone, or integrate it with other game launchers like Playnite or Lutris.
-* **Skip Epic Launcher**: Play Rocket League without the Epic Games Launcher running in the background.
-* **Simple Multi-Account Support**: Easily switch between multiple Epic Games accounts.
-* **No Dependencies**: Download and run a single executable; no Python or other libraries needed.
-
-## Installation
-
-> **Prerequisites:** Rocket League must be installed and kept up-to-date using a game manager like the Epic Games Launcher or Heroic Games Launcher. Slipstream only *launches* the game; it does not install, update, or manage game files.
-
-1. Go to the [**Releases** page](https://github.com/jun-eau/Slipstream/releases) and download the executable for your platform.
-2. Place the downloaded file in a dedicated folder, as Slipstream will generate configuration files in the same directory.
-3. Continue with the setup instructions for your operating system below.
-
-### Windows Setup
-
-For the best experience, it's recommended to add Slipstream to Steam to get the overlay and controller support.
-
-1. **Run the Launcher**: Double-click `Slipstream.exe`.
-2. **Locate Game File**: A file dialog will open. Navigate to your Rocket League installation and select `RocketLeague.exe` (usually in the `Binaries/Win64` folder).
-3. **Log In to Epic Games**: Your browser will open. Log in to the Epic account you want to use.
-4. **Get Authorization Code**: After logging in, you'll be redirected to a page with a 32-character `authorizationCode`. Copy this code.
-5. **Enter Code**: Paste the code into the launcher's dialog box and click OK.
-
-The game will now launch. A `config.json` file is created, so you won't have to repeat this.
-
-**To add Slipstream to Steam:**
+* **Integrate Anywhere**: Use with Steam (for overlay and controller support), standalone, or with other launchers like Playnite or Lutris.
+* **Skip Epic Launcher**: Play Rocket League without the Epic Games Launcher running.
+* **Optional BakkesMod**: Automatically launch BakkesMod (Windows & Linux).
+* **Simple Multi-Account**: Easily switch between Epic Games accounts.
+* **No Dependencies**: Single executable, no external libraries needed.
+
+## Installation & Setup
+
+> **Prerequisite:** Rocket League must be installed and kept up-to-date via a game manager (e.g., Epic Games Launcher, Heroic Games Launcher). Slipstream only *launches* the game.
+
+1. **Download**: Go to the [**Releases page**](https://github.com/jun-eau/Slipstream/releases) and download the executable for your platform.
+2. **Create a Folder**: Place the downloaded file in a new, dedicated folder. Slipstream will store its configuration file (`config.json`) there.
+3. **Run Slipstream**:
+ * **Windows**: Double-click `Slipstream.exe`.
+ * **Linux**:
+ * **Recommended (Proton/Steam)**:
+ 1. Add `Slipstream.exe` to Steam as a non-Steam game.
+ 2. In Steam, right-click Slipstream -> Properties -> Compatibility -> Force Proton (latest version).
+ 3. **Steam Deck Users**: Perform this setup in **Desktop Mode**.
+ * **Native Linux Binary**: `chmod +x Slipstream && ./Slipstream`. Use this for initial setup if Proton causes issues, or for use with tools like Lutris. It will save `config.json` but cannot launch the Windows game directly. Then use `Slipstream.exe` via Proton/Wine from the same folder.
+4. **Initial Configuration (One-Time Setup)**:
+ * **Locate Game**: A file dialog will prompt for `RocketLeague.exe` (usually in `Binaries/Win64` of your Rocket League installation).
+ * **BakkesMod (Optional)**: If you enable BakkesMod, you'll be prompted for `BakkesMod.exe`. See "Optional: BakkesMod Setup" under Usage for details.
+ * **Epic Games Login**: Your browser will open. Log in to your Epic Games account.
+ * **Authorization Code**: Copy the 32-character `authorizationCode` from the redirected page.
+ * **Enter Code**: Paste the code into Slipstream's dialog and click OK.
+
+The game will launch. Your settings are saved in `config.json` in the Slipstream folder.
+
+**To add Slipstream to Steam (Windows & Linux/Proton):**
1. In Steam: **Add a Game** -> **Add a Non-Steam Game...**
-2. **Browse...** to where you saved `Slipstream.exe` and select it.
+2. **Browse...** to `Slipstream.exe` and select it.
3. Click **Add Selected Programs**.
-### Linux Setup
-
-The recommended method for Linux is to use the Windows version (`Slipstream.exe`) with Proton, as this provides the best compatibility with gamepads and the Steam Overlay.
-
-> For **Steam Deck users**: do this setup process in **Desktop Mode** to ensure the file browser and Epic login work; the game will appear in Gaming Mode after setup.
+## Usage
-1. **Add to Steam**: In your Steam library, click **Add a Game** -> **Add a Non-Steam Game...** and select the `Slipstream.exe` file.
-2. **Force Proton**: Right-click on Slipstream in Steam -> **Properties...** -> **Compatibility**. Check the box to **"Force the use of a specific Steam Play compatibility tool"** and choose the latest Proton version.
-3. **Run and Configure**: Launch Slipstream from Steam. It will guide you through the one-time setup (locating `RocketLeague.exe`, browser login, etc.) just like on Windows.
+* **Updating Slipstream**: Replace your Slipstream executable with the latest from [Releases](https://github.com/jun-eau/Slipstream/releases). Your `config.json` will be preserved.
+* **Custom Launch Options**:
+ 1. In Steam, right-click Slipstream -> **Properties...**
+ 2. Under **General**, enter options in **Launch Options** (e.g., `-nomovie -high`). These are passed to Rocket League.
+* **Multiple Accounts**:
+ 1. Create a **new, separate folder** for each account.
+ 2. Copy the Slipstream executable into each new folder.
+ 3. Run it from the new folder for that account's first-time setup. Each folder gets its own `config.json`.
+ 4. If using Steam, add each Slipstream instance as a separate non-Steam game.
-Once configured, Slipstream will launch Rocket League correctly using Proton.
+
+Optional: BakkesMod Setup
-> **Using the Native Binary:** The native Linux binary is also provided. Its primary purpose is for initial setup without needing Steam/Proton running, or for use with other tools like Lutris. If you're having trouble with the file picker or authentication process under Proton, using the native binary first for setup can be a reliable alternative. To use it:
-> 1. Make it executable with `chmod +x Slipstream` and run with `./Slipstream`.
-> 2. Run through the setup as usual.
-> 3. After it saves your `config.json`, it will display a confirmation message, as it cannot launch the Windows game directly.
-> 4. You can then use `Slipstream.exe` with your preferred compatibility layer. Ensure it is in the same directory as `config.json`.
+Slipstream can automatically launch BakkesMod. If enabled during initial setup, you'll be prompted for `BakkesMod.exe`.
-## Usage & Configuration
+**Windows:**
+1. Install BakkesMod from [bakkesmod.com](https://bakkesmod.com/).
+2. When Slipstream asks, locate `BakkesMod.exe` (usually `C:\Program Files\BakkesMod\BakkesMod.exe`).
-### Updating Slipstream
+**Linux (using Wine/Proton):**
+BakkesMod is a Windows application, so it runs within Wine/Proton.
+1. Download `BakkesModSetup.exe` from [bakkesmod.com](https://bakkesmod.com/).
+2. Install it using your Wine/Proton environment:
+ * **Proton (via Steam):** Add `BakkesModSetup.exe` as a non-Steam game, force the same Proton version as Slipstream/Rocket League, and run it once.
+ * **Wine (standalone):** `wine /path/to/BakkesModSetup.exe`.
+3. Point Slipstream to the installed `BakkesMod.exe` within your Wine/Proton prefix (e.g., `~/.wine/drive_c/Program Files/BakkesMod/BakkesMod.exe` or `~/.steam/steam/steamapps/compatdata//pfx/drive_c/Program Files/BakkesMod/BakkesMod.exe`).
-Replace your current Slipstream executable with the latest one from the [Releases page](https://github.com/jun-eau/Slipstream/releases).
+> **If "Mod is out of date, waiting for an update" appears:** In the BakkesMod window (once running with Rocket League), go to "Settings", uncheck "Enable safe mode", and click "Yes" on the warning.
-### Custom Launch Options
+> BakkesMod on Steam Deck in Gaming Mode may require navigating windows (using the `Steam` button).
-Add Rocket League launch options via Steam:
+For detailed Linux help, see the [BakkesLinux guide](https://github.com/CrumblyLiquid/BakkesLinux) (Installation/Setup sections).
+
-1. In Steam, right-click Slipstream -> **Properties...**
-2. Under the **General** tab, find **Launch Options**.
-3. Enter options, space-separated (e.g., `-nomovie -high -USEALLAVAILABLECORES`). Slipstream forwards these to the game.
-
-This should also work well with other launchers.
-
-### Using Multiple Accounts
-
-1. Create a **new, separate folder** for each additional account.
-2. Copy the Slipstream executable into each new folder.
-3. Run it from the new folder and complete the first-time setup for that account. Each folder will have its own `config.json`, keeping accounts isolated. If using Steam, create separate library entries for each.
-
-## FAQ & Troubleshooting
+
+FAQ & Troubleshooting
#### Q: Do I still need the Epic Games Launcher installed?
-Yes. The Epic Launcher is still required for installing and updating the game. The main benefit of Slipstream is that you never have to actually run or interact with the launcher for gameplay sessions.
-
-#### Q: Does this improve my in-game FPS or performance?
-It can make the game boot faster, but it should not have any impact on your in-game performance (your FPS) one way or the other.
-
-#### Q: What's the difference between this and using Heroic or Legendary?
-Heroic and Legendary are intended to manage and interface with entire game libraries. Slipstream is a more minimal, standalone tool with a very specific goal: to be the simplest possible method to get Rocket League running directly through another launcher like Steam, without any other dependencies.
-
-#### Q: Does Slipstream modify my game files in any way?
-Not at all. Slipstream is a separate executable that should be kept in its own folder. It only reads your game path to launch the game; it never writes to or alters your game installation.
-
-#### Q: I'm getting a "version mismatch" error when I try to play online.
-This almost always means your game is out of date. Since Slipstream bypasses the launcher, it also bypasses the automatic update check. Run the Epic Games Launcher or Heroic to make sure Rocket League is fully updated, then try launching with Slipstream again.
-
-#### Q: Does this work with BakkesMod?
-Not at this time. BakkesMod support is the most requested feature and is currently being looked at for a future release.
+Yes, for installing and updating Rocket League. Slipstream lets you play without running the Epic Launcher.
-## Building from Source
+#### Q: Does this improve in-game FPS?
+It can speed up game boot time but shouldn't affect in-game FPS.
-If you prefer to compile the application yourself, you will need the **Go toolchain** (version 1.24 or newer) installed on your system.
+#### Q: Difference from Heroic/Legendary?
+Slipstream is minimal, focused only on launching Rocket League via other launchers (like Steam) without extra dependencies. Heroic/Legendary manage entire game libraries.
-1. **Clone the repository:**
- ```sh
- git clone https://github.com/jun-eau/Slipstream.git
- ```
+#### Q: Does Slipstream modify game files?
+No. It only reads your game path to launch the game.
-2. **Navigate to the project directory:**
- ```sh
- cd Slipstream
- ```
+#### Q: "Version mismatch" error online?
+Your game is likely outdated. Update Rocket League via Epic Games Launcher or Heroic, then try Slipstream again.
+
-3. **Build the executable:**
- The Go toolchain makes it simple to compile for different operating systems. Run the command corresponding to your target platform.
+
+Building from Source
- **For Windows (64-bit):**
- ```sh
- go build -o Slipstream.exe .
- ```
- *On Linux or macOS, you can cross-compile for Windows with:*
- ```sh
- GOOS=windows GOARCH=amd64 go build -o Slipstream.exe .
- ```
+Requires **Go toolchain** (v1.24+).
- **For Linux (64-bit):**
- ```sh
- go build -o Slipstream .
- ```
- *On Windows, you can cross-compile for Linux with:*
- ```powershell
- $env:GOOS = "linux"; $env:GOARCH = "amd64"; go build -o Slipstream .
- ```
+1. **Clone:** `git clone https://github.com/jun-eau/Slipstream.git`
+2. **Navigate:** `cd Slipstream`
+3. **Build:**
+ * **Windows (64-bit):** `go build -o Slipstream.exe .`
+ * Cross-compile on Linux/macOS: `GOOS=windows GOARCH=amd64 go build -o Slipstream.exe .`
+ * **Linux (64-bit):** `go build -o Slipstream .`
+ * Cross-compile on Windows: `$env:GOOS = "linux"; $env:GOARCH = "amd64"; go build -o Slipstream .`
-After running the command, the `Slipstream.exe` or `Slipstream` executable will be created in the project directory.
+The executable will be in the project directory.
+
## License and Credits
-This project is open source. See the `LICENSE` file for more details.
+This project is open source (see `LICENSE` file).
-This project is a derivative of [RocketLeagueLauncher](https://github.com/LittleScripterBoy/RocketLeagueLauncher) by **LittleScripterBoy**. The original project was unlicensed; an [issue](https://github.com/LittleScripterBoy/RocketLeagueLauncher/issues/1) has been opened requesting a permissive license be added. Slipstream is distributed in the good-faith belief that the original author would not object to the continuation and improvement of their work.
+It's a derivative of [RocketLeagueLauncher](https://github.com/LittleScripterBoy/RocketLeagueLauncher) by **LittleScripterBoy**. Original project was unlicensed; an [issue](https://github.com/LittleScripterBoy/RocketLeagueLauncher/issues/1) requests a permissive license. Slipstream is distributed in good faith.
diff --git a/main.go b/main.go
index 877c074..c5d9bdf 100644
--- a/main.go
+++ b/main.go
@@ -13,6 +13,7 @@ import (
"path/filepath"
"runtime"
"strings"
+ "time" // Added for BakkesMod launch delay
"github.com/google/uuid"
"github.com/ncruces/zenity"
@@ -36,8 +37,12 @@ const (
// Config holds all application settings.
type Config struct {
- RocketLeaguePath string `json:"rocket_league_path"`
- EpicToken string `json:"epic_token,omitempty"`
+ RocketLeaguePath string `json:"rocket_league_path"`
+ EpicToken string `json:"epic_token,omitempty"`
+ BakkesModEnabled bool `json:"bakkesmod_enabled"` // No omitempty, so it defaults to false in JSON
+ BakkesModPath string `json:"bakkesmod_path,omitempty"`
+ BakkesModLaunchDelay int `json:"bakkesmod_launch_delay,omitempty"`
+ BakkesModSetupDeclined bool `json:"bakkesmod_setup_declined"` // No omitempty, so it defaults to false
}
// LaunchCredentials holds the final codes needed to start the game.
@@ -116,7 +121,8 @@ func main() {
// 4. Launch Rocket League with the obtained credentials and any extra args.
log.Println("Successfully authenticated. Launching Rocket League...")
// os.Args[0] is the program name, os.Args[1:] is all subsequent arguments.
- if err := launchGame(cfg.RocketLeaguePath, creds, os.Args[1:]); err != nil {
+ // Updated to pass the full cfg object
+ if err := launchGame(cfg, creds, os.Args[1:]); err != nil {
detailedMsg := "Failed to Launch Rocket League.\n\n" +
"Please ensure the Rocket League path is correctly set in 'config.json' and that the game executable is not missing or corrupted.\n\n" +
"Details: " + err.Error()
@@ -192,37 +198,68 @@ func (a *Authenticator) GetLaunchCredentials(currentToken string) (LaunchCredent
// launchGame starts the game with the provided credentials and arguments.
// On Linux, it detects if the target is a Windows executable and guides the user.
-func launchGame(path string, creds LaunchCredentials, extraArgs []string) error {
- // On Linux, we can't directly execute a Windows .exe.
- // Instead of failing with a cryptic error, we'll guide the user.
- if runtime.GOOS == "linux" && strings.HasSuffix(strings.ToLower(path), ".exe") {
+// The function signature now takes the full Config object.
+func launchGame(cfg Config, creds LaunchCredentials, extraArgs []string) error {
+ // 1. Preserve existing Linux .exe check.
+ // This ensures the "Setup Complete" message appears correctly on Linux
+ // before any launch attempt is made.
+ if runtime.GOOS == "linux" && strings.HasSuffix(strings.ToLower(cfg.RocketLeaguePath), ".exe") {
showInfo("Setup Complete!",
"Your configuration and login token have been successfully saved to 'config.json'.\n\n"+
"To play, please add 'Slipstream.exe' (the Windows version) to Steam or Lutris and run it using Proton or Wine. "+
"It will use the config file you just created.")
- // We return 'nil' because this is an expected outcome, not an application error.
- return nil
+ return nil // Expected outcome on Linux with .exe path
}
- args := []string{
+ // 2. Launch Rocket League (asynchronously).
+ log.Println("Launching Rocket League...")
+ rlArgs := []string{
"-AUTH_LOGIN=unused",
"-AUTH_PASSWORD=" + creds.ExchangeCode,
"-AUTH_TYPE=exchangecode",
"-epicapp=Sugar",
"-epicenv=Prod",
"-EpicPortal",
- "-epicusername=\"\"",
+ "-epicusername=\"\"", // Intentionally empty as per original args
"-epicuserid=" + creds.AccountID,
}
+ rlArgs = append(rlArgs, extraArgs...)
+ rlCmd := exec.Command(cfg.RocketLeaguePath, rlArgs...)
- // Append the extra launch options from Steam.
- args = append(args, extraArgs...)
+ if err := rlCmd.Start(); err != nil {
+ return fmt.Errorf("failed to start Rocket League at %s: %w", cfg.RocketLeaguePath, err)
+ }
+ log.Println("Rocket League process started.")
- cmd := exec.Command(path, args...)
- if err := cmd.Start(); err != nil {
- return fmt.Errorf("could not start the game executable at:\n%s\n\nError: %w\n\nPlease ensure the path is correct in %s", path, err, configFileName)
+ // 3. Conditional BakkesMod Launch.
+ if !cfg.BakkesModEnabled || cfg.BakkesModPath == "" {
+ log.Println("BakkesMod is not enabled or path is not set. Launch complete.")
+ return nil // Standard launch finished successfully.
}
- return nil
+
+ // 4. Execute BakkesMod launch sequence.
+ // We need to import "time" for this. Ensure it's added if not already.
+ // (It should be, due to existing logging, but good to double check)
+ delay := time.Duration(cfg.BakkesModLaunchDelay) * time.Second
+ log.Printf("BakkesMod is enabled. Waiting for %v before launching...", delay)
+ time.Sleep(delay) // Requires "time" package
+
+ log.Println("Launching BakkesMod...")
+ bmCmd := exec.Command(cfg.BakkesModPath) // No arguments needed for BakkesMod.exe
+ if err := bmCmd.Start(); err != nil {
+ // Inform the user but do not treat it as a fatal error for the game itself.
+ errorMsg := fmt.Sprintf(
+ "Could not start BakkesMod.exe at the specified path:\n\n%s\n\nError: %v\n\nRocket League should still be running.",
+ cfg.BakkesModPath, err,
+ )
+ showError("BakkesMod Launch Failed", errorMsg)
+ log.Printf("Error launching BakkesMod: %v", err)
+ // Non-fatal error, Rocket League is (presumably) running.
+ } else {
+ log.Println("BakkesMod process started.")
+ }
+
+ return nil // Two-process launch sequence finished (or attempted).
}
// --- Authentication Steps ---
@@ -335,7 +372,7 @@ func loadConfig() (Config, error) {
// If the path is missing, always prompt for it.
if cfg.RocketLeaguePath == "" {
showInfo("Rocket League Path Setup", "Please locate your Rocket League executable (e.g., RocketLeague.exe). This will only be asked once.")
- selectedPath, err := zenity.SelectFile(
+ rlPath, err := zenity.SelectFile(
zenity.Title("Select Rocket League Executable"),
zenity.FileFilters{
{Name: "Rocket League Executable", Patterns: []string{"RocketLeague.exe", "RocketLeague"}, CaseFold: true},
@@ -343,13 +380,71 @@ func loadConfig() (Config, error) {
},
)
if err != nil {
- return cfg, fmt.Errorf("you must select a path to continue")
+ return cfg, fmt.Errorf("you must select a Rocket League path to continue")
}
- cfg.RocketLeaguePath = selectedPath
+ cfg.RocketLeaguePath = rlPath
+ // Save immediately after getting RL path, so it's there before BM setup
if err := saveConfig(cfg); err != nil {
- return cfg, fmt.Errorf("could not save the configuration file: %w", err)
+ // Log or show error, but try to continue to BM setup if possible
+ log.Printf("Warning: could not save Rocket League path: %v", err)
}
}
+
+ // BakkesMod Setup Prompt - only if RL path is set and BM not already configured or declined
+ if cfg.RocketLeaguePath != "" && cfg.BakkesModPath == "" && !cfg.BakkesModSetupDeclined {
+ log.Println("Prompting for BakkesMod setup.")
+ err := zenity.Question("Would you like to enable automatic launching for BakkesMod?",
+ zenity.Title("BakkesMod Setup"),
+ zenity.DefaultCancel(), // Makes "No" the default if user just closes dialog
+ zenity.OKLabel("Yes"),
+ zenity.CancelLabel("No"),
+ )
+
+ if err == nil { // User clicked "Yes"
+ log.Println("User opted to set up BakkesMod.")
+ bmPath, err := zenity.SelectFile(
+ zenity.Title("Select BakkesMod.exe"),
+ zenity.FileFilters{
+ {Name: "BakkesMod Executable", Patterns: []string{"BakkesMod.exe"}, CaseFold: true},
+ {Name: "All Files", Patterns: []string{"*"}},
+ },
+ )
+ if err == nil && bmPath != "" {
+ log.Printf("BakkesMod path selected: %s", bmPath)
+ cfg.BakkesModPath = bmPath
+ cfg.BakkesModEnabled = true
+ if cfg.BakkesModLaunchDelay == 0 { // Set default delay if not already set by user
+ cfg.BakkesModLaunchDelay = 5
+ }
+ cfg.BakkesModSetupDeclined = false // Ensure this is false if they just set it up
+ } else {
+ log.Println("User did not select a BakkesMod path or cancelled.")
+ // User cancelled BakkesMod selection, treat as "No" for this session, but don't set Declined.
+ // They might want to try again next time.
+ // If we want to treat this as a permanent "No", we'd set BakkesModSetupDeclined = true here.
+ // For now, let's assume cancelling file dialog means they don't want it *now*.
+ }
+ } else { // User clicked "No" or closed the dialog
+ log.Println("User declined BakkesMod setup.")
+ cfg.BakkesModSetupDeclined = true
+ cfg.BakkesModEnabled = false // Ensure it's disabled if they decline
+ }
+ // Save config after BM interaction (or lack thereof)
+ if err := saveConfig(cfg); err != nil {
+ return cfg, fmt.Errorf("could not save BakkesMod configuration: %w", err)
+ }
+ }
+
+ // Ensure default launch delay if enabled and not set
+ if cfg.BakkesModEnabled && cfg.BakkesModLaunchDelay == 0 {
+ log.Println("BakkesMod enabled but launch delay is 0, setting to default (5s).")
+ cfg.BakkesModLaunchDelay = 5
+ if err := saveConfig(cfg); err != nil {
+ // Log this, but it's not critical enough to halt the app
+ log.Printf("Warning: could not save default BakkesMod launch delay: %v", err)
+ }
+ }
+
return cfg, nil
}