Control Flow Documentation - DeckCheatz/wemod-launcher GitHub Wiki

WeMod Launcher - Control Flow and Logic Documentation

Table of Contents

  1. Project Overview
  2. Architecture Summary
  3. Entry Points and Initialization
  4. Core Control Flow
  5. Module Responsibilities
  6. Configuration System
  7. Process Flows
  8. Data Flow Patterns
  9. Environment Variables
  10. Error Handling and Logging

Project Overview

Purpose

The WeMod Launcher is a Python-based application that enables running WeMod (a Windows game modding/cheating tool) on Linux and Steam Deck systems via Wine/Proton compatibility layers. It transparently integrates WeMod into Steam games by managing Wine prefixes, handling dependencies, and orchestrating the game launch process.

Technology Stack

  • Language: Python 3
  • GUI Framework: FreeSimpleGUI
  • HTTP Library: requests
  • Compatibility Layer: Wine/Proton/GE-Proton
  • Package Manager: winetricks
  • Version Control: Git (for self-updates)
  • Container Support: Flatpak (with sandbox escape)

Project Structure

wemod-launcher/
β”œβ”€β”€ wemod                    # Main executable entry point (936 lines)
β”œβ”€β”€ setup.py                 # Initialization and setup logic (473 lines)
β”œβ”€β”€ wemod.bat               # Windows batch file for game/WeMod orchestration
β”œβ”€β”€ consts.py               # Constants and path initialization (144 lines)
β”œβ”€β”€ corenodep.py            # Dependency-free core utilities (163 lines)
β”œβ”€β”€ coreutils.py            # GUI and logging infrastructure (478 lines)
β”œβ”€β”€ constutils.py           # Wine/Proton environment utilities (402 lines)
β”œβ”€β”€ mainutils.py            # Downloads, file operations, UI (671 lines)
β”œβ”€β”€ requirements.txt        # Python dependencies
β”œβ”€β”€ wemod.conf             # User configuration (INI format)
β”œβ”€β”€ wemod.log              # Application log file
β”œβ”€β”€ wemod_data/            # Shared WeMod user data directory
β”œβ”€β”€ wemod_bin/             # WeMod.exe installation directory
β”œβ”€β”€ winetricks             # Downloaded winetricks utility
└── .cache/                # Temporary files and tracking

Total Code: ~2,329 lines of Python + batch script


Architecture Summary

Architectural Patterns

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    Steam Game Launch                         β”‚
β”‚         /path/to/wemod-launcher/wemod %command%             β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                     β”‚
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚   Phase 1: Initialization       β”‚
    β”‚   -------------------------     β”‚
    β”‚   β€’ Version tracking            β”‚
    β”‚   β€’ Dependency management       β”‚
    β”‚   β€’ Self-update from Git        β”‚
    β”‚   β€’ Flatpak sandbox detection   β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                     β”‚
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚   Phase 2: Wine Prefix Setup    β”‚
    β”‚   -------------------------     β”‚
    β”‚   β€’ Detect/create prefix        β”‚
    β”‚   β€’ Sync WeMod data             β”‚
    β”‚   β€’ Download/copy compatible    β”‚
    β”‚   β€’ Build (dotnet48, dxvk)     β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                     β”‚
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚   Phase 3: Game Execution       β”‚
    β”‚   -------------------------     β”‚
    β”‚   β€’ Construct launch command    β”‚
    β”‚   β€’ Monitor game timing         β”‚
    β”‚   β€’ Execute via Proton/Wine     β”‚
    β”‚   β€’ Post-game troubleshooting   β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Component Layering

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚         User Interface Layer                β”‚
β”‚  (FreeSimpleGUI dialogs, progress bars)     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                   β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚         Application Logic Layer             β”‚
β”‚  (wemod script, setup.py, run())           β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                   β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚         Service Layer                       β”‚
β”‚  (mainutils, coreutils, constutils)        β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                   β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚         Infrastructure Layer                β”‚
β”‚  (Wine/Proton, winetricks, GitHub API)     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Entry Points and Initialization

Primary Entry Point: /wemod Script

The main entry point is the wemod executable script invoked by Steam with the launch option:

/path/to/wemod-launcher/wemod %command%

Initialization Sequence (wemod:64-111)

if __name__ == "__main__":
    # 1. Version tracking and config update
    script_manager()

    # 2. Ensure dependencies are installed
    python_venv = venv_manager()

    # 3. Auto-update from Git if configured
    python_venv = self_update(python_venv)

    # 4. Detect and escape Flatpak sandbox if needed
    if_flatpak_list = check_flatpak(python_venv)

    # 5. Re-run in venv or on host if needed
    if len(if_flatpak_list) > 0:
        # Infinite loop protection
        inf_protect = os.getenv("WeModInfProtect", "1")
        if int(inf_protect) > 4:
            exit_with_message("Infinite rerun detected")

        # Increment counter and re-execute
        os.environ["WeModInfProtect"] = str(int(inf_protect) + 1)

        # Build command with venv python or flatpak-spawn
        command = if_flatpak_list + [SCRIPT_FILE] + sys.argv[1:]
        process = subprocess.run(command, capture_output=True)
        sys.exit(process.returncode)

Key Functions Called:

  1. script_manager() (coreutils.py): Tracks version, updates config
  2. venv_manager() (setup.py:201): Creates/manages Python virtual environment
  3. self_update() (setup.py:257): Updates launcher from Git repository
  4. check_flatpak() (setup.py:345): Detects Flatpak and escapes sandbox

Infinite Loop Protection

The WeModInfProtect environment variable prevents infinite re-runs when:

  • Creating virtual environments
  • Escaping Flatpak sandbox
  • Auto-updating from Git

Maximum allowed reruns: 4 (wemod:78)


Core Control Flow

Main Execution Path: run() Function (wemod:649-914)

The run() function is the core orchestrator that handles the entire game launch process.

Step 1: Parse Steam Arguments (wemod:651-721)

Steam passes arguments in one of two formats:

Format A: Standard Proton

[reaper_cmd...] /proton/path waitforexitandrun game.exe [options...]

Format B: Custom Launcher

[reaper_cmd...] -- /proton/path custom_launcher.exe game.exe [options...]

The parser identifies:

  • REAPER_CMD: Steam's command prefix (monitoring, logging)
  • PROTON: Path to Proton executable
  • verb: "waitforexitandrun" or empty (for custom launchers)
  • GAME_EXE: Actual game executable
  • LAUNCH_OPTIONS: Game launch arguments

Step 2: Initialize Wine Prefix (wemod:724-725)

init(PROTON, not bool(verb))

The init() function (wemod:295-442) handles prefix setup:

def init(proton: str, iswine: bool = False) -> None:
    # 1. Create wine prefix if doesn't exist
    if not os.path.isdir(WINEPREFIX):
        os.makedirs(BASE_STEAM_COMPAT, exist_ok=True)

    # 2. Set up wine environment and get version
    prefix_version_file = ensure_wine()
    current_version_parts = parse_version(
        read_file(prefix_version_file)
    )

    # 3. Check if WeMod is installed
    if not os.path.exists(INIT_FILE):
        # Try to find compatible existing prefix
        closest_version, closest_prefix_folder = \
            scanfolderforversions(current_version_parts)

        # Prompt user to copy compatible prefix
        if closest_version and user_accepts:
            copy_folder_with_progress(
                closest_prefix_folder, BASE_STEAM_COMPAT
            )
            syncwemod()
            return

        # If no compatible prefix or user declined
        prefix_op = popup_options(
            "Prefix Setup",
            "Download or build prefix?",
            ["download", "build"](/DeckCheatz/wemod-launcher/wiki/"download",-"build")
        )

        if prefix_op == "build":
            build_prefix(proton_dir)
        else:
            download_prefix(proton_dir)

        syncwemod()

Prefix Setup Decision Tree:

WeMod installed? ───┐
                    β”‚
           NO β—„β”€β”€β”€β”€β”€β”˜
            β”‚
            β”œβ”€β–Ί Scan for compatible prefix
            β”‚   β”œβ”€β–Ί Found exact match (same major.minor)
            β”‚   β”‚   └─► Prompt: "Very likely compatible"
            β”‚   β”œβ”€β–Ί Found same major, different minor
            β”‚   β”‚   └─► Prompt: "Likely compatible"
            β”‚   └─► Found different major
            β”‚       └─► Prompt: "Maybe compatible"
            β”‚
            β”œβ”€β–Ί User accepts? ──YES──► Copy prefix
            β”‚                    β”‚
            β”‚                   NO
            β”‚                    β”‚
            └───► Prompt: "Download or build?"
                  β”œβ”€β–Ί download ──► download_prefix()
                  └─► build ─────► build_prefix()

Step 3: Sync WeMod Data (wemod:135-292)

The syncwemod() function ensures all Wine prefixes share the same WeMod user data:

def syncwemod(folder: Optional[str] = None) -> None:
    WeModData = os.path.join(SCRIPT_PATH, "wemod_data")
    WeModExternal = os.path.join(
        folder or BASE_STEAM_COMPAT,
        "pfx/drive_c/users/steamuser/AppData/Roaming/WeMod"
    )

    # Create launcher data dir if doesn't exist
    if not os.path.isdir(WeModData):
        os.makedirs(WeModData)

    # If external is a real directory (not symlink)
    if os.path.isdir(WeModExternal) and \
       not os.path.islink(WeModExternal):

        # Handle account conflict
        if both_have_data:
            response = show_message(
                "Use Launcher dir account (Yes) or "
                "prefix dir account (No)?",
                yesno=True
            )
            if response == "No":
                # Copy external to launcher dir
                shutil.copytree(WeModExternal, WeModData)

        # Remove external directory
        shutil.rmtree(WeModExternal)

    # Create symlink
    if not os.path.exists(WeModExternal):
        os.symlink(WeModData, WeModExternal)

Data Sync Flow:

Launcher Dir: wemod_data/
Prefix Dir:   pfx/drive_c/.../WeMod/

Case 1: Both empty
  └─► Create symlink

Case 2: Launcher has data, prefix empty
  └─► Create symlink

Case 3: Prefix has data, launcher empty
  └─► Copy prefix β†’ launcher
  └─► Create symlink

Case 4: Both have data
  └─► Prompt user
  └─► Overwrite chosen target
  └─► Create symlink

Step 4: Build Final Command (wemod:727-790)

# Construct the final command
FINAL = (
    REAPER_CMD +           # Steam's command prefix
    [PROTON] +             # Proton executable
    verb +                 # ["waitforexitandrun"] or []
    BAT_COMMAND +          # ["start", "Z:\\...\\wemod.bat"]
    GAME_FRONT +           # Optional pre-game command
    [WIN_CMD] +            # Windows-format game path
    LAUNCH_OPTIONS         # Game arguments
)

Example Final Command:

/home/.../reaper SteamLaunch ... \
  /steamapps/common/Proton/proton \
  waitforexitandrun \
  start "Z:\\home\\...\\wemod.bat" \
  "C:\\game\\game.exe" \
  -windowed -nosound

Step 5: Monitor Game Timing (wemod:793-809)

A background thread monitors if the game closes too quickly:

ttfile = ".cache/early.tmp"
returnfile = ".cache/return.tmp"

# Create tracking file
open(ttfile, "w").close()

# Start monitoring thread
ttime_thread = threading.Thread(
    target=monitor_file,
    args=(ttfile, 90, returnfile)  # 90 second timeout
)
ttime_thread.start()

Monitoring Logic (coreutils.py):

def monitor_file(ttfile, timeout, returnfile):
    # Wait for timeout (90 seconds)
    time.sleep(timeout)

    # If tracking file still exists, game is running
    if os.path.exists(ttfile):
        return  # Game started successfully

    # Game closed early, check if batch wrote message
    if os.path.exists(returnfile):
        message = read_file(returnfile)
        response = bat_respond(returnfile, timeout)
        # User can choose to keep WeMod running

Step 6: Execute Command (wemod:861-900)

Regular Mode:

process = subprocess.Popen(
    FINAL,
    stdout=subprocess.PIPE,
    stderr=subprocess.PIPE
)
stdout, stderr = process.communicate()

# Wait for Wine to fully shut down
if using_custom_launcher:
    subprocess.run(["wineserver", "--wait"])

Flatpak Mode (wemod:804-855):

# Write command to file for host to execute
with open("insideflatpak.tmp", "w") as f:
    for line in FINAL:
        f.write(line + "\n")

# Wait for file to be removed (host executing)
while os.path.isfile("insideflatpak.tmp"):
    time.sleep(1)

# Check for errors
if os.path.isfile("flatpakerror.tmp"):
    raise Exception(read_file("flatpakerror.tmp"))

Step 7: Post-Game Troubleshooting (wemod:911)

troubleshooter()

The troubleshooter presents options after the game closes (constutils.py):

  • Disable/enable troubleshooter (globally or per-game)
  • Delete game prefix (full reinstall)
  • Delete WeMod.exe (force WeMod update)
  • View logs

Module Responsibilities

1. wemod - Main Launcher Script (936 lines)

Primary Responsibilities:

  • Application entry point
  • Initialization orchestration
  • Game launch coordination
  • Command parsing and construction

Key Functions:

  • run(skip_init): Main game launcher (wemod:649)
  • init(proton, iswine): Initialize Wine prefix (wemod:295)
  • syncwemod(folder): Sync WeMod user data (wemod:135)
  • download_prefix(proton_dir): Download pre-built prefix (wemod:445)
  • build_prefix(proton_dir): Build prefix from scratch (wemod:568)

2. setup.py - Setup and Initialization (473 lines)

Primary Responsibilities:

  • First-time setup
  • Dependency management
  • Self-update mechanism
  • Flatpak integration

Key Functions:

  • venv_manager(): Create/manage virtual environment (setup.py:201)
  • self_update(path): Update from Git repository (setup.py:257)
  • check_flatpak(flatpak_cmd): Detect and escape Flatpak (setup.py:345)
  • setup_main(): Download WeMod and winetricks (setup.py:387)
  • download_wemod(temp_dir): Download WeMod installer (setup.py:64)
  • get_wemod_exe_url(): Fetch WeMod URL from Scoop bucket (setup.py:115)
  • unpack_wemod(): Extract WeMod from ZIP (setup.py:135)

3. consts.py - Constants and Path Initialization (144 lines)

Primary Responsibilities:

  • Initialize global constants
  • Resolve Wine prefix paths
  • Handle environment variables

Key Functions:

  • getbatcmd(): Load or download wemod.bat (consts.py:24)
  • get_compat(): Resolve Steam compatibility path (consts.py:70)
  • get_scan_folder(): Determine folder to scan for prefixes (consts.py:130)

Global Constants:

BAT_COMMAND           # ["start", "Z:\\...\\wemod.bat"]
BASE_STEAM_COMPAT     # Wine prefix root directory
STEAM_COMPAT_FOLDER   # Parent of all prefix directories
SCAN_FOLDER           # Directory to scan for WeMod prefixes
WINEPREFIX            # Wine prefix path (.../pfx)
INIT_FILE             # Marker file (.wemod_installer)

4. corenodep.py - Dependency-Free Core (163 lines)

Primary Responsibilities:

  • Configuration file I/O
  • Version parsing
  • Path conversion
  • List manipulation

Key Functions:

  • load_conf_setting(setting, section): Read from wemod.conf (corenodep.py)
  • save_conf_setting(setting, value, section): Write to wemod.conf
  • parse_version(version_str): Extract major.minor version
  • winpath(path, dobble, addfront): Convert Linux β†’ Windows paths
  • check_dependencies(requirements_file): Verify Python packages

No external dependencies - safe for early initialization.

5. coreutils.py - GUI and Logging (478 lines)

Primary Responsibilities:

  • User interface dialogs
  • Logging infrastructure
  • Process monitoring
  • File caching

Key Functions:

  • log(message, open_log): Write to wemod.log
  • show_message(message, title, timeout, yesno): Display popup
  • exit_with_message(): Error dialog and exit
  • monitor_file(ttfile, timeout): Background game monitoring
  • bat_respond(responsefile, timeout): Handle early game close
  • popup_options(title, message, options, timeout): Multi-button dialog
  • get_user_input(title, message, default, timeout): Text input
  • script_manager(): Version tracking
  • pip(command, venv_path): Manage pip installation
  • cache(file_path, default_func): Simple file caching

6. constutils.py - Wine/Proton Environment (402 lines)

Primary Responsibilities:

  • Wine prefix management
  • Prefix compatibility scanning
  • Winetricks integration
  • Post-game troubleshooting

Key Functions:

  • ensure_wine(verstr): Initialize wine prefix (constutils.py:57)
  • scanfolderforversions(current_version): Find compatible prefixes (constutils.py:129)
  • winetricks(command, proton_bin): Execute winetricks (constutils.py)
  • wine(command, proton_bin): Execute wine commands (constutils.py)
  • troubleshooter(): Post-game menu dialog (constutils.py)

Prefix Compatibility Priority:

  1. Priority 1: Exact match (same major.minor)
  2. Priority 2: Same major, lower minor (prefer higher)
  3. Priority 3: Same major, higher minor (prefer lower)
  4. Priority 4: Lower major (prefer higher)
  5. Priority 5: Higher major (prefer lower)

7. mainutils.py - Downloads and File Operations (671 lines)

Primary Responsibilities:

  • HTTP downloads with progress
  • File operations (copy, zip, extract)
  • GitHub API integration
  • Flatpak utilities

Key Functions:

  • download_progress(link, file_name, callback): Download with progress
  • popup_download(title, link, file_name): Download with GUI
  • unpack_zip_with_progress(zip_path, dest_path): Extract ZIP
  • copy_folder_with_progress(source, dest, zipup, ...): Smart copy
  • get_github_releases(repo_name): Fetch releases from GitHub
  • find_closest_compatible_release(releases, version): Match version
  • get_dotnet48(): Cache/download .NET Framework 4.8
  • popup_execute(title, command): Run command with live output
  • is_flatpak(): Detect Flatpak sandbox
  • flatpakrunner(): Run commands on host from Flatpak
  • deref(path): Convert symlinks to real files

8. wemod.bat - Windows Batch Script (110 lines)

Primary Responsibilities:

  • Launch WeMod in background
  • Wait for game to close
  • Detect early game close
  • Kill WeMod when game exits

Execution Flow:

1. Parse paths and arguments
2. Start WeMod.exe in background
3. Retry finding WeMod PID (up to 3 attempts)
4. Start game and wait for exit
5. If game closed too fast (<90s):
   - Delete .cache/early.tmp
   - Write message to .cache/return.tmp
   - Wait for Python to delete return.tmp
6. Kill WeMod process

Configuration System

Configuration File: wemod.conf (INI Format)

Location: wemod-launcher/wemod.conf

Sections and Settings:

[Settings]
# Launcher metadata
Version=1.535                      # Current launcher version
ScriptName=wemod-launcher          # Script identifier

# Paths
WeModLog=wemod.log                 # Log file path
VirtualEnvironment=wemod_venv      # Virtual environment directory
SteamCompatDataPath=               # Override Steam compat path
WinePrefixPath=                    # Override wine prefix path
ScanFolder=                        # Override scan folder

# GitHub integration
RepoUser=DeckCheatz                # GitHub user for prefixes
RepoName=BuiltPrefixes-dev        # GitHub repo for prefixes

# Features
Troubleshoot=true                  # Enable post-game troubleshooter
PackagePrefix=                     # Zip current prefix to file
SelfUpdate=                        # Allow auto-update from Git
NoEXE=                             # Skip game EXE validation
ProtonMinorSeven=                  # Track GE-Proton7 for uploading
FlatpakRunning=                    # Flatpak execution state

Configuration Functions (corenodep.py):

def load_conf_setting(setting, section="Settings"):
    config = configparser.ConfigParser()
    config.read(os.path.join(SCRIPT_PATH, "wemod.conf"))

    if not config.has_section(section):
        return None
    if not config.has_option(section, setting):
        return None

    return config.get(section, setting)

def save_conf_setting(setting, value, section="Settings"):
    config = configparser.ConfigParser()
    config.read(os.path.join(SCRIPT_PATH, "wemod.conf"))

    if not config.has_section(section):
        config.add_section(section)

    if value is None:
        config.remove_option(section, setting)
    else:
        config.set(section, setting, str(value))

    with open(os.path.join(SCRIPT_PATH, "wemod.conf"), "w") as f:
        config.write(f)

Configuration Precedence

For most settings, the precedence order is:

  1. Environment variable (highest priority)
  2. wemod.conf file
  3. Default value (lowest priority)

Example: Wine prefix path

# 1. Check config file
ccompat = load_conf_setting("SteamCompatDataPath")
wcompat = load_conf_setting("WinePrefixPath")

# 2. Check environment variables
if os.getenv("WINE_PREFIX_PATH"):
    os.environ["WINEPREFIX"] = os.getenv("WINE_PREFIX_PATH")
ecompat = os.getenv("STEAM_COMPAT_DATA_PATH")

# 3. Use environment or fall back to config
if not ecompat:
    ecompat = wcompat or os.getenv("WINEPREFIX")

# 4. Default (error if still not set)
if not ecompat:
    exit_with_message("Not running wine")

Process Flows

Flow 1: First-Time Setup

User launches game with wemod %command%
    β”‚
    β”œβ”€β–Ί script_manager() - Create wemod.conf
    β”‚
    β”œβ”€β–Ί venv_manager() - Check dependencies
    β”‚   β”œβ”€β–Ί Dependencies OK? ──YES──► Continue
    β”‚   └─► NO
    β”‚       β”œβ”€β–Ί Try: pip install -r requirements.txt
    β”‚       β”œβ”€β–Ί Success? ──YES──► Continue
    β”‚       └─► NO
    β”‚           β”œβ”€β–Ί mk_venv() - Create virtual environment
    β”‚           β”œβ”€β–Ί pip install in venv
    β”‚           └─► Re-run with venv python
    β”‚
    β”œβ”€β–Ί self_update() - Check Git for updates
    β”‚   β”œβ”€β–Ί On main branch? ──NO──► Skip update
    β”‚   └─► YES
    β”‚       β”œβ”€β–Ί git fetch
    β”‚       β”œβ”€β–Ί Local == Remote? ──YES──► Skip update
    β”‚       └─► NO
    β”‚           β”œβ”€β–Ί git reset --hard origin
    β”‚           β”œβ”€β–Ί git pull
    β”‚           └─► chmod +x scripts
    β”‚
    β”œβ”€β–Ί check_flatpak() - Detect sandbox
    β”‚   β”œβ”€β–Ί In Flatpak? ──NO──► Continue
    β”‚   └─► YES
    β”‚       β”œβ”€β–Ί FROM_FLATPAK set? ──YES──► Continue
    β”‚       └─► NO
    β”‚           β”œβ”€β–Ί Build flatpak-spawn command
    β”‚           β”œβ”€β–Ί Re-execute on host
    β”‚           └─► Exit with host return code
    β”‚
    └─► run() - Main game launch
        β”œβ”€β–Ί init(PROTON) - Setup prefix
        β”‚   β”œβ”€β–Ί ensure_wine() - Create prefix structure
        β”‚   β”œβ”€β–Ί WeMod installed? ──YES──► syncwemod()
        β”‚   └─► NO
        β”‚       β”œβ”€β–Ί scanfolderforversions()
        β”‚       β”‚   β”œβ”€β–Ί Found compatible? ──YES──► copy_folder()
        β”‚       β”‚   └─► NO
        β”‚       β”‚
        β”‚       β”œβ”€β–Ί Prompt: "Download or build?"
        β”‚       β”‚   β”œβ”€β–Ί download
        β”‚       β”‚   β”‚   β”œβ”€β–Ί get_github_releases()
        β”‚       β”‚   β”‚   β”œβ”€β–Ί find_closest_compatible_release()
        β”‚       β”‚   β”‚   β”œβ”€β–Ί popup_download()
        β”‚       β”‚   β”‚   └─► unpack_zip_with_progress()
        β”‚       β”‚   β”‚
        β”‚       β”‚   └─► build
        β”‚       β”‚       β”œβ”€β–Ί deref() - Dereference symlinks
        β”‚       β”‚       β”œβ”€β–Ί popup_options() - dotnet48 method
        β”‚       β”‚       β”œβ”€β–Ί winetricks() - Install deps
        β”‚       β”‚       └─► wine() - Install dotnet48 (if selected)
        β”‚       β”‚
        β”‚       └─► syncwemod() - Link WeMod data
        β”‚
        β”œβ”€β–Ί Construct FINAL command
        β”œβ”€β–Ί monitor_file() thread - 90s timeout
        β”œβ”€β–Ί subprocess.Popen(FINAL)
        β”œβ”€β–Ί wineserver --wait (if custom launcher)
        └─► troubleshooter() - Post-game menu

Flow 2: Subsequent Launches

User launches game
    β”‚
    β”œβ”€β–Ί script_manager() - Update config
    β”œβ”€β–Ί venv_manager() - Check dependencies (already installed)
    β”œβ”€β–Ί self_update() - Skip (no updates)
    β”œβ”€β–Ί check_flatpak() - Skip (FROM_FLATPAK set or not in Flatpak)
    β”‚
    └─► run()
        β”œβ”€β–Ί init(PROTON)
        β”‚   β”œβ”€β–Ί ensure_wine() - Verify prefix exists
        β”‚   β”œβ”€β–Ί WeMod installed? ──YES──► syncwemod()
        β”‚   └─► Continue
        β”‚
        β”œβ”€β–Ί Construct FINAL command
        β”œβ”€β–Ί monitor_file() thread
        β”œβ”€β–Ί subprocess.Popen(FINAL)
        β”‚   β”‚
        β”‚   └─► Proton executes wemod.bat
        β”‚       β”œβ”€β–Ί start WeMod.exe (background)
        β”‚       β”œβ”€β–Ί Get WeMod PID from tasklist
        β”‚       β”œβ”€β–Ί start /wait game.exe
        β”‚       β”‚   β”‚
        β”‚       β”‚   └─► Game runs
        β”‚       β”‚       β”œβ”€β–Ί Game closes < 90s
        β”‚       β”‚       β”‚   β”œβ”€β–Ί Delete .cache/early.tmp
        β”‚       β”‚       β”‚   β”œβ”€β–Ί Write to .cache/return.tmp
        β”‚       β”‚       β”‚   └─► bat_respond() - Prompt user
        β”‚       β”‚       β”‚
        β”‚       β”‚       └─► Game closes > 90s
        β”‚       β”‚           └─► monitor_file() deletes early.tmp
        β”‚       β”‚
        β”‚       └─► taskkill /PID WeMod.exe
        β”‚
        β”œβ”€β–Ί wineserver --wait
        └─► troubleshooter()
            β”œβ”€β–Ί Disable troubleshooter?
            β”œβ”€β–Ί Enable troubleshooter?
            β”œβ”€β–Ί Delete game prefix?
            └─► Delete WeMod.exe?

Flow 3: Flatpak Execution

User launches from Flatpak Steam
    β”‚
    β”œβ”€β–Ί is_flatpak() ──YES──► FROM_FLATPAK set? ──NO
    β”‚                          β”‚
    β”‚                          └─► check_flatpak()
    β”‚                              β”œβ”€β–Ί Build command:
    β”‚                              β”‚   flatpak-spawn --host
    β”‚                              β”‚   --env=STEAM_COMPAT_DATA_PATH=...
    β”‚                              β”‚   --env=FROM_FLATPAK=true
    β”‚                              β”‚   --env=WeModInfProtect=2
    β”‚                              β”‚   -- python wemod [args]
    β”‚                              β”‚
    β”‚                              β”œβ”€β–Ί subprocess.run(command)
    β”‚                              └─► Exit with return code
    β”‚
    └─► Host execution (FROM_FLATPAK=true)
        β”œβ”€β–Ί run()
        β”‚   β”œβ”€β–Ί init(PROTON)
        β”‚   β”œβ”€β–Ί Construct FINAL command
        β”‚   β”œβ”€β–Ί Write command to insideflatpak.tmp
        β”‚   β”‚
        β”‚   └─► Flatpak side (original process):
        β”‚       β”œβ”€β–Ί flatpakrunner() thread
        β”‚       β”‚   β”œβ”€β–Ί Read insideflatpak.tmp
        β”‚       β”‚   β”œβ”€β–Ί subprocess.Popen(FINAL)
        β”‚       β”‚   β”œβ”€β–Ί Capture stdout/stderr
        β”‚       β”‚   β”œβ”€β–Ί Write errors to flatpakerror.tmp
        β”‚       β”‚   β”œβ”€β–Ί Delete insideflatpak.tmp
        β”‚       β”‚   └─► Exit thread
        β”‚       β”‚
        β”‚       └─► Host side (current process):
        β”‚           β”œβ”€β–Ί while insideflatpak.tmp exists:
        β”‚           β”‚   └─► sleep(1)
        β”‚           β”‚
        β”‚           β”œβ”€β–Ί Check flatpakerror.tmp
        β”‚           β”‚   └─► Raise exception if exists
        β”‚           β”‚
        β”‚           └─► troubleshooter()
        β”‚
        └─► Regular execution continues...

Flow 4: Prefix Compatibility Scan

scanfolderforversions(current_version=[8, 26])
    β”‚
    β”œβ”€β–Ί For each folder in STEAM_COMPAT_FOLDER:
    β”‚   β”œβ”€β–Ί Read version file
    β”‚   β”œβ”€β–Ί Parse version string
    β”‚   β”œβ”€β–Ί Check if .wemod_installer exists
    β”‚   β”‚
    β”‚   └─► Version comparison:
    β”‚       β”œβ”€β–Ί 8.26 == 8.26? ──YES──► Priority 1 (best)
    β”‚       β”œβ”€β–Ί 8.25 (same major, lower minor)? ──YES──► Priority 2
    β”‚       β”œβ”€β–Ί 8.27 (same major, higher minor)? ──YES──► Priority 3
    β”‚       β”œβ”€β–Ί 7.50 (lower major)? ──YES──► Priority 4
    β”‚       └─► 9.0 (higher major)? ──YES──► Priority 5 (worst)
    β”‚
    └─► Return closest_version, closest_prefix_folder

Example scan results:
    Current: 8.26

    Available prefixes:
    - 8.26 (priority 1) ← chosen
    - 8.25 (priority 2)
    - 8.30 (priority 3)
    - 7.55 (priority 4)
    - 9.0 (priority 5)

Flow 5: GitHub Prefix Download

download_prefix(proton_dir)
    β”‚
    β”œβ”€β–Ί Load RepoUser and RepoName from config
    β”‚   Default: DeckCheatz/BuiltPrefixes-dev
    β”‚
    β”œβ”€β–Ί Get current Proton version from version file
    β”‚   Example: "GE-Proton8-26" β†’ [8, 26]
    β”‚
    β”œβ”€β–Ί get_github_releases(repo_concat)
    β”‚   β”œβ”€β–Ί requests.get("https://api.github.com/repos/.../releases")
    β”‚   └─► Return list of releases
    β”‚
    β”œβ”€β–Ί find_closest_compatible_release(releases, [8, 26])
    β”‚   β”œβ”€β–Ί For each release:
    β”‚   β”‚   β”œβ”€β–Ί Parse tag: "GE-Proton8.25" β†’ [8, 25]
    β”‚   β”‚   └─► Calculate compatibility priority
    β”‚   β”‚
    β”‚   └─► Return closest_version, download_url
    β”‚
    β”œβ”€β–Ί Show compatibility dialog
    β”‚   β”œβ”€β–Ί Exact match: Auto-accept
    β”‚   β”œβ”€β–Ί Same major: "Likely compatible, download?"
    β”‚   └─► Different major: "Maybe compatible, download?"
    β”‚
    β”œβ”€β–Ί cache(file_name, download_func)
    β”‚   β”œβ”€β–Ί Check .cache/file_name exists?
    β”‚   β”‚   β”œβ”€β–Ί YES: Return cached path
    β”‚   β”‚   └─► NO: popup_download(url, file_name)
    β”‚   β”‚       β”œβ”€β–Ί download_progress() with GUI
    β”‚   β”‚       └─► Save to .cache/
    β”‚
    β”œβ”€β–Ί unpack_zip_with_progress(zip_path, BASE_STEAM_COMPAT)
    β”‚   β”œβ”€β–Ί Extract all files with progress bar
    β”‚   └─► Show percentage complete
    β”‚
    β”œβ”€β–Ί Delete cached ZIP (no longer needed)
    └─► syncwemod()

Flow 6: Prefix Building from Scratch

build_prefix(proton_dir)
    β”‚
    β”œβ”€β–Ί deref(winfolder)
    β”‚   β”œβ”€β–Ί For each file in winfolder:
    β”‚   β”‚   β”œβ”€β–Ί Is symlink? ──YES──► Copy actual file
    β”‚   β”‚   └─► NO ──► Continue
    β”‚   β”‚
    β”‚   └─► Show progress bar
    β”‚
    β”œβ”€β–Ί popup_options("dotnet48 method")
    β”‚   β”œβ”€β–Ί "winetricks" (default for GE-Proton8+)
    β”‚   └─► "wemod-launcher" (ONLY for GE-Proton7)
    β”‚
    β”œβ”€β–Ί Prepare dependency list
    β”‚   β”œβ”€β–Ί Always: "sdl cjkfonts vkd3d dxvk2030"
    β”‚   └─► If winetricks: "dotnet48"
    β”‚
    β”œβ”€β–Ί setup_main() - Download WeMod.exe and winetricks
    β”‚
    β”œβ”€β–Ί For each dependency:
    β”‚   └─► winetricks(dep, proton_dir)
    β”‚       β”œβ”€β–Ί Set PATH and WINEPREFIX
    β”‚       β”œβ”€β–Ί Execute winetricks with dependency
    β”‚       └─► popup_execute() - Show live output
    β”‚
    β”œβ”€β–Ί If wemod-launcher method:
    β”‚   β”œβ”€β–Ί get_dotnet48() - Download/cache installer
    β”‚   β”œβ”€β–Ί wine("winecfg -v win7", path)
    β”‚   β”œβ”€β–Ί wine(dotnet48_installer, path)
    β”‚   β”‚   └─► Check return code (0, 194, -15 are OK)
    β”‚   └─► wine("winecfg -v win10", path)
    β”‚
    β”œβ”€β–Ί Write INIT_FILE (.wemod_installer)
    └─► syncwemod()

Data Flow Patterns

1. Inter-Process Communication

The launcher uses file-based IPC for communication between processes:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                  Python Process                          β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚ Main Thread                                       β”‚  β”‚
β”‚  β”‚  β€’ Parse arguments                                β”‚  β”‚
β”‚  β”‚  β€’ Init wine prefix                               β”‚  β”‚
β”‚  β”‚  β€’ Build command                                  β”‚  β”‚
β”‚  β”‚  β€’ Execute subprocess                             β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚ Monitor Thread                                    β”‚  β”‚
β”‚  β”‚  β€’ sleep(90)                                      β”‚  β”‚
β”‚  β”‚  β€’ Check .cache/early.tmp exists                 β”‚  β”‚
β”‚  β”‚  β€’ Read .cache/return.tmp if early close         β”‚  β”‚
β”‚  β”‚  β€’ Show dialog, wait for user                    β”‚  β”‚
β”‚  β”‚  β€’ Delete .cache/return.tmp                      β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                        β”‚
                        β”‚ subprocess.Popen(FINAL)
                        β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚              Proton/Wine Process                         β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚ wemod.bat                                         β”‚  β”‚
β”‚  β”‚  β€’ start WeMod.exe (background)                   β”‚  β”‚
β”‚  β”‚  β€’ Get WeMod PID                                  β”‚  β”‚
β”‚  β”‚  β€’ start /wait game.exe                           β”‚  β”‚
β”‚  β”‚  β€’ If .cache/early.tmp exists when game closes:  β”‚  β”‚
β”‚  β”‚    - Delete .cache/early.tmp                     β”‚  β”‚
β”‚  β”‚    - Write message to .cache/return.tmp          β”‚  β”‚
β”‚  β”‚    - Wait for .cache/return.tmp to be deleted    β”‚  β”‚
β”‚  β”‚  β€’ taskkill WeMod.exe                             β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

IPC Files:

  • .cache/early.tmp: Game timing tracker (created by Python, deleted by BAT or monitor thread)
  • .cache/return.tmp: Early close message (created by BAT, deleted by Python)
  • .cache/insideflatpak.tmp: Flatpak command file (created by host, deleted by Flatpak)
  • .cache/flatpakerror.tmp: Flatpak error message (created by Flatpak, deleted by host)

2. Configuration Data Flow

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚            Environment Variables                β”‚
β”‚  (Highest Priority)                            β”‚
β”‚  β€’ STEAM_COMPAT_DATA_PATH                      β”‚
β”‚  β€’ WINEPREFIX / WINE_PREFIX_PATH               β”‚
β”‚  β€’ TROUBLESHOOT, SELF_UPDATE, etc.             β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                 β”‚
                 β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚            wemod.conf File                      β”‚
β”‚  (Medium Priority)                             β”‚
β”‚  [Settings]                                    β”‚
β”‚  SteamCompatDataPath=...                       β”‚
β”‚  WinePrefixPath=...                            β”‚
β”‚  Troubleshoot=true                             β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                 β”‚
                 β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚            Default Values                       β”‚
β”‚  (Lowest Priority)                             β”‚
β”‚  RepoUser=DeckCheatz                           β”‚
β”‚  RepoName=BuiltPrefixes-dev                    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Reading Flow:
  load_conf_setting("SteamCompatDataPath")
    β”‚
    β”œβ”€β–Ί Check environment: os.getenv("STEAM_COMPAT_DATA_PATH")
    β”‚   └─► If set, use this value
    β”‚
    β”œβ”€β–Ί Check config file: config.get("Settings", "SteamCompatDataPath")
    β”‚   └─► If exists, use this value
    β”‚
    └─► Use default or exit with error

3. WeMod Data Sync Flow

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚      Launcher Directory                         β”‚
β”‚      wemod_data/                                β”‚
β”‚      β”œβ”€β”€ app_settings.json                      β”‚
β”‚      β”œβ”€β”€ user_data.db                           β”‚
β”‚      └── ...                                    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                 β”‚
                 β”‚ os.symlink()
                 β”‚
                 β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚      Wine Prefix (Game 1)                       β”‚
β”‚      .../pfx/drive_c/users/steamuser/           β”‚
β”‚      AppData/Roaming/WeMod/ ───► (symlink)      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚      Wine Prefix (Game 2)                       β”‚
β”‚      .../pfx/drive_c/users/steamuser/           β”‚
β”‚      AppData/Roaming/WeMod/ ───► (symlink)      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

All game prefixes share the same WeMod account data

4. Log Data Flow

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚              Code Execution                      β”‚
β”‚  log("Message")                                 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                 β”‚
                 β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚          coreutils.log()                        β”‚
β”‚  1. Determine log file path:                    β”‚
β”‚     β€’ WEMOD_LOG env var                         β”‚
β”‚     β€’ WeModLog config setting                   β”‚
β”‚     β€’ Default: wemod.log                        β”‚
β”‚  2. Create parent directories                   β”‚
β”‚  3. Append message with timestamp               β”‚
β”‚  4. If open_log=True, open file in editor       β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                 β”‚
                 β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚              wemod.log                          β”‚
β”‚  2026-01-14 10:30:15 - Message line 1           β”‚
β”‚  2026-01-14 10:30:16 - Message line 2           β”‚
β”‚  ...                                            β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

5. Prefix Download Cache Flow

User requests prefix download
    β”‚
    β–Ό
cache("wemod_prefix8.26.zip", download_func)
    β”‚
    β”œβ”€β–Ί Check .cache/wemod_prefix8.26.zip exists?
    β”‚   β”‚
    β”‚   β”œβ”€β–Ί YES: Return ".cache/wemod_prefix8.26.zip"
    β”‚   β”‚
    β”‚   └─► NO:
    β”‚       β”œβ”€β–Ί download_func()
    β”‚       β”‚   └─► popup_download(url, file_name)
    β”‚       β”‚       β”œβ”€β–Ί download_progress() with callback
    β”‚       β”‚       β”œβ”€β–Ί Update progress bar
    β”‚       β”‚       └─► Save to .cache/
    β”‚       β”‚
    β”‚       └─► Return ".cache/wemod_prefix8.26.zip"
    β”‚
    └─► unpack_zip_with_progress(cached_path, BASE_STEAM_COMPAT)
        β”‚
        └─► Delete cached ZIP after extraction

Environment Variables

Critical Environment Variables

Variable Purpose Set By Used By
STEAM_COMPAT_TOOL_PATHS Proton installation directories Steam consts.py, wemod
STEAM_COMPAT_DATA_PATH Game-specific Wine prefix path Steam consts.py
WINEPREFIX Wine prefix to use Launcher/User Wine, constutils.py
WINE_PREFIX_PATH Alternative wine prefix env var User consts.py
WINE Wine executable path (external runners) User consts.py

Configuration Override Variables

Variable Config Equivalent Purpose
TROUBLESHOOT Troubleshoot Override troubleshooter setting
SELF_UPDATE SelfUpdate Enable/disable auto-update
WEMOD_LOG WeModLog Override log file path
SCANFOLDER ScanFolder Override prefix scan directory
NO_EXE NoEXE Skip game EXE validation
REPO_STRING RepoUser/RepoName Override GitHub repo (user/repo)
WAIT_ON_GAMECLOSE - Timeout for early close dialog (seconds)

Special Control Variables

Variable Purpose Values
GAME_FRONT Pre-game command to execute JSON array string
PACKAGEPREFIX Package current prefix as ZIP "true" or counter
FORCE_UPDATE_WEMOD Force WeMod redownload "1"
WeModInfProtect Infinite rerun protection counter "1" to "4"
FROM_FLATPAK Running from Flatpak host "true"
FLATPAK_ID Flatpak container identifier Container ID

Variable Usage Examples

1. Using external Wine instead of Proton:

export WINE=/usr/bin/wine
export WINEPREFIX=/home/user/.wine
/path/to/wemod game.exe

2. Overriding GitHub repository:

export REPO_STRING=MyUser/MyPrefixRepo
/path/to/wemod %command%

3. Running pre-game command:

export GAME_FRONT='["cmd", "/c", "echo Starting game"]'
/path/to/wemod %command%

4. Packaging current prefix:

export PACKAGEPREFIX=true
/path/to/wemod %command%
# Creates .../prefixes/GE-Proton8.26.zip

Error Handling and Logging

Logging System

Log Function (coreutils.py):

def log(message, open_log=False):
    # 1. Determine log file
    log_file = (
        os.getenv("WEMOD_LOG") or
        load_conf_setting("WeModLog") or
        os.path.join(SCRIPT_PATH, "wemod.log")
    )

    # 2. Create parent directories
    os.makedirs(os.path.dirname(log_file), exist_ok=True)

    # 3. Append message with timestamp
    with open(log_file, "a") as f:
        timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        f.write(f"{timestamp} - {message}\n")

    # 4. Optionally open log in editor
    if open_log:
        os.system(f"xdg-open '{log_file}'")

Logging Levels:

  • Info: Regular operation messages
  • Warning: Unexpected but handled situations
  • Error: Fatal errors (logged before exit_with_message)
  • Debug: Detailed command and state information

Error Handling Patterns

1. Exit with Message (coreutils.py):

def exit_with_message(title, message, code=1, timeout=None, ask_for_log=False):
    # Log the error
    log(f"ERROR [{title}]: {message}")

    # Show GUI dialog
    if ask_for_log:
        response = show_message(
            f"{message}\n\nOpen log file?",
            title, timeout, yesno=True
        )
        if response == "Yes":
            log(open_log=True)
    else:
        show_message(message, title, timeout)

    # Exit with code
    sys.exit(code)

2. Exception Handling in Main Loop (wemod:922-935):

if __name__ == "__main__":
    RESPONCE = ""
    logy = "No"
    try:
        RESPONCE = run()
    except Exception as e:
        RESPONCE = "ERR:\n" + str(e)
        logy = show_message(
            "Error occurred. Open the log?",
            "Error occurred", 30, True
        )

    # Log final response
    log(str(RESPONCE))

    # Open log if user requested
    if logy == "Yes":
        log(open_log=True)

3. Command Execution Error Handling (constutils.py):

def winetricks(command, proton_bin):
    try:
        returncode = popup_execute(
            "Installing dependencies",
            [WINETRICKS] + command.split()
        )

        if returncode != 0:
            log(f"Winetricks failed with code {returncode}")
            response = show_message(
                f"Dependency installation failed (code {returncode}). Continue anyway?",
                yesno=True
            )
            if response == "No":
                exit_with_message("Installation failed", ...)

        return returncode
    except Exception as e:
        log(f"Winetricks exception: {e}")
        exit_with_message("Winetricks error", str(e), ask_for_log=True)

Common Error Scenarios

1. Missing Dependencies:

Error: The python package 'venv' is not installed
Solution: Install python-venv or python3-venv package
Log: Check wemod.log for pip installation errors

2. Wine Prefix Issues:

Error: wine prefix is missing
Solution: Run the game once without WeMod launcher
Log: Check STEAM_COMPAT_DATA_PATH environment variable

3. Flatpak Sandbox:

Error: flatpak-xdg-utils not installed
Solution: Install flatpak-xdg-utils, allow system/session bus in Flatseal
Log: Check for flatpak-spawn errors in wemod.log

4. Network Errors:

Error: Failed to download WeMod
Solution: Check internet connection, try FORCE_UPDATE_WEMOD=1
Log: Check download URLs and HTTP response codes

5. Infinite Rerun Loop:

Error: Infinite script reruns were detected
Solution: Delete wemod.conf, check environment variables
Log: Check WeModInfProtect counter in wemod.log

Troubleshooting Menu

The post-game troubleshooter (constutils.py) provides recovery options:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚        Troubleshooter Menu                   β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ 1. Disable troubleshooter (globally)        β”‚
β”‚ 2. Disable troubleshooter (this game only)  β”‚
β”‚ 3. Enable troubleshooter (globally)         β”‚
β”‚ 4. Enable troubleshooter (this game only)   β”‚
β”‚ 5. Delete game prefix (full reinstall)      β”‚
β”‚ 6. Delete WeMod.exe (force WeMod update)    β”‚
β”‚ 7. View logs                                β”‚
β”‚ 8. Close                                    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Triggered When:

  • Game closes normally (if Troubleshoot=true in config)
  • Game closes early and user chooses to troubleshoot
  • User manually requests troubleshooter via environment variable

Summary

This WeMod Launcher is a sophisticated Python application that:

  1. Manages Wine/Proton prefixes with automatic compatibility detection
  2. Downloads and builds pre-configured Wine environments
  3. Synchronizes WeMod data across multiple game installations
  4. Handles Flatpak sandboxing with transparent host execution
  5. Monitors game execution to detect launcher vs actual game
  6. Provides troubleshooting tools for common issues
  7. Auto-updates from Git repository
  8. Integrates with GitHub for prefix distribution

The control flow is designed to be robust, with multiple fallback mechanisms, user prompts for ambiguous situations, and comprehensive logging for debugging. The architecture separates concerns into logical modules while maintaining tight integration through shared constants and configuration.

Total Lines of Code: ~2,329 Python + 110 Batch = ~2,439 lines

Key Design Patterns:

  • Separation of Concerns: Each module has distinct responsibilities
  • Configuration Hierarchy: Env vars > Config file > Defaults
  • Progressive Enhancement: Works without venv, Flatpak, or GitHub
  • User Confirmation: Prompts for destructive or uncertain operations
  • Comprehensive Logging: All significant operations are logged
  • Error Recovery: Troubleshooting menu and clear error messages