Building from source - BEEmod/BEE2-items GitHub Wiki

Build BEE2 from Source (Linux & Windows)

You can build BEE2 from source on both Windows and Linux.
Below is a detailed step-by-step guide.

Quick Navigation


1. Install Python

Linux:

  • Install via your package manager: sudo pacman -S python python-pip git (or equivalent)

  • Also install via your package manager: sudo pacman -S spatialindex (or equivalent, may also be called libspatialindex)

  • Make virtual environment (needed on linux):

    python -m venv ./venv
    source ./venv/bin/activate

Windows:

  • Download Python or use winget install Python.Python.3.13

  • Download Git or use winget install Git.Git

  • If you like to use a venv, you can use (in cmd):

    python -m venv ./venv
    venv/Scripts/activate

2. Download the Repository

  • Clone the repo:

    git clone --recurse-submodules https://github.com/BEEmod/BEE2.4.git
    cd BEE2.4
  • To use the dev branch:

    git clone -b dev --recurse-submodules https://github.com/BEEmod/BEE2.4.git

3. Install Python Dependencies

pip install -r requirements.txt

If you're using the dev branch, also run:

pip install -r dev-requirements.txt

4. Enable Sound Effects (FFmpeg)

On Linux, sound effects may still not work, to be save copy them anyway.


5. Update Git Submodules

From inside the BEE2.4/ directory run:

git submodule update --init

6. Compile with PyInstaller

From inside the BEE2.4/ directory run:

cd src
pyinstaller --distpath ../dist/64bit/ --workpath ../build_tmp compiler.spec
pyinstaller --distpath ../dist/64bit/ --workpath ../build_tmp BEE2.spec

7. Add Items to BEE2

You can add item packages to BEE2 using stable precompiled zips or by generating dev packages manually.

Option A: Use Prebuilt Stable Packages (Recommended for most users)

  1. Download the latest:

  2. Extract the contents into:

    BEE2.4/dist/64bit/BEE2/packages/
    

You may need to manually create the packages/ folder if it doesn’t exist.

Option B (Click to see): Use Dev Packages (compile from source, mostly needed for dev version of BEE)

This method is useful if you're working with or testing the latest unreleased item changes / using the dev version of BEE.

  1. Clone or download the dev branches:

    git clone -b dev https://github.com/BEEmod/BEE2-items.git dev-items
    git clone -b dev https://github.com/BEEmod/BEE2-music.git music-dev-items

    Alternatively, you can click the green "Code" β†’ "Download ZIP" button on each repo, unzip, and rename to dev-items and music-dev-items.

  2. Install requirements:

    cd dev-items
    pip install -r requirements.txt
  3. Compile the packages:

    mkdir -p zips/items
    python ./compile_packages.py imput . -c --zip --overwrite -o zips/items
  4. Compile the music packages:

    mkdir -p zips/music
    python ./compile_packages.py imput ../music-dev-items/. -c --zip --overwrite -o zips/music
  5. Link or move the compiled .bee_pack files from zips/items/ and zips/music/ to:

    BEE2.4/dist/64bit/BEE2/packages/
    

You can also use symbolic links to avoid duplicating data.


8. Fix Puzzle Maker on Linux

The Portal 2 Puzzle Maker is broken on Linux by default (still true in 2025).
You can fix it by copying a working filesystem_stdio.so from an old depot:

steam -console
# In Steam console:
download_depot 620 661 2854055004190207766

Then run:

cp "$HOME/.steam/steam/ubuntu12_32/steamapps/content/app_620/depot_661/bin/linux32/filesystem_stdio.so" \
   "$HOME/.steam/steam/steamapps/common/Portal 2/bin/linux32/filesystem_stdio.so"

More details: ValveSoftware/portal2#403


9. Run BEE2

Navigate to:

BEE2.4/dist/64bit/BEE2/

Then run:

./BEE2       # On Linux
BEE2.exe     # On Windows

10. Add a Desktop Shortcut

Windows:

  • Right-click BEE2.exe, then choose "Create shortcut".

Linux: Create a file named BEE2.desktop with:

[Desktop Entry]
Exec=%yourbeeroot%/dist/64bit/BEE2/BEE2
GenericName=BEE2
Icon=%yourbeeroot%/icon/BEE2.ico
Name=BEE2
Path=%yourbeeroot%/dist/64bit/BEE2
StartupNotify=true
StartupWMClass=BEE2
Terminal=false
Type=Application

Replace %yourbeeroot% with the absolute path to your BEE2.4 directory.


11. Fully Automate Building on Linux

If you're building on Linux, this script can automate nearly everything from cloning the repo to packaging, FFmpeg handling, and shortcuts.

One-Time Setup

Add the Script

Create a file named bee-builder.sh in your build root, and paste the following into it:

=== Click to expand the full script ===
#!/bin/bash

# Set default python environment path
DEFAULT_PYTHON_ENV="./venv"
pythonenv="${pythonenv:-$DEFAULT_PYTHON_ENV}"
customver="${customver:-}"
scriptdir="$(dirname "$(realpath "$0")")"

main() {
    show_help
    parse_input "$@"

    [[ "$goexit" == true ]] && echo "Exit selected, exiting." && exit
    post_cli_seperator

    setup_python_env
    prepare_build_dir
    clone_repo
    fix_portal_bug
    download_icon
    download_ffmpeg
    update_pip
    install_requirements
    update_submodules
    clean_old_builds
    run_pyinstaller
    download_packages
    download_dev_packages
    download_dev_music_packages
    build_packages
    copy_pac_and_ffmpeg
    create_shortcut_if_needed
    create_link_if_needed
    run_bee_if_needed
}

show_help() {
    cat <<EOF
Usage: $0 [options]
Options or User input choices (comma-separated, e.g., '0,1,2,3'):
  0  - Use only CLI args (no interactive input)
  1  - Delete old build directory (have a fresh directory for building; its faster to leave it as is)
  2  - Run BEE2 after build
  3  - Don't Create a shortcut next to this script
  4  - Disable pip update
  5  - Build from main branch instead of dev
  6  - Skip downloading pip requirements
  7  - Skip downloading pip requirements for building packages
  8  - Don't get the icon (Only downloded when icon is missing)
  9  - Skip Portal 2 fix for Linux (Still needed in 2025, skipping not recomended)
 10  - Use current FFmpeg (by default it will auto update when needed)
 11  - Use current package items (by default they will auto update when needed)
 12  - Don't create symlink to main BEE2 directory
 13  - Skip building packages (for testing only)
 14  - Skip building (for testing only)
 15  - Exit
EOF
}

parse_input() {
    if [[ "$1" != *",0" && "$1" != "0,"* && "$1" != *",0,"* && "$1" != "0" ]]; then
        read -p "Enter your choices: " choice
    fi
    local combined="${1},${choice}"
    IFS=',' read -ra user_choices <<< "$combined"
    set_defaults
    for choice in "${user_choices[@]}"; do
        case "$choice" in
            0) ;;
            1) fresh=true ;;
            2) run=true ;;
            3) shortcut=false ;;
            4) pipupdate=false ;;
            5) dev=false ;;
            6) requirements=false ;;
            7) pac_requirements=false ;;
            8) geticon=false ;;
            9) portallinuxbug=false ;;
           10) newffmpeg=false ;;
           11) newpackages=false ;;
           12) distlink=false ;;
           13) pac_build=false ;;
           14) build=false ;;
           15) goexit=true ;;
            *) [[ -n "$choice" ]] && echo "Invalid choice: $choice" ;;
        esac
    done
}

set_defaults() {
    goexit=false
    fresh=false
    run=false
    shortcut=true
    pipupdate=true
    dev=true
    distlink=true
    requirements=true
    pac_requirements=false
    geticon=true
    portallinuxbug=true
    newffmpeg=true
    newpackages=true
    build=true
    pac_build=true
}

post_cli_seperator() {
    echo "---"
    echo ""
}

setup_python_env() {
    cd "$scriptdir" || exit
    echo "Checking for python environment..."
    if [[ "$pythonenv" == false ]]; then
        echo "Python environment is disabled."
        post_cli_seperator
        return
    fi

    if [[ ! -f "$pythonenv" ]]; then
        echo "Python environment not found, creating one..."
        python3 -m venv "$pythonenv"
    fi

    echo "Activating environment: $pythonenv"
    source "$pythonenv/bin/activate"
    post_cli_seperator
}

prepare_build_dir() {
    echo "Peparing build directory..."
    cd "$scriptdir" || exit
    mainbuildir="BEE2.4"
    [[ "$dev" == false ]] && mainbuildir="MainBEE2.4"
    [[ -n "$customver" ]] && mainbuildir="$customver$mainbuildir"

    if [[ "$fresh" == true ]]; then
        echo "Removing old directory: $mainbuildir"
        rm -rf "$mainbuildir"
    fi
    post_cli_seperator
}

clone_repo() {
    echo "Getting Repo..."
    cd "$scriptdir" || exit
    local branch="dev"
    [[ "$dev" == false ]] && branch=""
    local repo_url="https://github.com/BEEmod/BEE2.4.git"

    if [[ -d "$mainbuildir/.git" ]]; then
        echo "Updating existing repo in $mainbuildir"
        cd "$mainbuildir" || exit
        if [[ -n "$branch" ]]; then
            git fetch origin "$branch"
            git reset --hard "origin/$branch"
        else
            git fetch origin
            git reset --hard "origin"
        fi
        [[ -n "$customver" ]] && git checkout "$customver"
        git pull
    else
        echo "Cloning BEE2.4 repository"
        if [[ -n "$branch" ]]; then
            git clone -b "$branch" --recurse-submodules "$repo_url" temp
        else
            git clone --recurse-submodules "$repo_url" temp
        fi

        if [[ -n "$customver" ]]; then
            echo "Checking out custom version: $customver"
            cd temp || exit
            git checkout "$customver"
            cd "$scriptdir"
        fi

        echo "Copying repo to $mainbuildir"
        mkdir -p "$mainbuildir"
        cp -rfa temp/. "$mainbuildir"
        rm -rf temp
    fi
    post_cli_seperator
}

fix_portal_bug() {
    [[ "$portallinuxbug" != true ]] && return
    cd "$scriptdir" || exit
    echo "Applying Portal 2 Linux fix..."
    fixf="$HOME/.steam/steam/ubuntu12_32/steamapps/content/app_620/depot_661/bin/linux32/filesystem_stdio.so"
    targetf="$HOME/.steam/steam/steamapps/common/Portal 2/bin/linux32/filesystem_stdio.so"
    if cp "$fixf" "$targetf"; then
        echo "File was copyed successfully."
    else
        echo "File could not be copyed."
        echo "Please run the following:"
        echo "- 'steam -console' or 'steam-native -console' in the teminal."
        echo "After that run in the steam console:"
        echo "download_depot 620 661 2854055004190207766"
        echo "After that the fix should work fine"
    fi

    post_cli_seperator
}

download_icon() {
    [[ "$geticon" != true ]] && return
    cd "$scriptdir" || exit
    echo "Getting Icon..."
    if [[ -f "icon/BEE2.ico" ]]; then
        echo "Icon already exists, skipping download."
        post_cli_seperator
        return
    fi
    echo "Downloading icon..."
    mkdir -p icon
    curl -L -o "icon/BEE2.ico" "https://raw.githubusercontent.com/BEEmod/BEE2.4/master/BEE2.ico"
    post_cli_seperator
}

download_ffmpeg() {
    [[ "$newffmpeg" != true ]] && return
    cd "$scriptdir" || exit
    echo "Checking FFmpeg version..."
    mkdir -p bin "$mainbuildir/lib-64"

    local url release_id id_file="bin/ffmpeg_release_id.txt"

    release_json=$(curl -s 'https://api.github.com/repos/BtbN/FFmpeg-Builds/releases/latest')
    release_id=$(echo "$release_json" | jq -r '.id')

    if [[ -f "$id_file" && "$(cat "$id_file")" == "$release_id" ]]; then
        echo "Latest FFmpeg already downloaded (release ID: $release_id)"
        post_cli_seperator
        return
    fi

    url=$(echo "$release_json" | jq -r '.assets[] | select(.name | endswith("linux64-lgpl-shared.tar.xz")) | .browser_download_url' | head -n 1)

    echo "Downloading FFmpeg (release ID: $release_id)..."
    curl -L -o bin/ffmpeg.tar.xz "$url"

    # Extract only the bin/* contents directly into bin/
    tar --wildcards --strip-components=2 -xvf bin/ffmpeg.tar.xz -C bin "ffmpeg-*/bin/*"
    rm bin/ffmpeg.tar.xz

    echo "$release_id" > "$id_file"
    post_cli_seperator
}

update_pip() {
    [[ "$pipupdate" != true ]] && return
    cd "$scriptdir" || exit
    echo "Updating pip..."
    pip install --upgrade pip
    post_cli_seperator
}

install_requirements() {
    [[ "$requirements" != true ]] && return
    cd "$scriptdir/$mainbuildir" || exit
    echo "Grabbing requirements..."
    pip install -r requirements.txt
    [[ "$dev" == true ]] && pip install -r dev-requirements.txt
    post_cli_seperator
}

update_submodules() {
    cd "$scriptdir" || exit
    echo "Updating submodules..."
    cd "$mainbuildir" || exit
    git submodule update --init
    cd "$scriptdir"
    post_cli_seperator
}

clean_old_builds() {
    [[ "$build" != true ]] && return
    cd "$scriptdir/$mainbuildir" || exit

    echo "Deleting old build files"
    [[ -d "./dist/64bit/compiler" ]] && rm -rf ./dist/64bit/compiler
    [[ -d "./dist/64bit/BEE2" ]] && rm -rf ./dist/64bit/BEE2
    post_cli_seperator
}

run_pyinstaller() {
    cd "$scriptdir/$mainbuildir/src" || exit
    echo "Checking for building requirements..."
    if [[ "$build" != true ]] && [[ ! -f "$scriptdir/$mainbuildir/dist/64bit/BEE2/BEE2" ]]; then
        echo "Building can't be skipped the first time. Forcing build..."
        build=true
    fi
    if [[ "$build" == true ]]; then
        pyinstaller --distpath ../dist/64bit/ --workpath ../build_tmp compiler.spec || {
            echo "Compiler build failed. Ensure 'tk' is installed."
            exit 1
        }
        pyinstaller --distpath ../dist/64bit/ --workpath ../build_tmp BEE2.spec || {
            echo "BEE2 build failed."
            [[ ! -f "$scriptdir/$mainbuildir/dist/64bit/BEE2/BEE2" ]] && echo "Can't find BEE. Closing..." && exit 1
            echo "BEE found. Continuing anyway..."
        }
    else
        echo "Building skipped (for testing). Continuing..."
    fi

    post_cli_seperator
}

download_packages() {
    [[ $newpackages != true ]] && return
    [[ "$dev" == true ]] && return
    cd "$scriptdir" || exit
    echo "Grabbing new packages..."

    version_file="packages/items_version.txt"
    mkdir -p bin

    api_json=$(curl -s 'https://api.github.com/repos/BEEmod/BEE2-items/releases/latest')
    release_id=$(echo "$api_json" | jq -r '.id')

    # Check if we already have this version
    if [[ -f "$version_file" && "$(cat "$version_file")" == "$release_id" ]]; then
        echo "Latest packages already downloaded (release id: $release_id)"
    else
        echo "New release detected (release id: $release_id)"
        ZIP_URLS=$(echo "$api_json" | jq -r '.assets[] | select(.name | endswith(".zip")) | .browser_download_url')

        for url in $ZIP_URLS; do
            filename=$(echo "$url" | awk -F'/' '{split($NF,a,"_"); printf "%s", a[3]; for(i=4;i<=length(a);i++) printf "_%s", a[i];}')
            foldername=${filename%.*}
            mkdir -p "$foldername"
            curl -L -o "$foldername/$filename" "$url"
            unzip -o "$foldername/$filename" -d "$foldername"
            rm "$foldername/$filename"
        done

        echo "$release_id" > "$version_file"
        echo "Packages downloaded"
    fi
    post_cli_seperator
}

download_dev_packages() {
    [[ $newpackages != true ]] && return
    [[ "$dev" != true ]] && return
    cd "$scriptdir" || exit
    echo "Grabbing new dev packages..."

    local repo_url="https://github.com/BEEmod/BEE2-items.git"
    if [[ -d "dev-items/.git" ]]; then
        echo "Updating existing repo in dev-items"
        cd "$scriptdir/dev-items" || exit
        git fetch origin dev
        git reset --hard "origin/dev"
        [[ -n "$customver" ]] && git checkout "$customver"
        git pull
    else
        echo "Cloning BEE2.4 repository"
        git clone -b "dev" --recurse-submodules "$repo_url" temp

        if [[ -n "$customver" ]]; then
            echo "Checking out custom version: $customver"
            cd "$scriptdir/temp" || exit
            git checkout "$customver"
            cd "$scriptdir"
        fi

        echo "Copying repo to dev-items"
        mkdir -p "dev-items"
        cp -rfa temp/. "dev-items"
        rm -rf temp
    fi
    post_cli_seperator
}

download_dev_music_packages() {
    [[ $newpackages != true ]] && return
    [[ "$dev" != true ]] && return
    cd "$scriptdir" || exit
    echo "Grabbing new music dev packages..."

    local music_repo_url="https://github.com/BEEmod/BEE2-music.git"
    if [[ -d "music-dev-items/.git" ]]; then
        echo "Updating existing repo in dev-items"
        cd "$scriptdir/music-dev-items" || exit
        git fetch origin dev
        git reset --hard "origin/dev"
        [[ -n "$customver" ]] && git checkout "$customver"
        git pull
        cd "$scriptdir"
    else
        echo "Cloning BEE2.4 repository"
        git clone -b "dev" --recurse-submodules "$music_repo_url" temp

        if [[ -n "$customver" ]]; then
            echo "Checking out custom version: $customver"
            cd "$scriptdir/temp" || exit
            git checkout "$customver"
            cd "$scriptdir"
        fi

        echo "Copying repo to dev-items"
        mkdir -p "music-dev-items"
        cp -rfa temp/. "music-dev-items"
        rm -rf temp
    fi
    post_cli_seperator
}

build_packages() {
    [[ "$dev" != true ]] && return
    [[ "$pac_build" != true ]] && return
    cd "$scriptdir/dev-items" || exit
    echo "Building item packages..."
    [[ "$pac_requirements" == true ]] && pip install -r requirements.txt
    mkdir -p "zips/items"
    python ./compile_packages.py imput . -c --zip --overwrite -o "zips/items"
    mkdir -p "zips/music"
    python ./compile_packages.py imput ../music-dev-items/. -c --zip --overwrite -o "zips/music"
    post_cli_seperator
}

copy_pac_and_ffmpeg() {
    cd "$scriptdir" || exit

    mkdir -p "$mainbuildir/dist/64bit/BEE2/lib-64/"
    for f in bin/*; do
        ln -sf "$scriptdir/$f" "$mainbuildir/dist/64bit/BEE2/lib-64/"
    done

    if [[ "$dev" != true ]]; then
        mkdir -p "$mainbuildir/dist/64bit/BEE2/packages/"
        for f in packages/*; do
            ln -sf "$scriptdir/$f" "$mainbuildir/dist/64bit/BEE2/packages/"
        done
        for f in music_packages/*; do
            ln -sf "$scriptdir/$f" "$mainbuildir/dist/64bit/BEE2/packages/"
        done
    fi

    if [[ "$dev" == true ]]; then
        mkdir -p "$mainbuildir/dist/64bit/BEE2/packages/"
        for f in dev-items/zips/items/*; do
            ln -sf "$scriptdir/$f" "$mainbuildir/dist/64bit/BEE2/packages/"
        done
        for f in dev-items/zips/music/*; do
            ln -sf "$scriptdir/$f" "$mainbuildir/dist/64bit/BEE2/packages/"
        done
    fi
}

create_shortcut_if_needed() {
    cd "$scriptdir" || exit
    [[ "$shortcut" != true ]] && return
    echo "Creating shortcut..."
    if [[ -f "$scriptdir/icon/BEE2.ico" ]]
    then
        geticon="$scriptdir/icon/BEE2.ico"
    else
        geticon="$scriptdir/$mainbuildir/dist/64bit/BEE2/bin/BEE2.ico"
    fi
    echo "[Desktop Entry]
Exec=$scriptdir/$mainbuildir/dist/64bit/BEE2/BEE2
GenericName=$mainbuildir
Icon=$geticon
Name=BEE2
Path=$scriptdir/$mainbuildir/dist/64bit/BEE2
StartupNotify=true
StartupWMClass=BEE2
Terminal=false
Type=Application" > $mainbuildir.desktop
    echo "Shortcut created"
    post_cli_seperator
}


create_link_if_needed() {
    cd "$scriptdir" || exit
    [[ "$distlink" != true ]] && return

    echo "Making link..."

    # Delete old link
    rm "./LN-$mainbuildir"

    # Create a symlink in the root (scriptdir) pointing to the BEE2 folder in the build dir
    ln -sf "$mainbuildir/dist/64bit/BEE2" "./LN-$mainbuildir"

    post_cli_seperator
}

run_bee_if_needed() {
    cd "$scriptdir" || exit
    [[ "$run" != true ]] && return
    echo "Running BEE.."
    cd "$mainbuildir/dist/64bit/BEE2/"
    ./BEE2
    post_cli_seperator
}

main "$@"

Make it executable:

chmod +x bee-builder.sh
Click to see some Script Features
  • Clones and updates BEE2.4 (dev or main)
  • Builds BEE2 using PyInstaller (if needed)
  • Auto-downloads FFmpeg (and only updates when changed)
  • Auto-downloads package items (or pulls dev zips and builds them)
  • Symlinks package items and ffmpeg instead of copying for efficiency
  • Adds Linux puzzlemaker fix
  • Creates a .desktop shortcut
  • Maintains a LN-BEE2.4 symlink for easy access

Usage

Run the script so it asks for options:

./bee-builder.sh

Or even non-interactively with the same options:

./bee-builder.sh "0,2"

This will not ask and just do the defaults with nr 2.

Options:

  • 0 – Use only CLI args (no interactive input)
  • 1 – Delete old build directory
  • 2 – Run BEE2 after build
  • 3 – Don't create shortcut
  • 4 – Disable pip update
  • 5 – Build from main branch instead of dev
  • 6 – Skip installing pip requirements
  • 7 – Skip package build requirements
  • 8 – Skip icon download
  • 9 – Skip Portal 2 Linux puzzlemaker fix
  • 10 – Use current FFmpeg
  • 11 – Use current item packages
  • 12 – Skip symlink creation
  • 13 – Skip package build (for testing)
  • 14 – Skip full build (for testing)
  • 15 – Exit immediately

  • You can link the created .desktop shortcut to your desktop or startmenu if you like

  • At this point there is nothing to to but to use BEE2 to create some chambers

⚠️ **GitHub.com Fallback** ⚠️