From 075ae3b69a6d40f13eb48bb1ead2a7038c4029e3 Mon Sep 17 00:00:00 2001 From: kate Date: Fri, 31 Oct 2025 16:40:03 +0800 Subject: [PATCH 01/16] Fixed paths not working for code signing --- .github/workflows/compile.yml | 2 +- .github/workflows/release.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/compile.yml b/.github/workflows/compile.yml index 50c0bb3..aaf1edb 100644 --- a/.github/workflows/compile.yml +++ b/.github/workflows/compile.yml @@ -67,7 +67,7 @@ jobs: with: certificate: '${{ secrets.CODESIGN }}' password: '${{ secrets.CODESIGN_PASSWORD }}' - folder: 'target/${{ matrix.target }}/debug/' + folder: 'target/${{ matrix.target }}/debug' description: 'beans-rs' certificatesha1: '${{ secrets.CODESIGN_HASH }}' - name: Upload artifact diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bc3e360..6c5d235 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -69,7 +69,7 @@ jobs: with: certificate: '${{ secrets.CODESIGN }}' password: '${{ secrets.CODESIGN_PASSWORD }}' - folder: 'target/${{ matrix.target }}/release/' + folder: 'target/${{ matrix.target }}/release' description: 'beans-rs' certificatesha1: '${{ secrets.CODESIGN_HASH }}' From f3f85cf046587b78cfcbfcd1500799c4ff1f128a Mon Sep 17 00:00:00 2001 From: kate Date: Fri, 31 Oct 2025 16:47:42 +0800 Subject: [PATCH 02/16] Use windows-2022 instead of windows-2025 (this is what i think broke it) --- .github/workflows/compile.yml | 2 +- .github/workflows/release.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/compile.yml b/.github/workflows/compile.yml index aaf1edb..520e8f7 100644 --- a/.github/workflows/compile.yml +++ b/.github/workflows/compile.yml @@ -18,7 +18,7 @@ jobs: - os: ubuntu-22.04 filename: 'beans-rs' target: x86_64-unknown-linux-gnu - - os: windows-latest + - os: windows-2022 target: x86_64-pc-windows-msvc filename: 'beans-rs.exe' runs-on: ${{ matrix.os }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6c5d235..863289c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -19,7 +19,7 @@ jobs: filename: 'beans-rs' target: x86_64-unknown-linux-gnu - - os: windows-latest + - os: windows-2022 target: x86_64-pc-windows-msvc filename: 'beans-rs.exe' From fbd7cf8651f896a5e53c4c434cf4b2abfa3c406f Mon Sep 17 00:00:00 2001 From: dani Date: Sun, 2 Nov 2025 02:51:26 -0500 Subject: [PATCH 03/16] fix: update deprecated actions --- .github/workflows/compile.yml | 4 ++-- .github/workflows/release.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/compile.yml b/.github/workflows/compile.yml index 50c0bb3..4e00a5f 100644 --- a/.github/workflows/compile.yml +++ b/.github/workflows/compile.yml @@ -52,10 +52,10 @@ jobs: libfltk1.3-dev \ libssl-dev - - uses: actions-rs/toolchain@v1 + - uses: dtolnay/rust-toolchain@stable with: toolchain: nightly-2025-01-10 - target: ${{ matrix.target }} + targets: ${{ matrix.target }} - uses: actions-rs/cargo@v1 with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bc3e360..fbdc3af 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -53,10 +53,10 @@ jobs: libfltk1.3-dev \ libssl-dev - - uses: actions-rs/toolchain@v1 + - uses: dtolnay/rust-toolchain@stable with: toolchain: nightly-2025-01-10 - target: ${{ matrix.target }} + targets: ${{ matrix.target }} - uses: actions-rs/cargo@v1 with: From 329606b9fa4910b276fd1a9fb279dea16f8fbc49 Mon Sep 17 00:00:00 2001 From: dani Date: Sun, 2 Nov 2025 02:13:12 -0500 Subject: [PATCH 04/16] chore: workflow name --- .github/workflows/release.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fbdc3af..3435710 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,4 +1,5 @@ # .github/workflows/release.yml +name: Release on: workflow_dispatch: From e164acdbf905fc37d449612613c955312c24e622 Mon Sep 17 00:00:00 2001 From: dani Date: Fri, 7 Nov 2025 19:40:19 -0500 Subject: [PATCH 05/16] fix: codesigning step accidentally skipped --- .github/workflows/compile.yml | 2 +- .github/workflows/release.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/compile.yml b/.github/workflows/compile.yml index 7b52b7e..1b64635 100644 --- a/.github/workflows/compile.yml +++ b/.github/workflows/compile.yml @@ -62,7 +62,7 @@ jobs: command: build args: --verbose --target ${{ matrix.target }} - name: Code Signing (Windows) - if: ${{ matrix.os == 'windows-latest' }} + if: ${{ matrix.os == 'windows-2022' }} uses: skymatic/code-sign-action@v1 with: certificate: '${{ secrets.CODESIGN }}' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 80c9c77..16759e4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -65,7 +65,7 @@ jobs: args: --release --target ${{ matrix.target }} - name: Code Signing (Windows) - if: ${{ matrix.os == 'windows-latest' }} + if: ${{ matrix.os == 'windows-2022' }} uses: skymatic/code-sign-action@v1 with: certificate: '${{ secrets.CODESIGN }}' From 71a76636159cac2389950480405e50bcc983650f Mon Sep 17 00:00:00 2001 From: dani Date: Sat, 8 Nov 2025 01:07:37 -0500 Subject: [PATCH 06/16] docs: new dir + server integration --- working commands.md => docs/commands.md | 0 docs/server-integration.md | 165 ++++++++++++++++++++++++ 2 files changed, 165 insertions(+) rename working commands.md => docs/commands.md (100%) create mode 100644 docs/server-integration.md diff --git a/working commands.md b/docs/commands.md similarity index 100% rename from working commands.md rename to docs/commands.md diff --git a/docs/server-integration.md b/docs/server-integration.md new file mode 100644 index 0000000..443b410 --- /dev/null +++ b/docs/server-integration.md @@ -0,0 +1,165 @@ +# Server-Side Integration + +`beans-rs` relies on Server-Side Integration to keep players up to date with the latest versions of Sourcemods. By utilizing a combination of free software and a few configuration files, Sourcemod developers can quickly deploy updates for either singleplayer or multiplayer experiences. + +## appvar.json + +In the `src` folder there is a file called `appvar.json` which determines the currently targeted Sourcemod. By default it will be configured for [Open Fortress](https://openfortress.fun), but it can be modified to support any mod. + +```json +{ + "mod": { + "sm_name": "open_fortress", + "short_name": "of", + "name_stylized": "Open Fortress" + }, + "remote": { + "base_url": "https://of-proxy.kate.pet/", + "versions_url": "https://of-proxy.kate.pet/versions.json" + } +} +``` + +- `sm_name`: The folder name of the sourcemod (e.g. open_fortress, pf2, tf2classic, tf_coop_extended). +- `short_name`: This is the shorthand name used when identifying diff files from the static file server associated with the `beans-rs` instance (e.g. of-20to21.pwr, pf2-73to74.pwr). +- `name_stylized`: The full name of the mod. +- `base_url`: The file server address where the `beans-rs` related files will be stored. **Do not forget to include `/` at the end of the link.** +- `versions_url`: The file server address where the `versions.json` file will be stored. + +## beans-rs + +To start using `beans-rs`, there must be a matching static file server hosted by the Sourcemod developer. The static file server will host a combination of archive, diff, and signature files. In charge of all of them is a file called `versions.json` and by carefully following these instructions, integrating `beans-rs` with any mod should be a simple process. `beans-rs` relies on [butler](https://itch.io/docs/butler) to generate signatures and diffs used in this process. + +### Versions Variables + +In `versions.json` there are numerous variables that relate to different components of an update. + +- `signature`: The `.sig` file generated by butler when creating a signature of a build. +- `heal`: The "heal" zip file is an archive of the game's contents that is used by butler. +- `file`: This is a `tar.zst` archive of the game used by `beans-rs` when making a fresh install. +- `url`: This is the torrent file related to a download, if your game is not available via torrent then just reuse the `file` entry data. +- `presz`: "Pre-Size" is the size of the `file` component in bytes. +- `postsz`: "Post-Size" is the file of the contents of `file` component in bytes after extraction. +- `tempreq`: The size required to temporarily store the `file` patch. + +### Versions File Formatting + +Here is an example of how to format a `versions.json` file. + +This example will be based off the [versions.json](https://beans.adastral.net/versions.json) file hosted by [Open Fortress](https://openfortress.fun). + +```json +{ + "versions": + { + "15": { + "signature": "of-15.sig", + "heal": "of-15-heal.zip" + }, + "20": { + "url": "of-20.torrent", + "file": "of-20.tar.zstd", + "presz": 5050680575, + "postsz": 13884901888, + "signature": "of-20.sig", + "heal": "of-20-heal.zip" + }, + "21": { + "url": "of-21.torrent", + "file": "of-21.tar.zstd", + "presz": 5050680575, + "postsz": 13884901888, + "signature": "of-21.sig", + "heal": "of-21-heal.zip" + } + }, + "patches": + { + + "15": { + "url": "of-15to21.torrent", + "file": "of-15to21.pwr", + "tempreq": 7500000000 + }, + "20": { + "url": "of-20to21.torrent", + "file": "of-20to21.pwr", + "tempreq": 7500000000 + } + } +} +``` + +In both `versions` and `patches` there is a numbered string entry for each version. It's important to stick to whole numbers when creating a new entry (e.g. 100, 201, 23, 60). + +The `versions` section is the data related to downloadable versions. `beans-rs` will always default to the latest version number. For Open Fortress Revision 15 (written as `15` in the entry), there is no `file`, `presz`, or `postsz` entry as Revision 15 can only be upgraded from. In other games that use the [`MAJOR.MINOR.PATCH` system](https://semver.org/), the version number **must** be converted to the closest whole number. For instance `0.7.4` as `74` or `2.2.3` as `223`. + +In the `patches` section, there is only a `url`, `file` and `tempreq` entry. The `.pwr` file is a diff generated by [butler](https://itch.io/docs/butler) and is used to upgrade the user to the latest version. As of `beans-rs` version 1.7.3, the `url` component is not yet optional and if the mod is not available via torrent, the `url` component should contain the same information as `file`. + +## butler + +Install butler based off the instructions on the [itch.io docs](https://itch.io/docs/butler/installing.html). + +```sh +curl -L -o butler.zip https://broth.itch.ovh/butler/linux-amd64/LATEST/archive/default +unzip butler.zip +chmod +x butler +``` + +> [!IMPORTANT] +> ### Read before generating diffs or signatures +> Please make sure that your game files are organized as such: +> - `[version number]/[game shorthand name]` +> +> For example: +> - `of-21/open_fortress` +> - `pf2-74/pf2` + +### Creating Heal Zips + +The `-heal.zip` archive contains all of the contents of the `game` folder. + +```sh +cd of-21/open_fortress +zip -r9 ../../of-21-heal.zip . +``` + +The heal zip must be an archive of the inside of the `game` folder or else it will create conflicts when generating diffs and signature. + +### Creating Tar Zstd + +The `.tar.zst` archive contains the `game` folder. +```sh +cd of-21 +tar --zstd -cvf ../of-21.tar.zst open_fortress +``` + +It's important for the Tar to be made of the game folder itself and not the contents. The Tar file will be used for fresh installs of your game and will be directly extracted to the `sourcemods` folder. + +### Generating a Diff with Signatures + +This section will generate a `.pwr` diff file and a `.pwr.sig` signature file which will be stored together in the file server. + +```sh +./butler diff --assume-yes of-20-heal.zip of-21-heal.zip of-20to21.pwr +``` + +When creating the diff file it is important to match this file structure: `of-20to21.pwr` or `[game]-[old version]to[latest version].pwr`. + +The first heal zip is the version you're upgrading from and the second one should be the latest. `butler` automatically makes a diff based on the contents of the heal zip, this is why it's important to make the heal zips based of the contents of a `game` folder rather than of the `game` folder itself. + +## Generating Build Signatures + +Creating a signature of a build can be done by selecting the heal zip of the version. + +```sh +./butler sign of-21-heal.zip of-21-heal.zip.sig +``` + +This will be used for verifying the integrity of the files when updating. + +## Iterating Versions + +When your `sourcemod` receives a new update it's a simple process to generate new `.pwr` files to the new latest version. + +Generate the `-heal.zip` and `.tar.zst` file for the new latest version and then replace the old `.pwr` and `.pwr.sig` files by running the diff command with the new latest version. Make sure to update the remote `versions.json` file to reflect the new diff and heal zips. `beans-rs` will always stay coordinated with the server so long as the `verion.json` file is maintained and the new update files are present on the static file server. \ No newline at end of file From 7be3e7b1b13a0d29f923d80cf2aa3f5f57c1bcbc Mon Sep 17 00:00:00 2001 From: dani Date: Sat, 8 Nov 2025 01:38:05 -0500 Subject: [PATCH 07/16] docs: suggested revisions for readme + CONTRIBUTING --- CONTRIBUTING.md | 48 ++++++++++++++++++++++++++ README.md | 91 ++++++++++++------------------------------------- 2 files changed, 70 insertions(+), 69 deletions(-) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..51aeed4 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,48 @@ +# Contributing + +**When creating a PR, you must branch off the `develop` branch.** When merging back into this repo remember to select the `develop` as the branch to merge into. After the **7th of June 2024** any PRs that **do not** use `develop` as the base branch will be closed. + +When adding a new feature (that a user will interact with), create a new file in `src/workflows/` with the name of the feature (for example, `launch.rs`). Inside of `launch.rs` you would have a struct with the name of `LaunchWorkflow`. It would look something like this; +```rust +use crate::{RunnerContext, BeansError}; + +#[derive(Debug, Clone)] +pub struct LaunchWorkflow { + pub context: RunnerContext +} +impl LaunchWorkflow { + pub async fn wizard(ctx: &mut RunnerContext) -> Result<(), BeansError> + { + todo!("Logic for handling the LaunchWorkflow") + } +} +``` +You would also be adding a subcommand for this ins `main.rs`. In `Launcher::run()` you would add the following ***before*** `.args` is called on `cmd`, and after the last `.subcommand()` that is used. What you would add would look like the following; +```rust +.subcommand(Command::new("launch") + .about("Launch the currently installed game") + .arg(Launcher::create_location_arg())) +``` + +All sub-commands must have the `--location` argument added so the end-user can specify if they have a custom location for their `sourcemods` folder. + +Next you'd add a match case so `Launcher::subcommand_processor(&mut self)`, which would look like the following; +```rust +Some(("launch", install_matches)) => { + self.task_launch(install_matches).await; +} +``` + +Then, you'd add a new function to `Launcher`, which would actually call `LaunchWorkflow`. It would look something like the following (if there is only the `--location` argument); +```rust +pub async fn task_launch(&mut self, matches: &ArgMatches) { + self.to_location = Launcher::find_arg_sourcemods_location(&matches); // must be done when the `--launcher` argument is provided on the subcommand! + if let Err(e) = LaunchWorkflow::wizard(&mut ctx).await { + panic!("Failed to run LaunchWorkflow {:#?}", e); + } else { + logic_done(); // must be called when any flow of logic has completed. + } +} +``` + +**Do not make any PRs to remove the embedded executables in favor of downloading.** Some users would like to use this application offline, or they may have unreliable internet. diff --git a/README.md b/README.md index 2a55743..1074158 100644 --- a/README.md +++ b/README.md @@ -1,84 +1,29 @@ # beans-rs + A Sourcemod Installer written with Rust, using the Kachemak versioning system (based off TF2C). Intended for general-purpose use, and for server owners. This is a complete rewrite of the original [beans](https://github.com/int-72h/ofinstaller-beans) installer, but with rust, and extended support. `beans-rs` is licensed under `GPLv3-only`, so please respect it! -**Note** Releases for Linux v1.5.0 and later are built with Ubuntu 20.04 - ## Developing -Requirements -- Rust Toolchain (nightly, only for building) +### Requirements: +- **Read the [CONTRIBUTING](CONTRIBUTING.md) rules!** +- Rust Toolchain (Nightly required for building) - Recommended to use [rustup](https://rustup.rs/) to install. -- x86-64/AMD64 Processor ([see notes](#notes-binaries)) -- OpenSSL v3 (also required on deployments) -- fltk ([please read "FLTK Linux Dependencies"](#fltk-linux-dependencies)) - - (Optional) `fluid` for creating `.fl` files. -- **Following requirements are only required for testing** -- Steam Installed +- x86-64/AMD64 Processor ([See notes](#notes)) +- OpenSSL v3 (Required on deployments) +- fltk ([Please read "FLTK Linux Dependencies"](#fltk-linux-dependencies)) + - (Optional) `fluid` for creating `.fl` files. +- Steam Installed (Only required for testing) - Source SDK Base 2013 Multiplayer ([install](steam://instal/243750)) -## Contributing -When creating a PR, please please please branch off the `develop` branch. Any PRs that are created after the 7th of June 2024 that **do not** use `develop` as the base branch will be closed. - -When adding a new feature (that a user will interact with), create a new file in `src/workflows/` with the name of the feature (for example, `launch.rs`). Inside of `launch.rs` you would have a struct with the name of `LaunchWorkflow`. It would look something like this; -```rust -use crate::{RunnerContext, BeansError}; - -#[derive(Debug, Clone)] -pub struct LaunchWorkflow { - pub context: RunnerContext -} -impl LaunchWorkflow { - pub async fn wizard(ctx: &mut RunnerContext) -> Result<(), BeansError> - { - todo!("Logic for handling the LaunchWorkflow") - } -} -``` -You would also be adding a subcommand for this ins `main.rs`. In `Launcher::run()` you would add the following ***before*** `.args` is called on `cmd`, and after the last `.subcommand()` that is used. What you would add would look like the following; -```rust -.subcommand(Command::new("launch") - .about("Launch the currently installed game") - .arg(Launcher::create_location_arg())) -``` - -All sub-commands must have the `--location` argument added so the end-user can specify if they have a custom location for their `sourcemods` folder. - -Next you'd add a match case so `Launcher::subcommand_processor(&mut self)`, which would look like the following; -```rust -Some(("launch", install_matches)) => { - self.task_launch(install_matches).await; -} -``` - -Then, you'd add a new function to `Launcher`, which would actually call `LaunchWorkflow`. It would look something like the following (if there is only the `--location` argument); -```rust -pub async fn task_launch(&mut self, matches: &ArgMatches) { - self.to_location = Launcher::find_arg_sourcemods_location(&matches); // must be done when the `--launcher` argument is provided on the subcommand! - if let Err(e) = LaunchWorkflow::wizard(&mut ctx).await { - panic!("Failed to run LaunchWorkflow {:#?}", e); - } else { - logic_done(); // must be called when any flow of logic has completed. - } -} -``` - -## Notes -### Binaries -All the bundled/embedded binaries are for x86-64/AMD64 systems. We only support that architecture because that's what Open Fortress supports. - -Please do not make any PRs to remove the embedded executables in favor of downloading. Some users would like to use this application offline, or they may have unreliable internet. - -Linux Systems not using glibc have not been tested. - -### FLTK Linux Dependencies +## FLTK Linux Dependencies When building `beans-rs`, some dependencies are required to build it since we need the build dependencies for fltk. -If your distribution is not listed (or the instructions are broken), please look at [`README.unix.txt` in the fltk repo.](https://github.com/fltk/fltk/blob/master/README.Unix.txt). +If your distribution is not listed (or the instructions are broken), please look at [`README.unix.txt` in the fltk repo](https://github.com/fltk/fltk/blob/master/README.Unix.txt). -#### Debian-based +### Debian-based This includes and Linux Distribution that is based off Debian or Ubuntu. Like; - [Ubuntu](https://ubuntu.com/), - [Debian](https://www.debian.org/), @@ -110,9 +55,17 @@ sudo apt-get install -y \ libxinerama-dev; ``` -#### Fedora +### Fedora ```bash sudo yum groupinstall -y "Development Tools" sudo yum groupinstall -y "X Software Development" sudo yum groupinstall -y "C Development Tools and Libraries" -``` \ No newline at end of file +``` + +## Notes +All the bundled/embedded binaries are for x86-64/AMD64 systems. We only support that architecture because that's what Open Fortress supports. + +Linux Systems not using glibc have not been tested. + +Releases for v1.5.0 and later are built with Ubuntu 20.04. + From 7a3488b9c3f4eecdb93291f28e08565e88d5af28 Mon Sep 17 00:00:00 2001 From: kate Date: Sun, 9 Nov 2025 09:48:21 +0800 Subject: [PATCH 08/16] Update to Rust 2024 and nightly-2025-11-09 --- Cargo.toml | 2 +- rust-toolchain.toml | 2 +- src/lib.rs | 2 -- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index fe9cb29..9734e30 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "beans-rs" version = "1.7.3" -edition = "2021" +edition = "2024" authors = [ "Kate Ward ", "ToastXC " diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 17e71b5..e03e0a6 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,2 +1,2 @@ [toolchain] -channel = "nightly-2025-01-10" \ No newline at end of file +channel = "nightly-2025-11-09" \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 76e596a..d24a684 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,4 @@ #![feature(error_generic_member_access)] -// required for aria2::get_executable_location() -#![feature(let_chains)] // todo // https://rust-lang.github.io/rust-clippy/master/index.html#/result_large_err #![allow(clippy::result_large_err)] From ae7117117eccc9c2e71e694b80d77bbeb2440939 Mon Sep 17 00:00:00 2001 From: kate Date: Sun, 9 Nov 2025 09:49:06 +0800 Subject: [PATCH 09/16] cargo fmt --- src/aria2.rs | 8 ++++---- src/butler.rs | 9 +++++---- src/ctx.rs | 12 ++++++------ src/depends.rs | 8 ++++---- src/error.rs | 8 ++++++-- src/extract.rs | 8 +++++--- src/gui/dialog.rs | 6 +++--- src/gui/mod.rs | 4 ++-- src/helper/linux.rs | 8 +++++--- src/helper/mod.rs | 23 ++++++++++++++--------- src/helper/windows.rs | 12 ++++++------ src/main.rs | 26 +++++++++++++++----------- src/version.rs | 8 ++++---- src/wizard.rs | 24 ++++++++++++------------ src/workflows/clean.rs | 6 +++--- src/workflows/install.rs | 18 +++++++++++------- src/workflows/uninstall.rs | 8 ++++---- src/workflows/update.rs | 12 +++++++----- src/workflows/verify.rs | 8 ++++---- 19 files changed, 120 insertions(+), 96 deletions(-) diff --git a/src/aria2.rs b/src/aria2.rs index fb1694e..1284d03 100644 --- a/src/aria2.rs +++ b/src/aria2.rs @@ -4,10 +4,10 @@ use log::{debug, error, info}; -use crate::{depends, - helper, - BeansError, - DownloadFailureReason}; +use crate::{BeansError, + DownloadFailureReason, + depends, + helper}; pub fn can_use_aria2() -> bool { diff --git a/src/butler.rs b/src/butler.rs index 7c2bca6..f0ee41a 100644 --- a/src/butler.rs +++ b/src/butler.rs @@ -1,14 +1,15 @@ use std::{backtrace::Backtrace, + io::Read, process::ExitStatus}; use log::{debug, error, info}; -use crate::{depends, - helper, - BeansError, - DownloadFailureReason}; +use crate::{BeansError, + DownloadFailureReason, + depends, + helper}; pub fn verify( signature_url: String, diff --git a/src/ctx.rs b/src/ctx.rs index ddcd129..0740e10 100644 --- a/src/ctx.rs +++ b/src/ctx.rs @@ -6,17 +6,17 @@ use log::{debug, error, info}; -use crate::{appvar::AppVarData, +use crate::{BeansError, + appvar::AppVarData, depends, helper, - helper::{find_sourcemod_path, - parse_location, - InstallType}, + helper::{InstallType, + find_sourcemod_path, + parse_location}, version, version::{RemotePatch, RemoteVersion, - RemoteVersionResponse}, - BeansError}; + RemoteVersionResponse}}; #[derive(Debug, Clone)] pub struct RunnerContext diff --git a/src/depends.rs b/src/depends.rs index d04b540..19a4148 100644 --- a/src/depends.rs +++ b/src/depends.rs @@ -8,11 +8,11 @@ use log::{debug, #[cfg(target_os = "windows")] use crate::ARIA2C_BINARY; -use crate::{helper, - BeansError, - BUTLER_BINARY, +use crate::{BUTLER_BINARY, BUTLER_LIB_1, - BUTLER_LIB_2}; + BUTLER_LIB_2, + BeansError, + helper}; /// try and write aria2c and butler if it doesn't exist /// paths that are used will be fetched from binary_locations() diff --git a/src/error.rs b/src/error.rs index ed772c1..12f2d6e 100644 --- a/src/error.rs +++ b/src/error.rs @@ -94,7 +94,9 @@ pub enum BeansError backtrace: Backtrace }, - #[error("Unable to perform action since the mod isn't installed since {missing_file} couldn't be found")] + #[error( + "Unable to perform action since the mod isn't installed since {missing_file} couldn't be found" + )] TargetSourcemodNotInstalled { missing_file: String, @@ -133,7 +135,9 @@ pub enum BeansError version: Option }, - #[error("Could not find steam installation, which means we can't find the sourcemods folder. Please provide the sourcemods folder with the --location parameter.")] + #[error( + "Could not find steam installation, which means we can't find the sourcemods folder. Please provide the sourcemods folder with the --location parameter." + )] SteamNotFound, #[error("{msg}")] diff --git a/src/extract.rs b/src/extract.rs index cb8d5cd..cf09835 100644 --- a/src/extract.rs +++ b/src/extract.rs @@ -9,8 +9,8 @@ use log::{debug, warn}; use zstd::stream::read::Decoder as ZstdDecoder; -use crate::{helper::join_path, - BeansError}; +use crate::{BeansError, + helper::join_path}; fn unpack_tarball_getfile( tarball_location: String, @@ -141,7 +141,9 @@ pub fn unpack_tarball( && error_str.contains("io: Os {") && error_str.contains("code: 5") { - warn!("Failed to unpack file {filename} (Permission Denied, might be read-only)") + warn!( + "Failed to unpack file {filename} (Permission Denied, might be read-only)" + ) } else { diff --git a/src/gui/dialog.rs b/src/gui/dialog.rs index cb1cb72..689f755 100644 --- a/src/gui/dialog.rs +++ b/src/gui/dialog.rs @@ -4,12 +4,12 @@ use fltk::{image::PngImage, *}; use log::warn; -use crate::gui::{apply_app_scheme, +use crate::gui::{GUIAppStatus, + apply_app_scheme, icon, shared_ui::GenericDialog, wait_for_quit, - window_centre_screen, - GUIAppStatus}; + window_centre_screen}; pub struct DialogBuilder { diff --git a/src/gui/mod.rs b/src/gui/mod.rs index b64d8b8..6b383b4 100644 --- a/src/gui/mod.rs +++ b/src/gui/mod.rs @@ -2,8 +2,8 @@ use fltk::{app::Receiver, prelude::*, window::Window, *}; -use fltk_theme::{color_themes, - ColorTheme}; +use fltk_theme::{ColorTheme, + color_themes}; use log::debug; mod dialog; diff --git a/src/helper/linux.rs b/src/helper/linux.rs index 44926e5..4797eb5 100644 --- a/src/helper/linux.rs +++ b/src/helper/linux.rs @@ -3,8 +3,8 @@ use std::fs::read_to_string; use log::{debug, error}; -use crate::{helper::format_directory_path, - BeansError}; +use crate::{BeansError, + helper::format_directory_path}; /// all possible known directory where steam *might* be /// only is used on linux, since windows will use the registry. @@ -71,7 +71,9 @@ fn find_steam_reg_path() -> Result } None => { - debug!("[helper::find_steam_reg_path] simple_home_dir::home_dir().to_str() returned None!"); + debug!( + "[helper::find_steam_reg_path] simple_home_dir::home_dir().to_str() returned None!" + ); return Err(BeansError::SteamNotFound); } }, diff --git a/src/helper/mod.rs b/src/helper/mod.rs index 3224d3b..9fb00a2 100644 --- a/src/helper/mod.rs +++ b/src/helper/mod.rs @@ -20,20 +20,20 @@ use log::{debug, error, trace, warn}; -use rand::{distr::Alphanumeric, - Rng}; +use rand::{Rng, + distr::Alphanumeric}; use reqwest::header::USER_AGENT; #[cfg(target_os = "windows")] pub use windows::*; -use crate::{appvar::AppVarData, - BeansError, +use crate::{BeansError, DownloadFailureReason, GameinfoBackupCreateDirectoryFail, GameinfoBackupFailureReason, GameinfoBackupReadContentFail, GameinfoBackupWriteFail, - RunnerContext}; + RunnerContext, + appvar::AppVarData}; #[derive(Clone, Debug)] pub enum InstallType @@ -642,7 +642,9 @@ pub fn get_tmp_dir() -> String } else if is_steamdeck() { - trace!("[helper::get_tmp_dir] Detected that we are running on a steam deck. Using ~/.tmp/beans-rs"); + trace!( + "[helper::get_tmp_dir] Detected that we are running on a steam deck. Using ~/.tmp/beans-rs" + ); match simple_home_dir::home_dir() { Some(v) => match v.to_str() @@ -799,8 +801,7 @@ pub async fn beans_has_update() -> Result, BeansError> { trace!( "Failed to deserialize GithubReleaseItem\nerror: {:#?}\ncontent: {:#?}", - e, - response_text + e, response_text ); return Err(BeansError::SerdeJson { error: e, @@ -931,7 +932,11 @@ pub fn backup_gameinfo(ctx: &mut RunnerContext) -> Result<(), BeansError> { if let Err(e) = std::fs::remove_file(&output_location) { - warn!("[helper::backup_gameinfo] Failed to delete existing file, lets hope things don't break. {:} {}", e, output_location.clone()); + warn!( + "[helper::backup_gameinfo] Failed to delete existing file, lets hope things don't break. {:} {}", + e, + output_location.clone() + ); } } diff --git a/src/helper/windows.rs b/src/helper/windows.rs index 933e07d..38ccd23 100644 --- a/src/helper/windows.rs +++ b/src/helper/windows.rs @@ -5,13 +5,13 @@ use std::{backtrace::Backtrace, use bitflags::bitflags; use log::debug; use widestring::U16String; -use windows::{core::PCWSTR, - Win32::Storage::FileSystem::*}; -use winreg::{enums::HKEY_CURRENT_USER, - RegKey}; +use windows::{Win32::Storage::FileSystem::*, + core::PCWSTR}; +use winreg::{RegKey, + enums::HKEY_CURRENT_USER}; -use crate::{helper::format_directory_path, - BeansError}; +use crate::{BeansError, + helper::format_directory_path}; /// TODO use windows registry to get the SourceModInstallPath /// HKEY_CURRENT_USER\Software\Value\Steam diff --git a/src/main.rs b/src/main.rs index d05a315..b07b750 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,10 @@ use std::str::FromStr; -use beans_rs::{flags, +use beans_rs::{BeansError, + PANIC_MSG_CONTENT, + RunnerContext, + SourceModDirectoryParam, + flags, flags::LaunchFlag, gui::DialogIconKind, helper, @@ -10,21 +14,17 @@ use beans_rs::{flags, InstallWorkflow, UninstallWorkflow, UpdateWorkflow, - VerifyWorkflow}, - BeansError, - RunnerContext, - SourceModDirectoryParam, - PANIC_MSG_CONTENT}; + VerifyWorkflow}}; use clap::{Arg, ArgAction, ArgMatches, Command}; -use log::{debug, +use log::{LevelFilter, + debug, error, info, trace, - warn, - LevelFilter}; + warn}; pub const DEFAULT_LOG_LEVEL_RELEASE: LevelFilter = LevelFilter::Info; #[cfg(debug_assertions)] @@ -286,8 +286,12 @@ impl Launcher if let Err(e) = std::fs::create_dir(x) { debug!("{:#?}", e); - error!("[Launcher::find_arg_sourcemods_location] Failed to create directory {x:?} ({e:})"); - panic!("[Launcher::find_arg_sourcemods_location] Failed to create directory {x:?}\n\n{e:#?}") + error!( + "[Launcher::find_arg_sourcemods_location] Failed to create directory {x:?} ({e:})" + ); + panic!( + "[Launcher::find_arg_sourcemods_location] Failed to create directory {x:?}\n\n{e:#?}" + ) } } sml_dir_manual = Some(parse_location(x.to_string())); diff --git a/src/version.rs b/src/version.rs index c33be09..9aae6e3 100644 --- a/src/version.rs +++ b/src/version.rs @@ -7,11 +7,11 @@ use log::{debug, error, trace}; -use crate::{appvar::AppVarData, +use crate::{BeansError, + appvar::AppVarData, helper, - helper::{find_sourcemod_path, - InstallType}, - BeansError}; + helper::{InstallType, + find_sourcemod_path}}; /// get the current version installed via the .adastral file in the sourcemod /// mod folder. will parse the value of `version` as usize. diff --git a/src/wizard.rs b/src/wizard.rs index 39c343a..53e0814 100644 --- a/src/wizard.rs +++ b/src/wizard.rs @@ -6,22 +6,22 @@ use log::{debug, info, trace}; -use crate::{appvar::AppVarData, +use crate::{BeansError, + RunnerContext, + SourceModDirectoryParam, + appvar::AppVarData, depends, flags, flags::LaunchFlag, helper, - helper::{find_sourcemod_path, - parse_location, - InstallType}, + helper::{InstallType, + find_sourcemod_path, + parse_location}, workflows::{CleanWorkflow, InstallWorkflow, UninstallWorkflow, UpdateWorkflow, - VerifyWorkflow}, - BeansError, - RunnerContext, - SourceModDirectoryParam}; + VerifyWorkflow}}; #[derive(Debug, Clone)] pub struct WizardContext @@ -94,7 +94,9 @@ impl WizardContext { if crate::aria2::get_executable_location().is_none() { - info!("Could not find aria2c!\nFor faster downloads, install it with your package manager (usually called \"aria2\")"); + info!( + "Could not find aria2c!\nFor faster downloads, install it with your package manager (usually called \"aria2\")" + ); } } @@ -119,9 +121,7 @@ impl WizardContext { println!( "======== A new update for {} is available! (latest: v{}, current: v{}) ========", - av.mod_info.name_stylized, - rv, - cv + av.mod_info.name_stylized, rv, cv ); } } diff --git a/src/workflows/clean.rs b/src/workflows/clean.rs index dc1549d..1af7f78 100644 --- a/src/workflows/clean.rs +++ b/src/workflows/clean.rs @@ -2,9 +2,9 @@ use log::{debug, info, warn}; -use crate::{helper, - BeansError, - RunnerContext}; +use crate::{BeansError, + RunnerContext, + helper}; #[derive(Debug, Clone)] pub struct CleanWorkflow diff --git a/src/workflows/install.rs b/src/workflows/install.rs index 251aa40..8b25393 100644 --- a/src/workflows/install.rs +++ b/src/workflows/install.rs @@ -3,13 +3,13 @@ use log::{debug, info, warn}; -use crate::{appvar::AppVarData, +use crate::{BeansError, + DownloadFailureReason, + RunnerContext, + appvar::AppVarData, helper, version::{AdastralVersionFile, - RemoteVersion}, - BeansError, - DownloadFailureReason, - RunnerContext}; + RemoteVersion}}; #[derive(Debug, Clone)] pub struct InstallWorkflow @@ -23,7 +23,9 @@ impl InstallWorkflow let (latest_remote_id, latest_remote) = ctx.latest_remote_version(); if let Some(_cv) = ctx.current_version { - println!("[InstallWorkflow::wizard] re-installing! game files will not be touched until extraction"); + println!( + "[InstallWorkflow::wizard] re-installing! game files will not be touched until extraction" + ); } Self::install_with_remote_version(ctx, latest_remote_id, latest_remote).await @@ -163,7 +165,9 @@ impl InstallWorkflow if let Err(e) = std::fs::create_dir(&out_dir) { debug!("{:#?}", e); - error!("[InstallWorkflow::install_from] Failed to create output directory, {out_dir} ({e:})"); + error!( + "[InstallWorkflow::install_from] Failed to create output directory, {out_dir} ({e:})" + ); return Err(BeansError::DirectoryCreateFailure { location: out_dir.clone(), error: e, diff --git a/src/workflows/uninstall.rs b/src/workflows/uninstall.rs index 1bea7bc..3d4cb0d 100644 --- a/src/workflows/uninstall.rs +++ b/src/workflows/uninstall.rs @@ -2,10 +2,10 @@ use log::{error, info, trace}; -use crate::{appvar::AppVarData, - helper, - BeansError, - RunnerContext}; +use crate::{BeansError, + RunnerContext, + appvar::AppVarData, + helper}; #[derive(Debug, Clone)] pub struct UninstallWorkflow diff --git a/src/workflows/update.rs b/src/workflows/update.rs index 315ac97..d22be92 100644 --- a/src/workflows/update.rs +++ b/src/workflows/update.rs @@ -1,11 +1,11 @@ use log::{debug, info}; -use crate::{appvar::AppVarData, +use crate::{BeansError, + RunnerContext, + appvar::AppVarData, butler, - helper, - BeansError, - RunnerContext}; + helper}; pub struct UpdateWorkflow { @@ -37,7 +37,9 @@ impl UpdateWorkflow Some(v) => v, None => { - println!("[UpdateWorkflow::wizard] No patch is available for the version that is currently installed."); + println!( + "[UpdateWorkflow::wizard] No patch is available for the version that is currently installed." + ); return Ok(()); } }; diff --git a/src/workflows/verify.rs b/src/workflows/verify.rs index de777c8..f172b3a 100644 --- a/src/workflows/verify.rs +++ b/src/workflows/verify.rs @@ -1,12 +1,12 @@ use log::{debug, error}; -use crate::{appvar::AppVarData, +use crate::{BeansError, + RunnerContext, + appvar::AppVarData, butler, helper, - version::RemoteVersion, - BeansError, - RunnerContext}; + version::RemoteVersion}; pub struct VerifyWorkflow { From 404138629fc31cdf4222be060d8fd9572d06ea43 Mon Sep 17 00:00:00 2001 From: kate Date: Sun, 9 Nov 2025 10:29:13 +0800 Subject: [PATCH 10/16] Updated dependencies --- Cargo.toml | 48 ++++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9734e30..2910149 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,46 +13,46 @@ license = "GPL-3.0" [dependencies] async-recursion = "1.1.1" async-stream = "0.3.6" -const_format = "0.2.34" +const_format = "0.2.35" futures = "0.3.31" futures-util = "0.3.31" -indicatif = "0.17.11" -rand = "0.9.1" -serde = { version = "1.0.219", features = ["derive"] } -serde_json = "1.0.140" -sysinfo = "0.35.1" +indicatif = "0.18.2" +rand = "0.9.2" +serde = { version = "1.0.228", features = ["derive"] } +serde_json = "1.0.145" +sysinfo = "0.37.2" tar = "0.4.44" -tokio-util = { version= "0.7.14", features = ["io"] } +tokio-util = { version= "0.7.17", features = ["io"] } zstd = "0.13.3" -thiserror = "2.0.12" -include-flate = "0.3.0" -simple-home-dir = "0.5.0" -clap = { version = "4.5.32", features = ["cargo"] } -bitflags = "2.9.0" -log = "0.4.26" +thiserror = "2.0.17" +include-flate = "0.3.1" +simple-home-dir = "0.5.2" +clap = { version = "4.5.51", features = ["cargo"] } +bitflags = "2.10.0" +log = "0.4.28" lazy_static = "1.5.0" thread-id = "5.0.0" colored = "3.0.0" -sentry-log = "0.38.0" -chrono = "0.4.40" +sentry-log = "0.45.0" +chrono = "0.4.42" -fltk = { version = "1.5.4" } -fltk-theme = "0.7.5" +fltk = { version = "1.5.21" } +fltk-theme = "0.7.9" dark-light = "2.0.0" -image = { version = "0.25.5", features = ["png"] } +image = { version = "0.25.8", features = ["png"] } [build-dependencies] -fl2rust = "0.7.0" +fl2rust = "0.7.1" [target.'cfg(target_os = "windows")'.dependencies] winconsole = { version = "0.11.1", features = ["window"] } winreg = "0.55.0" -widestring = "1.1.0" -windows = { version = "0.60.0", features = ["Win32_System_IO", "Win32_Storage_FileSystem"] } +widestring = "1.2.1" +windows = { version = "0.62.2", features = ["Win32_System_IO", "Win32_Storage_FileSystem"] } dunce = "1.0.5" [dependencies.sentry] -version = "0.38.0" +version = "0.45.0" default-features = false features = [ "backtrace", @@ -67,14 +67,14 @@ features = [ [dependencies.tokio] -version = "1.44.0" +version = "1.48.0" features = [ "macros", "rt-multi-thread" ] [dependencies.reqwest] -version = "0.12.14" +version = "0.12.24" features = [ "multipart", "stream", From 35b9bd7d82d0c655e162d96eb8bfd5cb964ccdcd Mon Sep 17 00:00:00 2001 From: kate Date: Sun, 9 Nov 2025 10:29:34 +0800 Subject: [PATCH 11/16] Replaced static mut with lazy static in logger.rs --- src/butler.rs | 1 - src/flags.rs | 17 ++++++++--------- src/logger.rs | 14 +++++++++----- 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/butler.rs b/src/butler.rs index f0ee41a..ac9603a 100644 --- a/src/butler.rs +++ b/src/butler.rs @@ -1,5 +1,4 @@ use std::{backtrace::Backtrace, - io::Read, process::ExitStatus}; use log::{debug, diff --git a/src/flags.rs b/src/flags.rs index 63d94b0..662b88d 100644 --- a/src/flags.rs +++ b/src/flags.rs @@ -29,12 +29,11 @@ pub fn has_flag(flag: LaunchFlag) -> bool /// Add a flag to `LAUNCH_FLAGS` pub fn add_flag(flag: LaunchFlag) { + if let Ok(mut lf) = crate::logger::LOG_FORMAT.write() + { + *lf = crate::logger::LOG_FORMAT_DEFAULT.to_string(); + } unsafe { - if let LaunchFlag::DEBUG_MODE = flag - { - crate::logger::LOG_FORMAT = crate::logger::LOG_FORMAT_DEFAULT; - }; - let mut data = LaunchFlag::from_bits(LAUNCH_FLAGS).unwrap_or(LaunchFlag::empty()); data.insert(flag); LAUNCH_FLAGS = data.bits(); @@ -44,11 +43,11 @@ pub fn add_flag(flag: LaunchFlag) /// remove a flag from `LAUNCH_FLAGS` pub fn remove_flag(flag: LaunchFlag) { + if let Ok(mut lf) = crate::logger::LOG_FORMAT.write() + { + *lf = crate::logger::LOG_FORMAT_MINIMAL.to_string(); + } unsafe { - if flag == LaunchFlag::DEBUG_MODE - { - crate::logger::LOG_FORMAT = crate::logger::LOG_FORMAT_MINIMAL; - }; let mut data = LaunchFlag::from_bits(LAUNCH_FLAGS).unwrap_or(LaunchFlag::empty()); data.remove(flag); LAUNCH_FLAGS = data.bits(); diff --git a/src/logger.rs b/src/logger.rs index d8b411d..498203a 100644 --- a/src/logger.rs +++ b/src/logger.rs @@ -1,6 +1,7 @@ use std::{io, io::Write, - sync::Mutex, + sync::{Mutex, + RwLock}, time::Instant}; use lazy_static::lazy_static; @@ -13,6 +14,7 @@ lazy_static! { static ref LOGGER: CustomLogger = CustomLogger { inner: Mutex::new(None) }; + pub static ref LOG_FORMAT: RwLock = RwLock::new(LOG_FORMAT_DEFAULT.to_string()); } struct CustomLogger @@ -110,9 +112,11 @@ impl CustomLoggerInner let milliseconds = now.subsec_millis(); #[allow(unused_assignments)] - let mut data = String::new(); - unsafe { - data = LOG_FORMAT.to_string(); + #[allow(static_mut_refs)] + let mut data: String = LOG_FORMAT_DEFAULT.to_string(); + if let Ok(datax) = LOG_FORMAT.read() + { + data = datax.clone(); } data = data .replace("#HOURS", &format!("{:02}", hours)) @@ -150,7 +154,7 @@ pub fn set_filter(filter: LevelFilter) } } static mut LOG_FILTER: LevelFilter = LevelFilter::Trace; -pub static mut LOG_FORMAT: &str = LOG_FORMAT_DEFAULT; + pub static mut LOG_COLOR: bool = true; pub const LOG_FORMAT_DEFAULT: &str = "[#HOURS:#MINUTES:#SECONDS.#MILLISECONDS] (#THREAD) #LEVEL #CONTENT"; From 4b7e0aeac7d38e48f335d391f77f4e3b1d2c89ab Mon Sep 17 00:00:00 2001 From: dani Date: Mon, 10 Nov 2025 01:18:35 -0500 Subject: [PATCH 12/16] docs: sentry --- docs/sentry.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 docs/sentry.md diff --git a/docs/sentry.md b/docs/sentry.md new file mode 100644 index 0000000..12466dd --- /dev/null +++ b/docs/sentry.md @@ -0,0 +1,18 @@ +# Sentry + +[Sentry is an error tracking and debugging platform](https://github.com/getsentry/sentry) that can be [self-hosted](https://develop.sentry.dev/self-hosted/) or [rented](https://sentry.io/pricing/). + +By default `bean-rs` will send crash dumps to a Sentry instance hosted by `beans-rs` developers, however you can modify `SENTRY_URL` in `src/lib.rs` to send crash dumps to a different Sentry URL address. + +By leaving `SENTRY_URL` to it's default value, the crash dumps will be used to help identify critical bugs in `beans-rs`. A small portion of the crash dump is data collected to identify your system and OS version, but most of the data related is to the application itself and it's dependencies. + +### Data Collected: + +- Machine Hostname +- OS Type and Kernel version +- System Architecture (e.g: x86_64) +- Everything in the log file (which may contain: home directory, network requests, debug information about fltk, etc..) +- What version of the Sentry SDK is being used +- What version of beans-rs you're using +- What version of the rust runtime you're using +- Full stack trace (of rust code) of the error reported \ No newline at end of file From d02ddd05c322a49561db6a11b931ef79e0d0b276 Mon Sep 17 00:00:00 2001 From: dani Date: Mon, 10 Nov 2025 20:32:31 -0500 Subject: [PATCH 13/16] docs: code signing --- docs/code-sign.md | 68 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 docs/code-sign.md diff --git a/docs/code-sign.md b/docs/code-sign.md new file mode 100644 index 0000000..ee61770 --- /dev/null +++ b/docs/code-sign.md @@ -0,0 +1,68 @@ +# Code Signing + +When developing software for Windows, it is important to code sign the application to make sure Windows Defender does not treat it as a virus or Unauthorized Application. This Code Signing Certificate acts as an identifier that Windows will use to build a positive reputation for your app. + +## Sign Tool GitHub Action + +This repo uses [`skymatic/code-sign-action@v1`](https://github.com/marketplace/actions/windows-signtool-exe-code-sign-action) to sign the latest builds of `beans-rs`. The most important components of this action are `certificate`, `certificatesha1` and `password`. + +## Generating a Self Signed Certificate on Windows + +There is [extensive Microsoft documentation about code signing certificates](https://learn.microsoft.com/en-us/entra/identity-platform/howto-create-self-signed-certificate), however `beans-rs` relies on a script inspired by [this guide](https://archi-lab.net/creating-a-self-signed-code-signing-certificate/). + +Before starting the generating certificate process, ensure you have 3 passwords: +- Certificate Authority Password +- Code Signing Certificate Password +- PFX Private Key Password + +Place this code in a file called `gen_cert.ps1` and run it in [Developer Powershell for VS 2022](https://learn.microsoft.com/en-us/visualstudio/ide/reference/command-prompt-powershell?view=vs-2022#start-from-windows-menu). + +```ps1 +# Set Certificate Information +# Replace each entry, including removing brackets. +$FullTeamName = "[replace with full team name]" +$ShortHandTeamName = "[short hand team name with spaces removed]" +$FullAppName = "[full app name]" +$AppName = "[app name with spaces removed]" +# NOTE: This process requires 3 separate passwords, Certificate Authority, Code Signing Certificate and PFX Private Key. +# This password should be different than your Certificate Authority and Code Signing Certificate. +$PFXPassword = "[pfx password]" + +# Create Certificate Authority +echo "This step uses the first password for the Certificate Authority." +makecert -r -pe -n "CN=$FullTeamName Certificate Authority" -ss CA -sr CurrentUser -a sha256 -cy authority -sky signature -sv $ShortHandTeamName-ca.pvk $ShortHandTeamName-ca.cer +echo "Pleas press Yes to install the certificate." +certutil -user -addstore Root $ShortHandTeamName-ca.cer + +# Create Code Signing Certificate +echo "The first two popups are for your second password for the Code Signing Certificate." +echo "The third popup is for your Certificate Authority password from the previous step." +makecert -pe -n "CN=$FullAppName by $FullTeamName" -a sha256 -cy end -sky signature -ic $ShortHandTeamName-ca.cer -iv $ShortHandTeamName-ca.pvk -sv $ShortHandTeamName-$AppName.pvk $ShortHandTeamName-$AppName.cer +echo "Enter your Code Signing Certificate password from the previous step." +pvk2pfx -pvk $ShortHandTeamName-$AppName.pvk -spc $ShortHandTeamName-$AppName.cer -pfx $ShortHandTeamName-$AppName.pfx -po "$PFXPassword" +echo "Certificate Successfully Created" + +# Output Certificate Information +echo "Generating Base64 Hash" +$fileContentBytes = get-content $ShortHandTeamName-$AppName.pfx -Encoding Byte +[System.Convert]::ToBase64String($fileContentBytes) > CODESIGN.txt +echo "Locating Thumbprint" +$StrPassword = ConvertTo-SecureString -String $PFXPassword -Force -AsPlainText +New-Object System.Security.Cryptography.X509Certificates.X509Certificate2("$ShortHandTeamName-$AppName.pfx", "$PFXPassword") > CODESIGN_HASH.txt +echo "Take the data from the CODESIGN.txt add it the repo GitHub Secrets as a secret called CODESIGN." +echo "The long string of numbers under the word Thumbprint is the SHA1 used to identify the certificate. Copy only the string into a secret called CODESIGN_HASH." +echo "Finally take the password entered into PFXPassword and add it as a secret called CODESIGN_PASSWORD." +``` +***Update the certificate information before running.*** + +## GitHub Secrets + +When creating a fork, visit the `secrets/actions` settings page and add the following entries: + +- `CODESIGN`: This is your `certificate` entry in the form of a Base 64 encoded hash from the `.pfx` certificate file. You can recover this from the `CODESIGN.txt` file created when generating the certificate. +- `CODESIGN_PASSWORD`: This is the strong `password` used for protecting the PFX File on Windows. This should be the same password used in `$PFXPassword`. +- `CODESIGN_HASH`: This is the SHA1 "Thumbprint" hash used to identify your `certificatesha1`. You can recover this from the `CODESIGN_HASH.txt` file. You can also find it by [opening Microsoft Management Console and using MMC snap-in to view the certificate](https://learn.microsoft.com/en-us/dotnet/framework/wcf/feature-details/how-to-retrieve-the-thumbprint-of-a-certificate). + +## Microsoft Security Intelligence Malware Analysis Submission + +After the program has been fully signed submit the application for a [Malware Analysis Submission](https://www.microsoft.com/en-us/wdsi/filesubmission) to certify the authenticity of the software. \ No newline at end of file From e4270d966d033ca07bfe23e8a49543de79f288fe Mon Sep 17 00:00:00 2001 From: dani Date: Thu, 13 Nov 2025 20:31:39 -0500 Subject: [PATCH 14/16] feat: mod specific butler staging foler --- src/ctx.rs | 4 ++-- src/lib.rs | 14 ++++++++++---- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/ctx.rs b/src/ctx.rs index 0740e10..3398488 100644 --- a/src/ctx.rs +++ b/src/ctx.rs @@ -97,13 +97,13 @@ impl RunnerContext } /// Get staging location for butler. - /// {sourcemod_dir}{crate::STAGING_DIR} + /// {sourcemod_dir}{crate::staging_dir()} /// e.g; /home/kate/.var/app/com.valvesoftware.Steam/.local/share/Steam/ /// steamapps/sourcemods/butler-staging C:\Games\Steam\steamapps\ /// sourcemods\butler-staging pub fn get_staging_location(&mut self) -> String { - helper::join_path(self.sourcemod_path.clone(), crate::STAGING_DIR.to_string()) + helper::join_path(self.sourcemod_path.clone(), crate::staging_dir()) } /// Get the latest item in `remote_version_list` diff --git a/src/lib.rs b/src/lib.rs index d24a684..1d78ce9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -222,10 +222,16 @@ pub fn get_user_agent() -> String result } -#[cfg(not(target_os = "windows"))] -pub const STAGING_DIR: &str = "/butler-staging"; -#[cfg(target_os = "windows")] -pub const STAGING_DIR: &str = "\\butler-staging"; +pub fn staging_dir() -> String +{ + let av = AppVarData::get(); + #[cfg(not(target_os = "windows"))] { + format!("/butler-staging-{}", av.mod_info.short_name) + } + #[cfg(target_os = "windows")] { + format!("\\butler-staging-{}", av.mod_info.short_name) + } +} #[cfg(target_os = "windows")] flate!(pub static BUTLER_BINARY: [u8] from "Binaries/butler.exe"); From c286bbe06d6143cdbe7890d432a26b1b31a4520f Mon Sep 17 00:00:00 2001 From: dani Date: Thu, 13 Nov 2025 20:35:14 -0500 Subject: [PATCH 15/16] feat: clean up command also removes butler-staging --- src/workflows/clean.rs | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/workflows/clean.rs b/src/workflows/clean.rs index 1af7f78..2878a95 100644 --- a/src/workflows/clean.rs +++ b/src/workflows/clean.rs @@ -14,9 +14,10 @@ pub struct CleanWorkflow impl CleanWorkflow { - pub fn wizard(_ctx: &mut RunnerContext) -> Result<(), BeansError> + pub fn wizard(ctx: &mut RunnerContext) -> Result<(), BeansError> { let target_directory = helper::get_tmp_dir(); + let staging_dir_location = ctx.get_staging_location(); info!("[CleanWorkflow] Cleaning up {}", target_directory); if !helper::file_exists(target_directory.clone()) @@ -46,6 +47,24 @@ impl CleanWorkflow }); } + // clean up butler files if it was interrupted in previous install + if !helper::file_exists(staging_dir_location.clone()) + { + debug!("[CleanWorkflow] Staging directory used by butler not found, nothing to clean.") + } else { + // delete temp butler directory and it's contents (and error handling) + info!("[CleanWorkflow] Cleaning up {}", staging_dir_location); + if let Err(e) = std::fs::remove_dir_all(&staging_dir_location) + { + debug!("[CleanWorkflow::wizard] remove_dir_all {:#?}", e); + return Err(BeansError::CleanTempFailure { + location: staging_dir_location, + error: e, + backtrace: std::backtrace::Backtrace::capture() + }); + } + } + info!("[CleanWorkflow] Done!"); Ok(()) } From 31e27dc162db31114371d124c45415a62306f11c Mon Sep 17 00:00:00 2001 From: Sour Dani Date: Fri, 28 Nov 2025 01:48:24 -0500 Subject: [PATCH 16/16] docs: cargo fmt --- CONTRIBUTING.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 51aeed4..01eedf1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,6 +2,8 @@ **When creating a PR, you must branch off the `develop` branch.** When merging back into this repo remember to select the `develop` as the branch to merge into. After the **7th of June 2024** any PRs that **do not** use `develop` as the base branch will be closed. +**Before submitting a PR** make sure to format your code using the `cargo fmt` command. Code that is not properly formatted will need to do so before being merged. + When adding a new feature (that a user will interact with), create a new file in `src/workflows/` with the name of the feature (for example, `launch.rs`). Inside of `launch.rs` you would have a struct with the name of `LaunchWorkflow`. It would look something like this; ```rust use crate::{RunnerContext, BeansError};