├── .github ├── ISSUE_TEMPLATE │ ├── bug-report.md │ ├── config.yml │ └── feature-request.md ├── dependabot.yml └── workflows │ ├── build.yml │ └── codeql.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── ChangeLog.md ├── LICENSE ├── README.md ├── SECURITY.md ├── classes ├── config_manager.py ├── file_watcher.py ├── logger.py ├── trigger_bot.py └── utility.py ├── gui ├── faq_tab.py ├── general_settings_tab.py ├── home_tab.py ├── logs_tab.py └── main_window.py ├── main.py ├── requirements.txt ├── src ├── img │ ├── github_icon.png │ ├── icon.ico │ ├── icon.png │ ├── telegram_icon.png │ └── update_icon.png ├── styles.css └── supporters.json └── version.txt /.github/ISSUE_TEMPLATE/bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Report a bug to help us improve. 4 | title: "[Bug]: " 5 | labels: "\U0001F41B Bug" 6 | assignees: Jesewe 7 | 8 | --- 9 | 10 | ### Bug Description 11 | *In your own words, describe the bug you encountered.* 12 | 13 | ### Steps to Reproduce 14 | 1. **Step 1:** Describe what you did. 15 | 2. **Step 2:** Explain what happened. 16 | 3. **Step 3:** List any additional steps that could help replicate the issue. 17 | 18 | ### Expected Behavior 19 | *Describe what you expected to happen instead.* 20 | 21 | ### Actual Behavior 22 | *Describe what actually happened. Include any error messages, logs, or screenshots if available.* 23 | 24 | ### Environment Details 25 | - **Operating System:** *(e.g., Windows 10, Windows 11)* 26 | - **Python Version:** *(e.g., Python 3.8)* 27 | - **Version of CS2 TriggerBot:** *(e.g., v1.1.5)* 28 | - **Game Version:** *(e.g., Latest CS2 update)* 29 | 30 | ### Logs 31 | *If available, attach logs from the following locations:* 32 | - `%LOCALAPPDATA%\Requests\ItsJesewe\crashes\tb_logs.log` 33 | - `%LOCALAPPDATA%\Requests\ItsJesewe\crashes\tb_detailed_logs.log` 34 | 35 | ### Additional Context 36 | *Include any other information that might help diagnose or reproduce the issue (e.g., recent changes, system configuration, etc.).* 37 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: Suggest a new feature for the project. 4 | title: "[Feature]: " 5 | labels: "\U0001F680 Enhancement" 6 | assignees: Jesewe 7 | 8 | --- 9 | 10 | ### Describe the Problem 11 | *In your own words, explain the problem that this feature would address. Why is this change necessary?* 12 | 13 | ### Proposed Feature 14 | *Outline your suggested solution. Describe what the new feature should do and provide any key details on how it might be implemented.* 15 | 16 | ### Alternatives Explored 17 | *Have you considered other solutions? List any alternatives and explain why they might be less effective than your proposal.* 18 | 19 | ### Additional Details 20 | *Include any extra information that might help clarify your request, such as screenshots, diagrams, or mockups.* 21 | 22 | ### Checklist 23 | - [ ] This feature is not already implemented or planned (please search existing issues). 24 | - [ ] I have reviewed the [contributing guidelines](https://github.com/Jesewe/cs2-triggerbot/blob/main/CONTRIBUTING.md) for feature requests. 25 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "pip" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | open-pull-requests-limit: 5 8 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build and Release 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | tag_name: 7 | description: 'Tag for the release' 8 | required: true 9 | default: 'v1.0.0' 10 | 11 | permissions: 12 | contents: write 13 | actions: read 14 | id-token: none 15 | 16 | jobs: 17 | build: 18 | 19 | runs-on: windows-latest 20 | 21 | steps: 22 | - name: Checkout code 23 | uses: actions/checkout@v4 24 | 25 | - name: Set up Python 26 | uses: actions/setup-python@v5 27 | with: 28 | python-version: '3.12.5' 29 | 30 | - name: Install dependencies 31 | run: | 32 | python -m pip install --upgrade pip 33 | pip install -r requirements.txt 34 | 35 | - name: Install PyInstaller 36 | run: pip install pyinstaller 37 | 38 | - name: Package Application 39 | run: | 40 | pyinstaller --noconfirm --onefile --windowed --icon "src\img\icon.ico" --name "CS2.Triggerbot" --version-file "version.txt" --add-data "classes;classes/" --add-data "gui;gui/" --add-data "src/img/*;src/img" --add-data "src/*;src" "main.py" 41 | 42 | - name: Upload Build Artifact 43 | uses: actions/upload-artifact@v4 44 | with: 45 | name: CS2_Triggerbot 46 | path: dist/CS2.Triggerbot.exe 47 | 48 | release: 49 | needs: build 50 | runs-on: ubuntu-latest 51 | 52 | steps: 53 | - name: Checkout code 54 | uses: actions/checkout@v4 55 | 56 | - name: Download Build Artifact 57 | uses: actions/download-artifact@v4 58 | with: 59 | name: CS2_Triggerbot 60 | path: ./artifact-download 61 | 62 | - name: Verify Downloaded Artifact 63 | run: | 64 | echo "Listing artifact-download directory:" 65 | ls -la ./artifact-download 66 | if [ ! -f ./artifact-download/CS2.Triggerbot.exe ]; then 67 | echo "Error: File CS2.Triggerbot.exe not found in artifact-download directory!" 68 | exit 1 69 | fi 70 | 71 | - name: Create release 72 | uses: softprops/action-gh-release@v1 73 | env: 74 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 75 | with: 76 | tag_name: ${{ github.event.inputs.tag_name }} 77 | name: CS2 TriggerBot - Release ${{ github.event.inputs.tag_name }} 78 | body_path: ./ChangeLog.md 79 | files: | 80 | ./artifact-download/CS2.Triggerbot.exe 81 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL Advanced" 13 | 14 | on: 15 | pull_request: 16 | branches: [ "main" ] 17 | schedule: 18 | - cron: '31 21 * * 5' 19 | 20 | jobs: 21 | analyze: 22 | name: Analyze (${{ matrix.language }}) 23 | # Runner size impacts CodeQL analysis time. To learn more, please see: 24 | # - https://gh.io/recommended-hardware-resources-for-running-codeql 25 | # - https://gh.io/supported-runners-and-hardware-resources 26 | # - https://gh.io/using-larger-runners (GitHub.com only) 27 | # Consider using larger runners or machines with greater resources for possible analysis time improvements. 28 | runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} 29 | permissions: 30 | # required for all workflows 31 | security-events: write 32 | 33 | # required to fetch internal or private CodeQL packs 34 | packages: read 35 | 36 | # only required for workflows in private repositories 37 | actions: read 38 | contents: read 39 | 40 | strategy: 41 | fail-fast: false 42 | matrix: 43 | include: 44 | - language: actions 45 | build-mode: none 46 | - language: python 47 | build-mode: none 48 | # CodeQL supports the following values keywords for 'language': 'actions', 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift' 49 | # Use `c-cpp` to analyze code written in C, C++ or both 50 | # Use 'java-kotlin' to analyze code written in Java, Kotlin or both 51 | # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both 52 | # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis, 53 | # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning. 54 | # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how 55 | # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages 56 | steps: 57 | - name: Checkout repository 58 | uses: actions/checkout@v4 59 | 60 | # Add any setup steps before running the `github/codeql-action/init` action. 61 | # This includes steps like installing compilers or runtimes (`actions/setup-node` 62 | # or others). This is typically only required for manual builds. 63 | # - name: Setup runtime (example) 64 | # uses: actions/setup-example@v1 65 | 66 | # Initializes the CodeQL tools for scanning. 67 | - name: Initialize CodeQL 68 | uses: github/codeql-action/init@v3 69 | with: 70 | languages: ${{ matrix.language }} 71 | build-mode: ${{ matrix.build-mode }} 72 | # If you wish to specify custom queries, you can do so here or in a config file. 73 | # By default, queries listed here will override any specified in a config file. 74 | # Prefix the list here with "+" to use these queries and those in the config file. 75 | 76 | # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 77 | # queries: security-extended,security-and-quality 78 | 79 | # If the analyze step fails for one of the languages you are analyzing with 80 | # "We were unable to automatically build your code", modify the matrix above 81 | # to set the build mode to "manual" for that language. Then modify this step 82 | # to build your code. 83 | # ℹ️ Command-line programs to run using the OS shell. 84 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 85 | - if: matrix.build-mode == 'manual' 86 | shell: bash 87 | run: | 88 | echo 'If you are using a "manual" build mode for one or more of the' \ 89 | 'languages you are analyzing, replace this with the commands to build' \ 90 | 'your code, for example:' 91 | echo ' make bootstrap' 92 | echo ' make release' 93 | exit 1 94 | 95 | - name: Perform CodeQL Analysis 96 | uses: github/codeql-action/analyze@v3 97 | with: 98 | category: "/language:${{matrix.language}}" 99 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /gui/__pycache__/ 2 | /classes/__pycache__/ 3 | /output/ -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | jesewescience@outlook.com. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to CS2 TriggerBot 2 | 3 | Thank you for your interest in contributing to the CS2 TriggerBot project. This guide will assist you from setting up the development environment to submitting pull requests. 4 | 5 | ## Table of Contents 6 | 7 | * [Code of Conduct](#code-of-conduct) 8 | * [Getting Started](#getting-started) 9 | * [Development Setup](#development-setup) 10 | * [Coding Standards](#coding-standards) 11 | * [Submitting Issues](#submitting-issues) 12 | * [Pull Request Process](#pull-request-process) 13 | * [Feature Requests and Feedback](#feature-requests-and-feedback) 14 | 15 | --- 16 | 17 | ## Code of Conduct 18 | 19 | By participating in this project, you agree to abide by the [Code of Conduct](CODE_OF_CONDUCT.md). Please be respectful, considerate, and open to constructive feedback. 20 | 21 | ## Getting Started 22 | 23 | 1. **Fork the Repository**: Create a personal fork of the repository on GitHub. 24 | 2. **Clone Your Fork**: Clone your fork to your local environment: 25 | 26 | ```bash 27 | git clone https://github.com/Jesewe/cs2-triggerbot.git 28 | cd cs2-triggerbot 29 | ``` 30 | 3. **Add Upstream Remote**: Stay updated with the original repository: 31 | 32 | ```bash 33 | git remote add upstream https://github.com/Jesewe/cs2-triggerbot.git 34 | ``` 35 | 36 | ## Development Setup 37 | 38 | 1. **Install Python**: Ensure you have Python version **≥ 3.8** and **< 3.12.5** installed. 39 | 2. **Install Dependencies**: Install required packages: 40 | 41 | ```bash 42 | pip install -r requirements.txt 43 | ``` 44 | 3. **Run the Application**: Launch the application for testing and development: 45 | 46 | ```bash 47 | python main.py 48 | ``` 49 | 50 | ### Testing Changes 51 | 52 | * Execute the application to verify your modifications. 53 | * Review logs at `%LOCALAPPDATA%\Requests\ItsJesewe\crashes\tb_logs.log` for debugging. 54 | 55 | ## Coding Standards 56 | 57 | Adhere to the following guidelines for consistency and readability: 58 | 59 | * **PEP 8**: Follow the [PEP 8 style guide](https://www.python.org/dev/peps/pep-0008/). 60 | * **Naming**: Use clear, descriptive names for variables, functions, and classes. 61 | * **Error Handling**: Handle exceptions gracefully and log errors appropriately. 62 | * **Documentation**: Comment complex or critical sections to explain intent. 63 | * **Modularity**: Decompose large functions into smaller, reusable units. 64 | 65 | ### GUI Development 66 | 67 | * **customtkinter**: Use `customtkinter` for any enhancements or changes to the graphical interface. 68 | 69 | ### Logging 70 | 71 | * **Format**: Use the `Logger` class format for log entries. 72 | * **Levels**: Apply appropriate log levels (`INFO`, `WARNING`, `ERROR`) based on severity. 73 | 74 | ## Submitting Issues 75 | 76 | Before submitting a new issue: 77 | 78 | 1. Search existing issues to avoid duplicates. 79 | 2. Provide a clear problem description, steps to reproduce, expected behavior, and relevant logs or screenshots. 80 | 81 | ## Pull Request Process 82 | 83 | 1. **Create a Branch**: Use a descriptive branch name: 84 | 85 | ```bash 86 | git checkout -b feature/your-feature 87 | ``` 88 | 2. **Commit Changes**: Ensure each commit message is concise and descriptive: 89 | 90 | ```bash 91 | git commit -m "Brief description of changes" 92 | ``` 93 | 3. **Push to Fork**: 94 | 95 | ```bash 96 | git push origin feature/your-feature 97 | ``` 98 | 4. **Open a Pull Request**: On GitHub, submit a PR to the main repository, including: 99 | 100 | * Purpose of your changes. 101 | * Potential impact on existing functionality. 102 | * Testing steps and results. 103 | 104 | ### Review Process 105 | 106 | * **Code Review**: PRs are reviewed for quality, functionality, and adherence to standards. 107 | * **Feedback**: Address requested changes promptly. 108 | 109 | ## Feature Requests and Feedback 110 | 111 | For new ideas or improvements, open an issue labeled **Feature Request** in the [Issues tab](https://github.com/Jesewe/cs2-triggerbot/issues). 112 | 113 | We appreciate your contributions to CS2 TriggerBot and your efforts in improving this project! 114 | -------------------------------------------------------------------------------- /ChangeLog.md: -------------------------------------------------------------------------------- 1 | ### Summary 2 | 3 | - Added `Share/Import Settings` feature to export and import configuration settings via the GUI. 4 | - Added `Reset to Defaults` button in the GUI to restore all settings to their default values. 5 | - Replaced `json` module with `orjson` for improved performance in handling configuration files. 6 | 7 | ![Downloads](https://img.shields.io/github/downloads/Jesewe/cs2-triggerbot/v1.2.4.5/total?style=for-the-badge&logo=github&color=D5006D) ![Platforms](https://img.shields.io/badge/platform-Windows-blue?style=for-the-badge&color=D5006D) ![License](https://img.shields.io/github/license/jesewe/cs2-triggerbot?style=for-the-badge&color=D5006D) 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 ItsJesewe 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | CS2 TriggerBot 3 |

CS2 TriggerBot

4 |

Your ultimate aiming assistant for Counter-Strike 2

5 | 6 | ![Downloads](https://img.shields.io/github/downloads/jesewe/cs2-triggerbot/total?style=for-the-badge&logo=github&color=D5006D) 7 | ![Platforms](https://img.shields.io/badge/platform-Windows-blue?style=for-the-badge&logo=windows&color=D5006D) 8 | ![License](https://img.shields.io/github/license/jesewe/cs2-triggerbot?style=for-the-badge&color=D5006D) 9 | 10 | Features • 11 | Installation • 12 | Usage • 13 | Configuration • 14 | Troubleshooting • 15 | Contributing 16 | 17 |
18 | 19 | --- 20 | 21 | # Overview 22 | 23 | CS2 TriggerBot is an automated tool designed for Counter-Strike 2 that assists with precise aiming by automatically triggering a mouse click when an enemy is detected in the player's crosshairs. The tool features a graphical user interface (GUI) for easy configuration. 24 | 25 | ## Features 26 | 27 | - **Automatic Trigger**: Fires your weapon when an enemy is detected under your crosshair. 28 | - **Configurable Trigger Key**: Configure a keyboard key or mouse button (`x1` or `x2`) as the trigger via the GUI or `config.json` file. 29 | - **Configurable Delays**: Set minimum, maximum, and post-shot delays for more natural shooting behavior. 30 | - **Toggle Mode**: Enable this mode to keep the bot active after pressing the trigger key once, without holding it. 31 | - **Attack Teammates Option**: Toggle friendly fire with a checkbox in the GUI. 32 | - **Offsets and Client Data**: Automatically fetches the latest offsets and client data from remote sources. 33 | - **Logging**: Detailed logs are saved in `%LOCALAPPDATA%\Requests\ItsJesewe\crashes\tb_logs.log`. 34 | - **Update Checker**: Automatically checks for updates from the GitHub repository. 35 | - **GUI Interface**: Control the bot's behavior and configuration using the included graphical interface. 36 | - **Dynamic Config Update**: Automatically detects and applies changes to the `config.json` file without restarting the bot. 37 | - **Share/Import Settings**: Export your settings to share with others or import settings from a file. 38 | - **Reset to Defaults**: Restore all settings to their default values via the GUI. 39 | 40 | ## Installation 41 | 42 | You can install the trigger bot by cloning the repository or by downloading a pre-built executable file from the releases. 43 | 44 | ### Option 1: Clone the Repository 45 | 46 | 1. **Clone the Repository:** 47 | 48 | ```bash 49 | git clone https://github.com/Jesewe/cs2-triggerbot.git 50 | cd cs2-triggerbot 51 | ``` 52 | 53 | 2. **Install Dependencies:** 54 | 55 | ```bash 56 | pip install -r requirements.txt 57 | ``` 58 | 59 | 3. **Run the Script:** 60 | ```bash 61 | python main.py 62 | ``` 63 | 64 | ### Option 2: Download Pre-Built Executable 65 | 66 | Alternatively, download the ready-to-use executable from the [Releases](https://github.com/jesewe/cs2-triggerbot/releases) page. Download the latest version and run the executable directly. 67 | 68 | **Note:** This project requires Python version >= 3.8 and < 3.12.5. 69 | 70 | ## Configuration 71 | 72 | The `config.json` file is automatically generated in the directory `%LOCALAPPDATA%\Requests\ItsJesewe\` on the first run. You can modify the `TriggerKey` in this file or via the GUI. 73 | 74 | Example `config.json`: 75 | 76 | ```json 77 | { 78 | "Settings": { 79 | "TriggerKey": "x", 80 | "ToggleMode": false, 81 | "ShotDelayMin": 0.01, 82 | "ShotDelayMax": 0.03, 83 | "AttackOnTeammates": false, 84 | "PostShotDelay": 0.1 85 | } 86 | } 87 | ``` 88 | 89 | - **TriggerKey**: The key or mouse button (`x`, `x1`, or `x2`) to activate the bot. 90 | - **ToggleMode**: If `true`, pressing the `TriggerKey` once activates the bot, and pressing it again deactivates it. 91 | - **ShotDelayMin** and **ShotDelayMax**: Control the delay between shots to simulate natural behavior. 92 | - **PostShotDelay**: Set a delay after each shot for more controlled firing. 93 | - **AttackOnTeammates**: Set to `true` to enable friendly fire. 94 | 95 | ## Usage 96 | 97 | 1. Launch Counter-Strike 2. 98 | 2. Run the TriggerBot using the command mentioned above or by launching the GUI version. 99 | 3. Adjust settings like `Trigger Key`, `Toggle Mode`, `Shot Delay`, `Post Shot Delay`, `Attack Teammates`, or use `Share/Import Settings` and `Reset to Defaults` from the GUI. 100 | 4. The bot will automatically start functioning when the game is active. 101 | 102 | ## Troubleshooting 103 | 104 | - **Failed to Fetch Offsets:** Ensure you have an active internet connection and that the source URLs are accessible. 105 | - **Errors with Offsets after Game Update:** After a Counter-Strike 2 game update, there may be issues with offsets, which can result in errors. In this case, please wait for updated offsets to be released. 106 | - **Could Not Open `cs2.exe`:** Make sure the game is running and that you have the necessary permissions. 107 | - **Unexpected Errors:** Check the log file located in the log directory for more details. 108 | - **Issues with Importing Settings:** Ensure the imported config.json file is valid and matches the expected format. 109 | 110 | ## Contributing 111 | 112 | Contributions are welcome! Please open an issue or submit a pull request on the [GitHub repository](https://github.com/Jesewe/cs2-triggerbot). 113 | 114 | ## Disclaimer 115 | 116 | This script is for educational purposes only. Using cheats or hacks in online games is against the terms of service of most games and can result in bans or other penalties. Use this script at your own risk. 117 | 118 | ## License 119 | 120 | This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details. 121 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | The "CS2 TriggerBot" project is an open-source tool designed to assist with aiming in Counter-Strike 2. As of the latest update, the project is actively maintained, and users are encouraged to use the latest version available on the [GitHub repository](https://github.com/Jesewe/cs2-triggerbot). 6 | 7 | ## Reporting a Vulnerability 8 | 9 | If you discover any security vulnerabilities or have concerns regarding the security of this project, please follow these steps: 10 | 11 | 1. **Contact the Maintainer**: Send an email to the project maintainer at [jesewescience@outlook.com](mailto:jesewescience@outlook.com) with detailed information about the vulnerability. 12 | 2. **Provide Detailed Information**: Include in your report: 13 | - A description of the vulnerability. 14 | - Steps to reproduce the issue. 15 | - Potential impact and severity assessment. 16 | - Any possible solutions or mitigations you might suggest. 17 | 3. **Confidentiality**: Please do not disclose the vulnerability publicly until it has been addressed and a fix is released. 18 | 19 | The maintainer will acknowledge receipt of your report within 5 business days and will work with you to understand and address the issue promptly. 20 | 21 | ## Security Considerations 22 | 23 | - **Use at Your Own Risk**: This tool is intended for educational purposes only. Using cheats or automation tools in online games like Counter-Strike 2 may violate the game's terms of service and can result in penalties, including bans. Users should be aware of these risks and use the tool responsibly. 24 | 25 | - **Malware Risks**: Always download the tool from the official [GitHub repository](https://github.com/Jesewe/cs2-triggerbot) to avoid malicious versions. Be cautious of executables from untrusted sources, as they may contain malware or backdoors. 26 | 27 | - **Code Review**: Users are encouraged to review the source code before running the tool to ensure it behaves as expected and does not contain any malicious code. 28 | 29 | - **Data Privacy**: The tool does not collect or transmit any personal data. Ensure that any modifications or forks of the project maintain this standard to protect user privacy. 30 | 31 | ## Disclaimer 32 | 33 | The maintainers of this project are not responsible for any consequences resulting from the use or misuse of this tool. Users assume full responsibility for their actions when using the "CS2 TriggerBot." 34 | 35 | By using this tool, you agree to comply with all relevant laws and game terms of service. 36 | 37 | --- 38 | 39 | For more information, refer to the [README.md](https://github.com/Jesewe/cs2-triggerbot/blob/main/README.md) and [LICENSE](https://github.com/Jesewe/cs2-triggerbot/blob/main/LICENSE) files in the project's repository. 40 | -------------------------------------------------------------------------------- /classes/config_manager.py: -------------------------------------------------------------------------------- 1 | import os, zlib, base64, orjson 2 | from pathlib import Path 3 | 4 | from classes.logger import Logger 5 | 6 | # Initialize the logger for consistent logging 7 | logger = Logger.get_logger() 8 | 9 | class ConfigManager: 10 | """ 11 | Manages the configuration file for the application. 12 | Provides methods to load and save configuration settings, 13 | with caching for efficiency and default configuration management. 14 | """ 15 | # Directory where the configuration file is stored 16 | CONFIG_DIRECTORY = os.path.expanduser(r'~\AppData\Local\Requests\ItsJesewe') 17 | # Full path to the configuration file 18 | CONFIG_FILE = Path(CONFIG_DIRECTORY) / 'config.json' 19 | 20 | # Default configuration settings 21 | DEFAULT_CONFIG = { 22 | "Settings": { 23 | "TriggerKey": "x", # Default trigger key 24 | "ToggleMode": False, # Whether to use toggle mode for the trigger 25 | "ShotDelayMin": 0.01, # Minimum delay between shots in seconds 26 | "ShotDelayMax": 0.03, # Maximum delay between shots in seconds 27 | "AttackOnTeammates": False, # Whether to attack teammates 28 | "PostShotDelay": 0.1 # Delay after each shot 29 | } 30 | } 31 | 32 | # Cache to store the loaded configuration 33 | _config_cache = None 34 | 35 | @classmethod 36 | def load_config(cls): 37 | """ 38 | Loads the configuration from the configuration file. 39 | - Creates the configuration directory and file with default settings if they do not exist. 40 | - Caches the configuration to avoid redundant file reads. 41 | Returns: 42 | dict: The configuration settings. 43 | """ 44 | # Return cached configuration if available. 45 | if cls._config_cache is not None: 46 | return cls._config_cache 47 | 48 | # Ensure the configuration directory exists. 49 | os.makedirs(cls.CONFIG_DIRECTORY, exist_ok=True) 50 | 51 | # Create the configuration file with default settings if it doesn't exist. 52 | if not Path(cls.CONFIG_FILE).exists(): 53 | logger.info("config.json not found at %s, creating a default configuration.", cls.CONFIG_FILE) 54 | cls.save_config(cls.DEFAULT_CONFIG, log_info=False) 55 | cls._config_cache = cls.DEFAULT_CONFIG 56 | else: 57 | try: 58 | # Read and parse the configuration file using orjson. 59 | file_bytes = Path(cls.CONFIG_FILE).read_bytes() 60 | cls._config_cache = orjson.loads(file_bytes) 61 | logger.info("Loaded configuration.") 62 | except (orjson.JSONDecodeError, IOError) as e: 63 | logger.exception("Failed to load configuration: %s", e) 64 | cls.save_config(cls.DEFAULT_CONFIG, log_info=False) 65 | cls._config_cache = cls.DEFAULT_CONFIG.copy() 66 | 67 | # Update the configuration if any keys are missing. 68 | if cls._update_config(cls.DEFAULT_CONFIG, cls._config_cache): 69 | cls.save_config(cls._config_cache, log_info=False) 70 | return cls._config_cache 71 | 72 | @classmethod 73 | def _update_config(cls, default: dict, current: dict) -> bool: 74 | """ 75 | Recursively update `current` with missing keys from `default`. 76 | Returns True if any keys were added. 77 | """ 78 | updated = False 79 | for key, value in default.items(): 80 | if key not in current: 81 | current[key] = value 82 | updated = True 83 | elif isinstance(value, dict) and isinstance(current.get(key), dict): 84 | if cls._update_config(value, current[key]): 85 | updated = True 86 | return updated 87 | 88 | @classmethod 89 | def save_config(cls, config: dict, log_info: bool = True): 90 | """ 91 | Saves the configuration to the configuration file. 92 | Updates the cache with the new configuration. 93 | Args: 94 | config (dict): The configuration settings to save. 95 | log_info (bool): Whether to log a success message after saving. 96 | """ 97 | cls._config_cache = config 98 | try: 99 | # Ensure the configuration directory exists. 100 | Path(cls.CONFIG_DIRECTORY).mkdir(parents=True, exist_ok=True) 101 | # Serialize and write the configuration to the file with pretty formatting. 102 | config_bytes = orjson.dumps(config, option=orjson.OPT_INDENT_2) 103 | Path(cls.CONFIG_FILE).write_bytes(config_bytes) 104 | if log_info: 105 | logger.info("Saved configuration to %s.", cls.CONFIG_FILE) 106 | except IOError as e: 107 | logger.exception("Failed to save configuration: %s", e) -------------------------------------------------------------------------------- /classes/file_watcher.py: -------------------------------------------------------------------------------- 1 | import os, threading 2 | from watchdog.events import FileSystemEventHandler 3 | 4 | from classes.config_manager import ConfigManager 5 | from classes.logger import Logger 6 | 7 | # Initialize the logger for consistent logging 8 | logger = Logger.get_logger() 9 | 10 | class ConfigFileChangeHandler(FileSystemEventHandler): 11 | """ 12 | A file system event handler for monitoring changes to the configuration file. 13 | Automatically updates the bot's configuration when the configuration file is modified. 14 | """ 15 | def __init__(self, bot, debounce_interval=1.0): 16 | """ 17 | Initializes the file change handler with a reference to the bot instance. 18 | Caches the configuration file path for efficiency. 19 | """ 20 | self.bot = bot 21 | self.debounce_interval = debounce_interval 22 | self.debounce_timer = None 23 | self.config_path = ConfigManager.CONFIG_FILE 24 | 25 | def on_modified(self, event): 26 | """Called when a file or directory is modified.""" 27 | if event.src_path and os.path.exists(self.config_path) and os.path.samefile(event.src_path, self.config_path): 28 | if self.debounce_timer is not None: 29 | self.debounce_timer.cancel() 30 | self.debounce_timer = threading.Timer(self.debounce_interval, self.reload_config) 31 | self.debounce_timer.start() 32 | 33 | def reload_config(self): 34 | """Reloads the configuration file and updates the bot's configuration.""" 35 | try: 36 | # Reload the updated configuration file 37 | new_config = ConfigManager.load_config() 38 | # Update the bot's configuration with the new settings 39 | self.bot.update_config(new_config) 40 | except Exception as e: 41 | # Log an error if the configuration update fails 42 | logger.exception("Failed to reload configuration from %s", self.config_path) -------------------------------------------------------------------------------- /classes/logger.py: -------------------------------------------------------------------------------- 1 | import os, logging 2 | 3 | class Logger: 4 | # Define the directory where logs will be stored. 5 | # Use an environment variable (%LOCALAPPDATA%) to ensure logs are stored in a user-specific location. 6 | LOG_DIRECTORY = os.path.expanduser(r'~\AppData\Local\Requests\ItsJesewe\crashes') 7 | 8 | # Define the full path for the log file within the LOG_DIRECTORY. 9 | LOG_FILE = os.path.join(LOG_DIRECTORY, 'tb_logs.log') 10 | 11 | # Define the full path for the detailed log file within the LOG_DIRECTORY. 12 | DETAILED_LOG_FILE = os.path.join(LOG_DIRECTORY, 'tb_detailed_logs.log') 13 | 14 | # Cache for the logger instance. 15 | _logger = None 16 | 17 | @staticmethod 18 | def setup_logging(): 19 | """ 20 | Configures logging for the application. 21 | - Ensures the log directory exists. 22 | - Initializes the log files (clearing previous logs). 23 | - Sets up logging to write messages to both a brief log file, a detailed log file, and the console. 24 | """ 25 | # Ensure the log directory exists. 26 | os.makedirs(Logger.LOG_DIRECTORY, exist_ok=True) 27 | 28 | # Get the root logger and clear any existing handlers. 29 | root_logger = logging.getLogger() 30 | root_logger.handlers.clear() 31 | root_logger.setLevel(logging.INFO) 32 | 33 | # Define the standard formatter for the brief logs and console. 34 | standard_formatter = logging.Formatter( 35 | fmt='[%(asctime)s %(levelname)s]: %(message)s', 36 | datefmt='%Y-%m-%d %H:%M:%S' 37 | ) 38 | 39 | # Create a file handler for the brief log file. 40 | file_handler = logging.FileHandler(Logger.LOG_FILE, mode='w') 41 | file_handler.setLevel(logging.INFO) 42 | file_handler.setFormatter(standard_formatter) 43 | root_logger.addHandler(file_handler) 44 | 45 | # Create a stream handler for console output. 46 | stream_handler = logging.StreamHandler() 47 | stream_handler.setLevel(logging.INFO) 48 | stream_handler.setFormatter(standard_formatter) 49 | root_logger.addHandler(stream_handler) 50 | 51 | # Define a detailed formatter. 52 | detailed_formatter = logging.Formatter( 53 | fmt='[%(asctime)s %(levelname)s {%(module)s : %(funcName)s} (%(lineno)d)]: %(message)s', 54 | datefmt='%Y-%m-%d %H:%M:%S' 55 | ) 56 | 57 | # Create a file handler for detailed logging. 58 | detailed_handler = logging.FileHandler(Logger.DETAILED_LOG_FILE, mode='w') 59 | detailed_handler.setLevel(logging.INFO) 60 | detailed_handler.setFormatter(detailed_formatter) 61 | root_logger.addHandler(detailed_handler) 62 | 63 | @staticmethod 64 | def get_logger(): 65 | """ 66 | Provides a logger instance for use throughout the application. 67 | Returns: 68 | logging.Logger: A logger configured with the settings from `setup_logging`. 69 | """ 70 | if Logger._logger is None: 71 | Logger._logger = logging.getLogger(__name__) 72 | return Logger._logger 73 | 74 | @staticmethod 75 | def log_exception(exc: Exception): 76 | """ 77 | Logs an exception along with its traceback. 78 | Use this method to capture exceptions in a standardized way. 79 | """ 80 | logger_instance = Logger.get_logger() 81 | logger_instance.error("An exception occurred", exc_info=True) 82 | -------------------------------------------------------------------------------- /classes/trigger_bot.py: -------------------------------------------------------------------------------- 1 | import threading, time, random, pymem, pymem.process, keyboard, winsound 2 | 3 | from pynput.mouse import Controller, Button, Listener as MouseListener 4 | from pynput.keyboard import Listener as KeyboardListener 5 | 6 | from classes.config_manager import ConfigManager 7 | from classes.logger import Logger 8 | from classes.utility import Utility 9 | 10 | # Initialize mouse controller and logger 11 | mouse = Controller() 12 | # Initialize the logger for consistent logging 13 | logger = Logger.get_logger() 14 | # Define the main loop sleep time for reduced CPU usage 15 | MAIN_LOOP_SLEEP = 0.05 16 | 17 | class CS2TriggerBot: 18 | VERSION = "v1.2.4.5" 19 | 20 | def __init__(self, offsets: dict, client_data: dict) -> None: 21 | """ 22 | Initialize the TriggerBot with offsets, configuration, and client data. 23 | """ 24 | # Load the configuration settings 25 | self.config = ConfigManager.load_config() 26 | self.offsets, self.client_data = offsets, client_data 27 | self.pm, self.client_base = None, None 28 | self.is_running, self.stop_event = False, threading.Event() 29 | self.trigger_active = False 30 | self.toggle_state = False 31 | self.ent_list = None # Cache for entity list pointer 32 | self.update_config(self.config) 33 | 34 | # Initialize offsets and configuration settings 35 | self.load_configuration() 36 | self.initialize_offsets() 37 | 38 | # Setup listeners 39 | self.keyboard_listener = KeyboardListener(on_press=self.on_key_press, on_release=self.on_key_release) 40 | self.mouse_listener = MouseListener(on_click=self.on_mouse_click) 41 | self.keyboard_listener.start() 42 | self.mouse_listener.start() 43 | 44 | def load_configuration(self) -> None: 45 | """Load and apply configuration settings.""" 46 | settings = self.config['Settings'] 47 | self.trigger_key = settings['TriggerKey'] 48 | self.toggle_mode = settings['ToggleMode'] 49 | self.shot_delay_min = settings['ShotDelayMin'] 50 | self.shot_delay_max = settings['ShotDelayMax'] 51 | self.post_shot_delay = settings['PostShotDelay'] 52 | self.attack_on_teammates = settings['AttackOnTeammates'] 53 | # Check if the trigger key is a mouse button 54 | self.is_mouse_trigger = self.trigger_key in ["x1", "x2"] 55 | 56 | def initialize_offsets(self) -> None: 57 | """Load memory offsets.""" 58 | try: 59 | client = self.offsets["client.dll"] 60 | self.dwEntityList = client["dwEntityList"] 61 | self.dwLocalPlayerPawn = client["dwLocalPlayerPawn"] 62 | 63 | classes = self.client_data["client.dll"]["classes"] 64 | self.m_iHealth = classes["C_BaseEntity"]["fields"]["m_iHealth"] 65 | self.m_iTeamNum = classes["C_BaseEntity"]["fields"]["m_iTeamNum"] 66 | self.m_iIDEntIndex = classes["C_CSPlayerPawnBase"]["fields"]["m_iIDEntIndex"] 67 | logger.info("Offsets have been initialized.") 68 | except KeyError as e: 69 | logger.error(f"Offset initialization error: Missing key {e}") 70 | 71 | def update_config(self, config): 72 | """Update the configuration settings.""" 73 | self.config = config 74 | self.load_configuration() 75 | 76 | def play_toggle_sound(self, state: bool) -> None: 77 | """Play a sound when the toggle key is pressed.""" 78 | try: 79 | if state: 80 | # Sound for activation: frequency 1000 Hz, duration 200 ms 81 | winsound.Beep(1000, 200) 82 | else: 83 | # Sound for deactivation: frequency 500 Hz, duration 200 ms 84 | winsound.Beep(500, 200) 85 | except Exception as e: 86 | logger.error("Error playing toggle sound: {e}") 87 | 88 | def on_key_press(self, key) -> None: 89 | """Handle key press events.""" 90 | if not self.is_mouse_trigger: 91 | try: 92 | # Check if the key pressed is the trigger key 93 | if hasattr(key, 'char') and key.char == self.trigger_key: 94 | if self.toggle_mode: 95 | self.toggle_state = not self.toggle_state 96 | self.play_toggle_sound(self.toggle_state) 97 | else: 98 | self.trigger_active = True 99 | except AttributeError: 100 | pass 101 | 102 | def on_key_release(self, key) -> None: 103 | """Handle key release events.""" 104 | if not self.is_mouse_trigger and not self.toggle_mode: 105 | try: 106 | if hasattr(key, 'char') and key.char == self.trigger_key: 107 | self.trigger_active = False 108 | except AttributeError: 109 | pass 110 | 111 | def on_mouse_click(self, x, y, button, pressed) -> None: 112 | """Handle mouse click events.""" 113 | if self.is_mouse_trigger and button == Button[self.trigger_key]: 114 | if self.toggle_mode and pressed: 115 | self.toggle_state = not self.toggle_state 116 | self.play_toggle_sound(self.toggle_state) 117 | else: 118 | self.trigger_active = pressed 119 | 120 | def initialize_pymem(self) -> bool: 121 | """Attach pymem to the game process.""" 122 | try: 123 | # Attempt to attach to the cs2.exe process 124 | self.pm = pymem.Pymem("cs2.exe") 125 | logger.info("Successfully attached to cs2.exe process.") 126 | return True 127 | except pymem.exception.ProcessNotFound: 128 | # Log an error if the process is not found 129 | logger.error("cs2.exe process not found. Ensure the game is running.") 130 | return False 131 | except Exception as e: 132 | # Log any other exceptions that may occur 133 | logger.error(f"Unexpected error while attaching to cs2.exe: {e}") 134 | return False 135 | 136 | def get_client_module(self) -> bool: 137 | """Retrieve the client.dll module base address.""" 138 | try: 139 | # Attempt to retrieve the client.dll module 140 | client_module = pymem.process.module_from_name(self.pm.process_handle, "client.dll") 141 | self.client_base = client_module.lpBaseOfDll 142 | logger.info("client.dll module found and base address retrieved.") 143 | return True 144 | except pymem.exception.ModuleNotFoundError: 145 | # Log an error if the module is not found 146 | logger.error("client.dll not found. Ensure it is loaded.") 147 | return False 148 | except Exception as e: 149 | # Log any other exceptions that may occur 150 | logger.error(f"Unexpected error while retrieving client.dll module: {e}") 151 | return False 152 | 153 | def get_entity(self, index: int): 154 | """Retrieve an entity from the entity list.""" 155 | try: 156 | # Use cached entity list pointer 157 | list_offset = 0x8 * (index >> 9) 158 | ent_entry = self.pm.read_longlong(self.ent_list + list_offset + 0x10) 159 | entity_offset = 120 * (index & 0x1FF) 160 | return self.pm.read_longlong(ent_entry + entity_offset) 161 | except Exception as e: 162 | logger.error(f"Error reading entity: {e}") 163 | return None 164 | 165 | def should_trigger(self, entity_team: int, player_team: int, entity_health: int) -> bool: 166 | """Determine if the bot should fire.""" 167 | return (self.attack_on_teammates or entity_team != player_team) and entity_health > 0 168 | 169 | def perform_fire_logic(self) -> None: 170 | """Execute the firing logic.""" 171 | try: 172 | # Read the local player and entity ID 173 | player = self.pm.read_longlong(self.client_base + self.dwLocalPlayerPawn) 174 | entity_id = self.pm.read_int(player + self.m_iIDEntIndex) 175 | 176 | if entity_id > 0: 177 | # Retrieve the entity, team, and health 178 | entity = self.get_entity(entity_id) 179 | if entity: 180 | entity_team = self.pm.read_int(entity + self.m_iTeamNum) 181 | player_team = self.pm.read_int(player + self.m_iTeamNum) 182 | entity_health = self.pm.read_int(entity + self.m_iHealth) 183 | # Check if the bot should fire 184 | if self.should_trigger(entity_team, player_team, entity_health): 185 | time.sleep(random.uniform(self.shot_delay_min, self.shot_delay_max)) 186 | mouse.click(Button.left) 187 | time.sleep(self.post_shot_delay) 188 | except Exception as e: 189 | # Log any exceptions that may occur during the process 190 | logger.error(f"Error in fire logic: {e}") 191 | 192 | def start(self) -> None: 193 | """Start the TriggerBot.""" 194 | # Check if pymem is initialized and the client module is retrieved 195 | if not self.initialize_pymem() or not self.get_client_module(): 196 | return 197 | # Cache the entity list pointer 198 | self.ent_list = self.pm.read_longlong(self.client_base + self.dwEntityList) 199 | # Set the running flag to True and log that the TriggerBot has started 200 | self.is_running = True 201 | logger.info("TriggerBot started.") 202 | 203 | # Define local variables for utility functions 204 | is_game_active = Utility.is_game_active 205 | sleep = time.sleep 206 | 207 | while not self.stop_event.is_set(): 208 | try: 209 | # Check if the game is active 210 | if not is_game_active(): 211 | sleep(MAIN_LOOP_SLEEP) 212 | continue 213 | 214 | if self.toggle_mode: 215 | if self.toggle_state: 216 | self.perform_fire_logic() 217 | else: 218 | if self.is_mouse_trigger and self.trigger_active: 219 | self.perform_fire_logic() 220 | elif not self.is_mouse_trigger and keyboard.is_pressed(self.trigger_key): 221 | self.perform_fire_logic() 222 | 223 | sleep(MAIN_LOOP_SLEEP) 224 | except KeyboardInterrupt: 225 | logger.info("TriggerBot stopped by user.") 226 | self.stop() 227 | except Exception as e: 228 | logger.error(f"Unexpected error in main loop: {e}", exc_info=True) 229 | 230 | def stop(self) -> None: 231 | """ 232 | Stops the TriggerBot and cleans up resources. 233 | Also stops the keyboard and mouse listeners. 234 | """ 235 | # Set the running flag to False and signal the stop event 236 | self.is_running = False 237 | self.stop_event.set() 238 | # Stop the keyboard and mouse listeners 239 | if self.keyboard_listener.running: 240 | self.keyboard_listener.stop() 241 | if self.mouse_listener.running: 242 | self.mouse_listener.stop() 243 | # Log that the TriggerBot has stopped 244 | logger.info(f"TriggerBot stopped.") -------------------------------------------------------------------------------- /classes/utility.py: -------------------------------------------------------------------------------- 1 | import os 2 | import requests 3 | import psutil 4 | import sys 5 | import subprocess 6 | import pygetwindow as gw 7 | import orjson 8 | from packaging import version 9 | from dateutil.parser import parse as parse_date 10 | 11 | from PyQt6.QtWidgets import QDialog, QProgressBar, QMessageBox, QVBoxLayout 12 | from PyQt6.QtCore import QThread, pyqtSignal 13 | 14 | from classes.logger import Logger 15 | 16 | # Initialize the logger for consistent logging 17 | logger = Logger.get_logger() 18 | 19 | class Utility: 20 | @staticmethod 21 | def fetch_offsets(): 22 | """ 23 | Fetches JSON data from two remote URLs and parses it using orjson. 24 | - Retrieves data from 'offsets.json' and 'client_dll.json' on GitHub. 25 | - Logs an error if either request fails or the server returns a non-200 status code. 26 | - Handles exceptions gracefully, ensuring no unhandled errors crash the application. 27 | """ 28 | try: 29 | offsets_url = os.getenv( 30 | 'OFFSETS_URL', 31 | 'https://raw.githubusercontent.com/a2x/cs2-dumper/main/output/offsets.json' 32 | ) 33 | response_offset = requests.get(offsets_url) 34 | 35 | client_dll_url = os.getenv( 36 | 'CLIENT_DLL_URL', 37 | 'https://raw.githubusercontent.com/a2x/cs2-dumper/main/output/client_dll.json' 38 | ) 39 | response_client = requests.get(client_dll_url) 40 | 41 | if response_offset.status_code != 200: 42 | logger.error("Failed to fetch offsets: offsets.json request failed.") 43 | return None, None 44 | 45 | if response_client.status_code != 200: 46 | logger.error("Failed to fetch offsets: client_dll.json request failed.") 47 | return None, None 48 | 49 | try: 50 | offset = orjson.loads(response_offset.content) 51 | client = orjson.loads(response_client.content) 52 | return offset, client 53 | except orjson.JSONDecodeError as e: 54 | logger.error(f"Failed to decode JSON response: {e}") 55 | return None, None 56 | 57 | except requests.exceptions.RequestException as e: 58 | logger.error(f"Request failed: {e}") 59 | return None, None 60 | except Exception as e: 61 | logger.exception(f"An unexpected error occurred: {e}") 62 | return None, None 63 | 64 | @staticmethod 65 | def check_for_updates(current_version): 66 | """ 67 | Checks GitHub for the latest version using orjson for JSON parsing. 68 | """ 69 | try: 70 | response = requests.get("https://api.github.com/repos/Jesewe/cs2-triggerbot/releases/latest") 71 | response.raise_for_status() 72 | data = orjson.loads(response.content) 73 | latest_version = data.get("tag_name") 74 | update_url = data.get("html_url") 75 | if version.parse(latest_version) > version.parse(current_version): 76 | logger.info(f"New version available: {latest_version}.") 77 | return update_url 78 | logger.info("No new updates available.") 79 | return None 80 | except requests.exceptions.RequestException as e: 81 | logger.error(f"Update check failed: {e}") 82 | return None 83 | except Exception as e: 84 | logger.error(f"An unexpected error occurred during update check: {e}") 85 | return None 86 | 87 | @staticmethod 88 | def get_latest_exe_download_url(): 89 | """ 90 | Retrieves the direct download URL for the 'CS2.Triggerbot.exe' asset 91 | from the latest GitHub release, parsing JSON with orjson. 92 | """ 93 | try: 94 | response = requests.get("https://api.github.com/repos/Jesewe/cs2-triggerbot/releases/latest") 95 | response.raise_for_status() 96 | data = orjson.loads(response.content) 97 | for asset in data.get("assets", []): 98 | if asset.get("name") == "CS2.Triggerbot.exe": 99 | return asset.get("browser_download_url") 100 | logger.error("Executable asset not found in the latest release.") 101 | return None 102 | except Exception as e: 103 | logger.error(f"Error getting update asset URL: {e}") 104 | return None 105 | 106 | @staticmethod 107 | def fetch_last_offset_update(last_update_label): 108 | """ 109 | Fetches the timestamp of the latest commit to the offsets repository. 110 | """ 111 | try: 112 | response = requests.get("https://api.github.com/repos/a2x/cs2-dumper/commits/main") 113 | response.raise_for_status() 114 | commit_data = orjson.loads(response.content) 115 | commit_timestamp = commit_data["commit"]["committer"]["date"] 116 | 117 | last_update_dt = parse_date(commit_timestamp) 118 | formatted_timestamp = last_update_dt.strftime("%m/%d/%Y %H:%M:%S") 119 | last_update_label.setText(f"Last offsets update: {formatted_timestamp} (UTC)") 120 | last_update_label.setStyleSheet("font-size: 16px; color: #ffa420; font-weight: bold;") 121 | except requests.exceptions.HTTPError as e: 122 | if response.status_code == 403: 123 | last_update_label.setText("Request limit exceeded. Please try again later.") 124 | last_update_label.setStyleSheet("font-size: 16px; color: #0bda51; font-weight: bold;") 125 | logger.error(f"Offset update fetch failed: {e} (403 Forbidden)") 126 | else: 127 | last_update_label.setText("Error fetching last offsets update. Please check your internet connection or try again later.") 128 | last_update_label.setStyleSheet("font-size: 16px; color: #0bda51; font-weight: bold;") 129 | logger.error(f"Offset update fetch failed: {e}") 130 | except Exception as e: 131 | last_update_label.setText("Error fetching last offsets update. Please check your internet connection or try again later.") 132 | last_update_label.setStyleSheet("font-size: 16px; color: #0bda51; font-weight: bold;") 133 | logger.error(f"Offset update fetch failed: {e}") 134 | 135 | @staticmethod 136 | def resource_path(relative_path): 137 | """Returns the path to a resource, supporting both normal startup and frozen .exe.""" 138 | try: 139 | if hasattr(sys, '_MEIPASS'): 140 | return os.path.join(sys._MEIPASS, relative_path) 141 | return os.path.join(os.path.abspath("."), relative_path) 142 | except Exception as e: 143 | logger.error(f"Failed to get resource path: {e}") 144 | return None 145 | 146 | @staticmethod 147 | def is_game_active(): 148 | """Check if the game window is active using pygetwindow.""" 149 | windows = gw.getWindowsWithTitle('Counter-Strike 2') 150 | return any(window.isActive for window in windows) 151 | 152 | @staticmethod 153 | def is_game_running(): 154 | """Check if the game process is running using psutil.""" 155 | return any(proc.info['name'] == 'cs2.exe' for proc in psutil.process_iter(attrs=['name'])) -------------------------------------------------------------------------------- /gui/faq_tab.py: -------------------------------------------------------------------------------- 1 | from PyQt6.QtWidgets import QWidget, QVBoxLayout, QTextEdit 2 | 3 | def init_faq_tab(main_window): 4 | """ 5 | Sets up the FAQs tab with common questions and answers. 6 | """ 7 | faq_tab = QWidget() 8 | layout = QVBoxLayout() 9 | faqs_content = """ 10 |

Frequently Asked Questions

11 |

Q: What is a TriggerBot?

12 |

A: A TriggerBot is a tool that automatically shoots when your crosshair is over an enemy.

13 |

Q: Is this tool safe to use?

14 |

A: This tool is for educational purposes only. Use it at your own risk.

15 |

Q: How do I start the TriggerBot?

16 |

A: Go to the 'Home' tab and click 'Start Bot' after ensuring the game is running.

17 |

Q: How can I update the offsets?

18 |

A: Offsets are fetched automatically from the server. Check the 'Home' tab for the last update timestamp.

19 |

Q: Can I customize the bot's behavior?

20 |

A: Yes, use the 'General Settings' tab to adjust key configurations, delays, and teammate attack settings.

21 |

Q: Does the TriggerBot work on FACEIT?

22 |

A: No, using the TriggerBot on FACEIT is against their terms of service and can result in a ban.

23 |

Q: I found a bug, where can I report it?

24 |

A: Report bugs by opening an issue on our GitHub Issues page.

25 |

Q: How can I contribute to the project?

26 |

A: Open a pull request on our GitHub Pull Requests page.

27 |

Q: Where can I join the community?

28 |

A: Join our Telegram Channel for updates and support.

29 | """ 30 | faqs_widget = QTextEdit() 31 | faqs_widget.setHtml(faqs_content) 32 | faqs_widget.setReadOnly(True) 33 | layout.addWidget(faqs_widget) 34 | faq_tab.setLayout(layout) 35 | main_window.tabs.addTab(faq_tab, "FAQs") -------------------------------------------------------------------------------- /gui/general_settings_tab.py: -------------------------------------------------------------------------------- 1 | import orjson, base64, zlib 2 | 3 | from PyQt6.QtWidgets import QWidget, QFormLayout, QLineEdit, QCheckBox, QPushButton, QHBoxLayout, QSpacerItem, QSizePolicy, QDialog, QVBoxLayout, QLabel, QTextEdit, QApplication 4 | from PyQt6.QtGui import QDesktopServices 5 | from PyQt6.QtCore import QUrl, Qt 6 | 7 | from classes.config_manager import ConfigManager 8 | from classes.utility import Utility 9 | 10 | class ShareImportDialog(QDialog): 11 | def __init__(self, parent=None): 12 | super().__init__(parent) 13 | self.setWindowTitle("Share/Import Settings") 14 | self.setModal(True) 15 | self.setFixedSize(400, 250) 16 | self.init_ui() 17 | 18 | def init_ui(self): 19 | layout = QVBoxLayout() 20 | 21 | # Label and text area for code input/output 22 | self.label = QLabel("Enter code to import or generate code to share:") 23 | self.code_input = QTextEdit() 24 | self.code_input.setFixedHeight(100) 25 | 26 | # Buttons 27 | self.import_button = QPushButton("Import Settings") 28 | self.export_button = QPushButton("Generate and Copy Code") 29 | 30 | # Button connections 31 | self.import_button.clicked.connect(self.import_settings) 32 | self.export_button.clicked.connect(self.export_settings) 33 | 34 | # Add widgets to layout 35 | layout.addWidget(self.label) 36 | layout.addWidget(self.code_input) 37 | layout.addWidget(self.import_button) 38 | layout.addWidget(self.export_button) 39 | 40 | self.setLayout(layout) 41 | 42 | def export_settings(self): 43 | # Gather current settings 44 | settings = { 45 | 'TriggerKey': self.parent().trigger_key_input.text(), 46 | 'ToggleMode': self.parent().toggle_mode_checkbox.isChecked(), 47 | 'ShotDelayMin': float(self.parent().min_delay_input.text() or 0.01), 48 | 'ShotDelayMax': float(self.parent().max_delay_input.text() or 0.1), 49 | 'PostShotDelay': float(self.parent().post_shot_delay_input.text() or 0.1), 50 | 'AttackOnTeammates': self.parent().attack_teammates_checkbox.isChecked() 51 | } 52 | 53 | # Serialize to JSON, compress with zlib, and encode to base64 54 | json_bytes = orjson.dumps(settings) 55 | compressed = zlib.compress(json_bytes) 56 | encoded = base64.b64encode(compressed).decode() 57 | code = f"TB-{encoded}" 58 | 59 | # Copy to clipboard using QApplication 60 | clipboard = QApplication.instance().clipboard() 61 | clipboard.setText(code) 62 | 63 | # Show in text area and notify user 64 | self.code_input.setText(code) 65 | self.label.setText("Code copied to clipboard!") 66 | 67 | def import_settings(self): 68 | code = self.code_input.toPlainText().strip() 69 | if not code.startswith("TB-"): 70 | self.label.setText("Invalid code format. Must start with 'TB-'") 71 | return 72 | 73 | # Decode, decompress, and apply settings 74 | try: 75 | encoded = code[3:] 76 | compressed = base64.b64decode(encoded) 77 | json_bytes = zlib.decompress(compressed) 78 | settings = orjson.loads(json_bytes) 79 | 80 | # Apply settings to UI 81 | main_window = self.parent() 82 | main_window.trigger_key_input.setText(settings.get('TriggerKey', '')) 83 | main_window.toggle_mode_checkbox.setChecked(settings.get('ToggleMode', False)) 84 | main_window.min_delay_input.setText(str(settings.get('ShotDelayMin', 0.01))) 85 | main_window.max_delay_input.setText(str(settings.get('ShotDelayMax', 0.1))) 86 | main_window.post_shot_delay_input.setText(str(settings.get('PostShotDelay', 0.1))) 87 | main_window.attack_teammates_checkbox.setChecked(settings.get('AttackOnTeammates', False)) 88 | 89 | # Save the imported settings using the module function 90 | save_general_settings(main_window) 91 | 92 | self.label.setText("Settings imported and saved successfully!") 93 | except Exception as e: 94 | self.label.setText(f"Error importing settings: {str(e)}") 95 | 96 | def init_general_settings_tab(main_window): 97 | """ 98 | Sets up the General Settings tab for configuring the bot. 99 | Creates input fields and buttons, then attaches them to the main window. 100 | """ 101 | general_settings_tab = QWidget() 102 | form_layout = QFormLayout() 103 | 104 | settings = main_window.bot.config.get('Settings', {}) 105 | 106 | main_window.trigger_key_input = QLineEdit(settings.get('TriggerKey', '')) 107 | main_window.trigger_key_input.setToolTip("Set the key to activate the trigger bot (e.g., 'x' or 'x1' for mouse button 4, 'x2' for mouse button 5).") 108 | main_window.toggle_mode_checkbox = QCheckBox("Toggle Mode") 109 | main_window.toggle_mode_checkbox.setChecked(settings.get('ToggleMode', False)) 110 | main_window.toggle_mode_checkbox.setToolTip("If checked, the trigger will toggle on/off with the trigger key.") 111 | main_window.min_delay_input = QLineEdit(str(settings.get('ShotDelayMin', 0.01))) 112 | main_window.min_delay_input.setToolTip("Minimum delay between shots in seconds (e.g., 0.01).") 113 | main_window.max_delay_input = QLineEdit(str(settings.get('ShotDelayMax', 0.1))) 114 | main_window.max_delay_input.setToolTip("Maximum delay between shots in seconds (must be >= Min Delay).") 115 | main_window.post_shot_delay_input = QLineEdit(str(settings.get('PostShotDelay', 0.1))) 116 | main_window.post_shot_delay_input.setToolTip("Delay after each shot in seconds (e.g., 0.1).") 117 | main_window.attack_teammates_checkbox = QCheckBox("Attack Teammates") 118 | main_window.attack_teammates_checkbox.setChecked(settings.get('AttackOnTeammates', False)) 119 | main_window.attack_teammates_checkbox.setToolTip("If checked, the bot will attack teammates as well.") 120 | 121 | save_button = QPushButton("Save Config") 122 | save_button.setToolTip("Save the configuration settings to the configuration file.") 123 | save_button.clicked.connect(lambda: save_general_settings(main_window)) 124 | open_config_button = QPushButton("Open Config Directory") 125 | open_config_button.setToolTip("Open the directory where the configuration file is stored.") 126 | open_config_button.clicked.connect(lambda: QDesktopServices.openUrl(QUrl.fromLocalFile(ConfigManager.CONFIG_DIRECTORY))) 127 | share_import_button = QPushButton("Share/Import") 128 | share_import_button.setToolTip("Open a dialog to share or import settings.") 129 | share_import_button.clicked.connect(lambda: ShareImportDialog(main_window).exec()) 130 | reset_button = QPushButton("Reset to Defaults") 131 | reset_button.setToolTip("Reset all settings to their default values.") 132 | reset_button.clicked.connect(lambda: reset_to_defaults(main_window)) 133 | 134 | # First checkbox layout for Toggle Mode and Attack Teammates 135 | checkbox_layout_1 = QHBoxLayout() 136 | checkbox_layout_1.addWidget(main_window.toggle_mode_checkbox) 137 | checkbox_layout_1.addWidget(main_window.attack_teammates_checkbox) 138 | 139 | button_layout = QHBoxLayout() 140 | button_layout.addWidget(save_button) 141 | button_layout.addWidget(open_config_button) 142 | button_layout.addWidget(share_import_button) 143 | button_layout.addWidget(reset_button) 144 | 145 | form_layout.addRow("Trigger Key:", main_window.trigger_key_input) 146 | form_layout.addRow(checkbox_layout_1) 147 | form_layout.addRow("Min Shot Delay:", main_window.min_delay_input) 148 | form_layout.addRow("Max Shot Delay:", main_window.max_delay_input) 149 | form_layout.addRow("Post Shot Delay:", main_window.post_shot_delay_input) 150 | form_layout.addItem(QSpacerItem(15, 15, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding)) 151 | form_layout.addRow(button_layout) 152 | 153 | general_settings_tab.setLayout(form_layout) 154 | main_window.tabs.addTab(general_settings_tab, "General Settings") 155 | 156 | def save_general_settings(main_window): 157 | """ 158 | Saves the user's changes to the bot's configuration: 159 | - Validates user inputs. 160 | - Updates the bot's configuration with new values. 161 | - Persists the updated configuration using the ConfigManager. 162 | """ 163 | try: 164 | validate_inputs(main_window) 165 | settings = main_window.bot.config['Settings'] 166 | settings['TriggerKey'] = main_window.trigger_key_input.text().strip() 167 | settings['ToggleMode'] = main_window.toggle_mode_checkbox.isChecked() 168 | settings['AttackOnTeammates'] = main_window.attack_teammates_checkbox.isChecked() 169 | settings['ShotDelayMin'] = float(main_window.min_delay_input.text()) 170 | settings['ShotDelayMax'] = float(main_window.max_delay_input.text()) 171 | settings['PostShotDelay'] = float(main_window.post_shot_delay_input.text()) 172 | ConfigManager.save_config(main_window.bot.config) 173 | main_window.bot.update_config(main_window.bot.config) 174 | from PyQt6.QtWidgets import QMessageBox 175 | QMessageBox.information(main_window, "Settings Saved", "Configuration has been successfully saved.") 176 | except ValueError as e: 177 | from PyQt6.QtWidgets import QMessageBox 178 | QMessageBox.critical(main_window, "Invalid Input", str(e)) 179 | 180 | def validate_inputs(main_window): 181 | """ 182 | Validates user input fields in the General Settings tab. 183 | Ensures all required fields have valid values. 184 | """ 185 | trigger_key = main_window.trigger_key_input.text().strip() 186 | if not trigger_key: 187 | raise ValueError("Trigger key cannot be empty.") 188 | 189 | try: 190 | min_delay = float(main_window.min_delay_input.text().strip()) 191 | max_delay = float(main_window.max_delay_input.text().strip()) 192 | post_delay = float(main_window.post_shot_delay_input.text().strip()) 193 | except ValueError: 194 | raise ValueError("Delay values must be valid numbers.") 195 | 196 | if min_delay < 0 or max_delay < 0 or post_delay < 0: 197 | raise ValueError("Delay values must be non-negative.") 198 | if min_delay > max_delay: 199 | raise ValueError("Minimum delay cannot be greater than maximum delay.") 200 | 201 | def reset_to_defaults(main_window): 202 | """ 203 | Resets all settings in the General Settings tab to their default values 204 | as defined in ConfigManager.DEFAULT_CONFIG, then сохраняет их. 205 | """ 206 | defaults = ConfigManager.DEFAULT_CONFIG['Settings'] 207 | 208 | main_window.trigger_key_input.setText(defaults.get('TriggerKey', '')) 209 | main_window.toggle_mode_checkbox.setChecked(defaults.get('ToggleMode', False)) 210 | main_window.min_delay_input.setText(str(defaults.get('ShotDelayMin', 0.01))) 211 | main_window.max_delay_input.setText(str(defaults.get('ShotDelayMax', 0.1))) 212 | main_window.post_shot_delay_input.setText(str(defaults.get('PostShotDelay', 0.1))) 213 | main_window.attack_teammates_checkbox.setChecked(defaults.get('AttackOnTeammates', False)) 214 | 215 | save_general_settings(main_window) -------------------------------------------------------------------------------- /gui/home_tab.py: -------------------------------------------------------------------------------- 1 | from PyQt6.QtWidgets import QWidget, QVBoxLayout, QLabel, QHBoxLayout, QPushButton 2 | from PyQt6.QtCore import Qt 3 | 4 | from classes.utility import Utility 5 | 6 | def init_home_tab(main_window): 7 | """ 8 | Sets up the Home tab with bot instructions, status, and control buttons. 9 | Attaches key widgets to the main window (e.g. status_label, start_button). 10 | """ 11 | home_tab = QWidget() 12 | layout = QVBoxLayout() 13 | layout.setSpacing(15) 14 | 15 | # Bot status label 16 | main_window.status_label = QLabel("Bot Status: Inactive") 17 | main_window.status_label.setStyleSheet("font-size: 16px; color: #FF5252; font-weight: bold;") 18 | 19 | # Last offsets update label 20 | main_window.last_update_label = QLabel("Last offsets update: Fetching...") 21 | main_window.last_update_label.setStyleSheet("font-size: 14px; font-style: italic; color: #B0B0B0;") 22 | Utility.fetch_last_offset_update(main_window.last_update_label) 23 | 24 | # Quick start guide 25 | layout.addWidget(create_section_label("Quick Start Guide", 18)) 26 | quick_start_text = QLabel( 27 | "1. Open CS2 game and ensure it’s running.
" 28 | "2. Configure trigger key and delays in General Settings.
" 29 | "3. Press Start Bot to activate.
" 30 | "4. Monitor bot status and logs in the 'Logs' tab." 31 | ) 32 | quick_start_text.setStyleSheet("font-size: 14px;") 33 | quick_start_text.setTextFormat(Qt.TextFormat.RichText) 34 | quick_start_text.setWordWrap(True) 35 | layout.addWidget(quick_start_text) 36 | 37 | # Additional Information 38 | layout.addWidget(create_section_label("Additional Information", 18)) 39 | additional_info_text = QLabel( 40 | "For more details, visit our " 41 | "GitHub repository " 42 | "or join our " 43 | "Telegram channel." 44 | ) 45 | additional_info_text.setStyleSheet("font-size: 14px;") 46 | additional_info_text.setTextFormat(Qt.TextFormat.RichText) 47 | additional_info_text.setOpenExternalLinks(True) 48 | additional_info_text.setWordWrap(True) 49 | layout.addWidget(additional_info_text) 50 | 51 | # Bot control buttons 52 | main_window.start_button = QPushButton("Start Bot") 53 | main_window.stop_button = QPushButton("Stop Bot") 54 | main_window.start_button.clicked.connect(main_window.start_bot) 55 | main_window.stop_button.clicked.connect(main_window.stop_bot) 56 | 57 | buttons_layout = QHBoxLayout() 58 | buttons_layout.addWidget(main_window.start_button) 59 | buttons_layout.addWidget(main_window.stop_button) 60 | buttons_layout.setSpacing(20) 61 | 62 | layout.addWidget(main_window.status_label) 63 | layout.addWidget(main_window.last_update_label) 64 | layout.addLayout(buttons_layout) 65 | 66 | home_tab.setLayout(layout) 67 | main_window.tabs.addTab(home_tab, "Home") 68 | 69 | def create_section_label(text, font_size): 70 | """Helper to create a styled section label.""" 71 | label = QLabel(text) 72 | label.setStyleSheet(f"font-size: {font_size}px; font-weight: bold; color: #D5006D; margin-top: 10px;") 73 | return label -------------------------------------------------------------------------------- /gui/logs_tab.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from PyQt6.QtWidgets import QWidget, QVBoxLayout, QTextEdit 4 | from PyQt6.QtCore import QTimer 5 | 6 | from classes.logger import Logger 7 | 8 | def init_logs_tab(main_window): 9 | """ 10 | Sets up the Logs tab to display application logs. 11 | Attaches a read-only text area to the main window. 12 | """ 13 | logs_tab = QWidget() 14 | layout = QVBoxLayout() 15 | main_window.log_output = QTextEdit() 16 | main_window.log_output.setReadOnly(True) 17 | 18 | # Initialize log tracking 19 | main_window.last_log_position = 0 20 | main_window.log_timer = QTimer(main_window) 21 | main_window.log_timer.timeout.connect(lambda: update_log_output(main_window)) 22 | main_window.log_timer.start(1000) # Update logs every second 23 | 24 | layout.addWidget(main_window.log_output) 25 | logs_tab.setLayout(layout) 26 | main_window.tabs.addTab(logs_tab, "Logs") 27 | 28 | def update_log_output(main_window): 29 | """ 30 | Periodically updates the Logs tab with new log entries from the log file. 31 | Appends new log entries since the last read position to the log display. 32 | """ 33 | try: 34 | # Check the current size of the log file. 35 | file_size = os.path.getsize(Logger.LOG_FILE) 36 | 37 | # If the file has been truncated or rotated, reset the position. 38 | if file_size < main_window.last_log_position: 39 | main_window.last_log_position = 0 40 | 41 | # If there's no new content, exit early. 42 | if main_window.last_log_position == file_size: 43 | return 44 | 45 | with open(Logger.LOG_FILE, 'r') as log_file: 46 | log_file.seek(main_window.last_log_position) 47 | new_logs = log_file.read() 48 | # Update the last read position. 49 | main_window.last_log_position = log_file.tell() 50 | 51 | if new_logs: 52 | main_window.log_output.insertPlainText(new_logs) 53 | main_window.log_output.ensureCursorVisible() 54 | 55 | except Exception as e: 56 | main_window.log_output.append(f"Failed to read log file: {e}") 57 | main_window.log_output.ensureCursorVisible() -------------------------------------------------------------------------------- /gui/main_window.py: -------------------------------------------------------------------------------- 1 | import os, threading 2 | 3 | from PyQt6.QtCore import Qt, QTimer, QUrl, QSize 4 | from PyQt6.QtWidgets import (QMainWindow, QPushButton, QLabel, QLineEdit, QTextEdit, 5 | QCheckBox, QVBoxLayout, QHBoxLayout, QWidget, QMessageBox, 6 | QFormLayout, QTabWidget, QSpacerItem, QSizePolicy) 7 | from PyQt6.QtGui import QIcon, QDesktopServices 8 | 9 | from watchdog.observers import Observer 10 | 11 | from classes.utility import Utility 12 | from classes.trigger_bot import CS2TriggerBot 13 | from classes.config_manager import ConfigManager 14 | from classes.file_watcher import ConfigFileChangeHandler 15 | from classes.logger import Logger 16 | 17 | from gui.home_tab import init_home_tab 18 | from gui.general_settings_tab import init_general_settings_tab 19 | from gui.logs_tab import init_logs_tab 20 | from gui.faq_tab import init_faq_tab 21 | 22 | # Cache the logger instance 23 | logger = Logger.get_logger() 24 | 25 | class MainWindow(QMainWindow): 26 | def __init__(self): 27 | """ 28 | Initialize the main application window and setup UI components. 29 | """ 30 | super().__init__() 31 | 32 | self.repo_url = "github.com/Jesewe/cs2-triggerbot" 33 | self.setWindowTitle(f"CS2 TriggerBot | {self.repo_url}") 34 | self.setFixedSize(700, 500) 35 | 36 | # Load and apply custom styles from external stylesheet. 37 | self.apply_stylesheet(Utility.resource_path('src/styles.css')) 38 | 39 | # Set application icon if available. 40 | self.set_app_icon(Utility.resource_path('src/img/icon.png')) 41 | 42 | # Create main layout with tabs. 43 | self.main_layout = QVBoxLayout() 44 | self.tabs = QTabWidget() 45 | 46 | # Fetch offsets and initialize the TriggerBot. 47 | offsets, client_data = self.fetch_offsets_or_warn() 48 | self.bot = CS2TriggerBot(offsets, client_data) 49 | 50 | # Build tabs using functions from separate modules. 51 | init_home_tab(self) 52 | init_general_settings_tab(self) 53 | init_logs_tab(self) 54 | init_faq_tab(self) 55 | 56 | # Build the top section (header) with app name and icon buttons. 57 | self.top_layout = self.build_top_layout() 58 | 59 | self.main_layout.addLayout(self.top_layout) 60 | self.main_layout.addWidget(self.tabs) 61 | container = QWidget() 62 | container.setLayout(self.main_layout) 63 | self.setCentralWidget(container) 64 | 65 | # Initialize file watcher for configuration changes. 66 | self.init_config_watcher() 67 | 68 | def apply_stylesheet(self, stylesheet_path): 69 | """Load and apply the stylesheet if available.""" 70 | try: 71 | with open(stylesheet_path, 'r') as f: 72 | self.setStyleSheet(f.read()) 73 | except Exception as e: 74 | logger.error("Failed to load stylesheet: %s", e) 75 | 76 | def set_app_icon(self, icon_path): 77 | """Set the application icon if the file exists.""" 78 | if os.path.exists(icon_path): 79 | self.setWindowIcon(QIcon(icon_path)) 80 | else: 81 | logger.info("Icon not found at %s, skipping.", icon_path) 82 | 83 | def fetch_offsets_or_warn(self): 84 | """Attempt to fetch offsets; warn the user and return empty dictionaries on failure.""" 85 | try: 86 | offsets, client_data = Utility.fetch_offsets() 87 | if offsets is None or client_data is None: 88 | raise ValueError("Failed to fetch offsets from the server.") 89 | return offsets, client_data 90 | except Exception as e: 91 | QMessageBox.warning(self, "Offsets Fetch Error", str(e)) 92 | return {}, {} 93 | 94 | def build_top_layout(self): 95 | """Builds the top layout with the app title and icon buttons.""" 96 | top_layout = QHBoxLayout() 97 | 98 | # Application title label. 99 | name_app = QLabel(f"CS2 TriggerBot {CS2TriggerBot.VERSION}") 100 | name_app.setStyleSheet("color: #D5006D; font-size: 22px; font-weight: bold;") 101 | 102 | # Create icon buttons. 103 | icon_layout = QHBoxLayout() 104 | icon_layout.setAlignment(Qt.AlignmentFlag.AlignRight) 105 | icon_layout.addWidget(self.create_icon_button('src/img/telegram_icon.png', 106 | "Join our Telegram channel", 107 | "https://t.me/cs2_jesewe")) 108 | icon_layout.addWidget(self.create_icon_button('src/img/github_icon.png', 109 | "Visit our GitHub repository", 110 | "https://github.com/Jesewe/cs2-triggerbot")) 111 | 112 | # Check for updates. 113 | update_url = Utility.check_for_updates(CS2TriggerBot.VERSION) 114 | if update_url: 115 | update_btn = self.create_icon_button('src/img/update_icon.png', 116 | "New update available! Click to download.", 117 | update_url, 118 | custom_style=("QPushButton { background-color: #333333; " 119 | "border-radius: 12px; border: 2px solid #D5006D; } " 120 | "QPushButton:hover { background-color: #444444; }")) 121 | icon_layout.addWidget(update_btn) 122 | 123 | # Assemble top layout. 124 | top_layout.addWidget(name_app) 125 | top_layout.addStretch() 126 | top_layout.addLayout(icon_layout) 127 | return top_layout 128 | 129 | def create_icon_button(self, relative_path, tooltip, url, custom_style=None): 130 | """ 131 | Create a flat icon button that opens the provided URL when clicked. 132 | """ 133 | btn = QPushButton() 134 | btn.setIcon(QIcon(Utility.resource_path(relative_path))) 135 | btn.setIconSize(QSize(24, 24)) 136 | btn.setFlat(True) 137 | btn.setToolTip(tooltip) 138 | if custom_style: 139 | btn.setStyleSheet(custom_style) 140 | btn.clicked.connect(lambda: QDesktopServices.openUrl(QUrl(url))) 141 | return btn 142 | 143 | def init_config_watcher(self): 144 | """ 145 | Initializes a file watcher to monitor changes in the configuration file. 146 | Automatically updates the bot's configuration when the file changes. 147 | """ 148 | try: 149 | event_handler = ConfigFileChangeHandler(self.bot) 150 | self.observer = Observer() 151 | self.observer.schedule(event_handler, path=ConfigManager.CONFIG_DIRECTORY, recursive=False) 152 | self.observer.start() 153 | logger.info("Config file watcher started successfully.") 154 | except Exception as e: 155 | logger.error("Failed to initialize config watcher: %s", e) 156 | 157 | def closeEvent(self, event): 158 | """ 159 | Handles application close event to ensure resources are cleaned up: 160 | - Stops the file watcher. 161 | - Stops the bot if it is running. 162 | """ 163 | try: 164 | if hasattr(self, 'observer'): 165 | self.observer.stop() 166 | self.observer.join() 167 | except Exception as e: 168 | logger.error("Error stopping observer: %s", e) 169 | 170 | if self.bot.is_running: 171 | self.bot.stop() 172 | if hasattr(self, 'bot_thread') and self.bot_thread is not None: 173 | self.bot_thread.join(timeout=2) 174 | if self.bot_thread.is_alive(): 175 | logger.warning("Bot thread did not terminate cleanly.") 176 | self.bot_thread = None 177 | event.accept() 178 | 179 | def start_bot(self): 180 | """ 181 | Starts the bot if it is not already running: 182 | - Ensures the game process (cs2.exe) is running. 183 | - Launches the bot in a separate thread. 184 | """ 185 | if self.bot.is_running: 186 | QMessageBox.warning(self, "Bot Already Running", "The bot is already running.") 187 | return 188 | 189 | if not Utility.is_game_running(): 190 | QMessageBox.critical(self, "Game Not Running", "Could not find cs2.exe process. Make sure the game is running.") 191 | return 192 | 193 | # Clear the stop event to ensure the bot runs. 194 | self.bot.stop_event.clear() 195 | self.bot_thread = threading.Thread(target=self.bot.start, daemon=True) 196 | self.bot_thread.start() 197 | 198 | self.status_label.setText("Bot Status: Active") 199 | self.status_label.setStyleSheet("font-size: 16px; color: #008000; font-weight: bold;") 200 | 201 | def stop_bot(self): 202 | """ 203 | Stops the bot if it is currently running: 204 | - Signals the bot's stop event to terminate its main loop. 205 | - Waits for the bot's thread to terminate and updates the UI. 206 | """ 207 | if not self.bot.is_running: 208 | QMessageBox.warning(self, "Bot Not Started", "The bot is not running.") 209 | return 210 | 211 | self.bot.stop() 212 | if hasattr(self, 'bot_thread') and self.bot_thread is not None: 213 | self.bot_thread.join(timeout=2) 214 | if self.bot_thread.is_alive(): 215 | logger.warning("Bot thread did not terminate cleanly.") 216 | self.bot_thread = None 217 | 218 | self.status_label.setText("Bot Status: Inactive") 219 | self.status_label.setStyleSheet("font-size: 16px; color: #FF5252; font-weight: bold;") -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from PyQt6.QtWidgets import QApplication 3 | 4 | from classes.logger import Logger 5 | from gui.main_window import MainWindow 6 | from classes.trigger_bot import CS2TriggerBot 7 | 8 | def main(): 9 | # Set up logging for the application. 10 | Logger.setup_logging() 11 | logger = Logger.get_logger() 12 | 13 | # Initialize the QApplication. If an instance already exists, use it; otherwise, create a new one. 14 | app = QApplication.instance() or QApplication(sys.argv) 15 | 16 | # Log the loaded version. 17 | logger.info("Loaded version: %s", CS2TriggerBot.VERSION) 18 | 19 | # Create and display the main application window. 20 | window = MainWindow() 21 | window.show() 22 | 23 | # Start the application event loop and exit with the returned status. 24 | sys.exit(app.exec()) 25 | 26 | if __name__ == "__main__": 27 | main() -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | keyboard==0.13.5 2 | packaging==25.0 3 | psutil==7.0.0 4 | pymem==1.14.0 5 | pynput==1.8.1 6 | PyQt6==6.9.0 7 | PyQt6_sip==13.10.0 8 | python_dateutil==2.9.0.post0 9 | pywin32==310 10 | Requests==2.32.4 11 | watchdog==6.0.0 12 | PyGetWindow==0.0.9 13 | orjson==3.10.18 -------------------------------------------------------------------------------- /src/img/github_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jesewe/cs2-triggerbot/6b9be3c789d301b1dbea36615e22794b4c818ebb/src/img/github_icon.png -------------------------------------------------------------------------------- /src/img/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jesewe/cs2-triggerbot/6b9be3c789d301b1dbea36615e22794b4c818ebb/src/img/icon.ico -------------------------------------------------------------------------------- /src/img/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jesewe/cs2-triggerbot/6b9be3c789d301b1dbea36615e22794b4c818ebb/src/img/icon.png -------------------------------------------------------------------------------- /src/img/telegram_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jesewe/cs2-triggerbot/6b9be3c789d301b1dbea36615e22794b4c818ebb/src/img/telegram_icon.png -------------------------------------------------------------------------------- /src/img/update_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jesewe/cs2-triggerbot/6b9be3c789d301b1dbea36615e22794b4c818ebb/src/img/update_icon.png -------------------------------------------------------------------------------- /src/styles.css: -------------------------------------------------------------------------------- 1 | QMainWindow { 2 | background-color: #1A1A1A; 3 | color: #E0E0E0; 4 | font-family: Segoe UI, Arial, sans-serif; 5 | font-size: 16px; 6 | } 7 | 8 | QLabel { 9 | color: #F0F0F0; 10 | font-weight: bold; 11 | font-size: 14px; 12 | } 13 | 14 | QLineEdit, 15 | QComboBox { 16 | background-color: #2C2C2C; 17 | color: #E0E0E0; 18 | border: 1px solid #444444; 19 | padding: 6px; 20 | border-radius: 6px; 21 | font-size: 14px; 22 | } 23 | 24 | QLineEdit:focus, 25 | QComboBox:focus { 26 | border: 1px solid #D5006D; 27 | background-color: #333333; 28 | } 29 | 30 | QComboBox::drop-down { 31 | border: none; 32 | width: 20px; 33 | } 34 | 35 | QComboBox::down-arrow { 36 | image: none; 37 | } 38 | 39 | QComboBox QAbstractItemView { 40 | background-color: #2C2C2C; 41 | color: #E0E0E0; 42 | selection-background-color: #444444; 43 | selection-color: #D5006D; 44 | border: 1px solid #444444; 45 | border-radius: 4px; 46 | padding: 4px; 47 | } 48 | 49 | QTextEdit { 50 | background-color: #2C2C2C; 51 | color: #E0E0E0; 52 | border: 1px solid #444444; 53 | border-radius: 6px; 54 | padding: 8px; 55 | font-family: Consolas, monospace; 56 | font-size: 14px; 57 | } 58 | 59 | QTextEdit:focus { 60 | border: 1px solid #D5006D; 61 | background-color: #333333; 62 | } 63 | 64 | QTextEdit:disabled { 65 | background-color: #1F1F1F; 66 | color: #666666; 67 | border: 1px solid #333333; 68 | } 69 | 70 | QPushButton { 71 | background-color: #333333; 72 | color: #D5006D; 73 | font-weight: bold; 74 | padding: 8px 16px; 75 | border-radius: 8px; 76 | border: 1px solid #555555; 77 | font-size: 14px; 78 | } 79 | 80 | QPushButton:hover { 81 | background-color: #444444; 82 | } 83 | 84 | QPushButton:pressed { 85 | background-color: #222222; 86 | border: 1px solid #D5006D; 87 | } 88 | 89 | QPushButton:disabled { 90 | background-color: #1F1F1F; 91 | color: #666666; 92 | border: 1px solid #333333; 93 | } 94 | 95 | QTabWidget::pane { 96 | border: 1px solid #444444; 97 | background-color: #1A1A1A; 98 | border-radius: 6px; 99 | padding: 4px; 100 | } 101 | 102 | QTabBar::tab { 103 | background-color: #2C2C2C; 104 | color: #E0E0E0; 105 | padding: 8px 16px; 106 | border: 1px solid #444444; 107 | border-top-left-radius: 6px; 108 | border-top-right-radius: 6px; 109 | margin: 2px 2px; 110 | font-weight: bold; 111 | font-size: 14px; 112 | } 113 | 114 | QTabBar::tab:selected { 115 | background-color: #444444; 116 | color: #D5006D; 117 | border-bottom: 2px solid #D5006D; 118 | } 119 | 120 | QTabBar::tab:hover { 121 | background-color: #3A3A3A; 122 | } 123 | 124 | QTabBar::tab:disabled { 125 | background-color: #1F1F1F; 126 | color: #666666; 127 | border: 1px solid #333333; 128 | } 129 | 130 | QTabBar::tab:selected:hover { 131 | background-color: #505050; 132 | } 133 | 134 | QTabWidget>QWidget { 135 | background-color: #1A1A1A; 136 | } 137 | 138 | QCheckBox { 139 | color: #E0E0E0; 140 | padding: 6px; 141 | font-size: 14px; 142 | } 143 | 144 | QCheckBox::indicator { 145 | width: 18px; 146 | height: 18px; 147 | border-radius: 9px; 148 | border: 2px solid #444444; 149 | background-color: #2C2C2C; 150 | } 151 | 152 | QCheckBox::indicator:checked { 153 | background-color: #D5006D; 154 | border: 2px solid #D5006D; 155 | } 156 | 157 | QCheckBox::indicator:unchecked:hover { 158 | border: 2px solid #666666; 159 | } 160 | 161 | QCheckBox::indicator:checked:hover { 162 | background-color: #E91E63; 163 | border: 2px solid #E91E63; 164 | } 165 | 166 | QCheckBox::indicator:disabled { 167 | background-color: #1F1F1F; 168 | border: 2px solid #333333; 169 | } 170 | 171 | QScrollBar:vertical { 172 | background-color: #2C2C2C; 173 | width: 14px; 174 | margin: 14px 2px 14px 2px; 175 | border: 1px solid #444444; 176 | border-radius: 7px; 177 | } 178 | 179 | QScrollBar::handle:vertical { 180 | background-color: #D5006D; 181 | min-height: 20px; 182 | border-radius: 7px; 183 | } 184 | 185 | QScrollBar::handle:vertical:hover { 186 | background-color: #E91E63; 187 | } 188 | 189 | QScrollBar::add-line:vertical, 190 | QScrollBar::sub-line:vertical { 191 | background: none; 192 | } 193 | 194 | QScrollBar::add-page:vertical, 195 | QScrollBar::sub-page:vertical { 196 | background: none; 197 | } 198 | 199 | QScrollBar:horizontal { 200 | background-color: #2C2C2C; 201 | height: 14px; 202 | margin: 2px 14px 2px 14px; 203 | border: 1px solid #444444; 204 | border-radius: 7px; 205 | } 206 | 207 | QScrollBar::handle:horizontal { 208 | background-color: #D5006D; 209 | min-width: 20px; 210 | border-radius: 7px; 211 | } 212 | 213 | QScrollBar::handle:horizontal:hover { 214 | background-color: #E91E63; 215 | } 216 | 217 | QScrollBar::add-line:horizontal, 218 | QScrollBar::sub-line:horizontal { 219 | background: none; 220 | } 221 | 222 | QScrollBar::add-page:horizontal, 223 | QScrollBar::sub-page:horizontal { 224 | background: none; 225 | } 226 | 227 | QMenu { 228 | background-color: #2C2C2C; 229 | color: #E0E0E0; 230 | border: 1px solid #444444; 231 | border-radius: 4px; 232 | } 233 | 234 | QMenu::item { 235 | padding: 6px 20px; 236 | } 237 | 238 | QMenu::item:selected { 239 | background-color: #444444; 240 | color: #D5006D; 241 | } 242 | 243 | QToolTip { 244 | background-color: #2C2C2C; 245 | color: #E0E0E0; 246 | border: 1px solid #444444; 247 | border-radius: 4px; 248 | padding: 4px; 249 | } 250 | 251 | QProgressBar { 252 | background-color: #2C2C2C; 253 | color: #E0E0E0; 254 | border: 1px solid #444444; 255 | border-radius: 6px; 256 | text-align: center; 257 | } 258 | 259 | QProgressBar::chunk { 260 | background-color: #D5006D; 261 | border-radius: 4px; 262 | } -------------------------------------------------------------------------------- /src/supporters.json: -------------------------------------------------------------------------------- 1 | { 2 | "boosty": { 3 | "supporter": [ 4 | "lexus88" 5 | ], 6 | "early_access": [ 7 | "MagaTZZ" 8 | ] 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /version.txt: -------------------------------------------------------------------------------- 1 | # UTF-8 2 | # This is a version file for the executable 3 | 4 | VSVersionInfo( 5 | ffi=FixedFileInfo( 6 | filevers=(1, 2, 4, 4), 7 | prodvers=(1, 2, 4, 4), 8 | mask=0x3f, 9 | flags=0x0, 10 | OS=0x4, 11 | fileType=0x1, 12 | subtype=0x0, 13 | date=(0, 0) 14 | ), 15 | kids=[ 16 | StringFileInfo( 17 | [ 18 | StringTable( 19 | u'040904b0', 20 | [ 21 | StringStruct(u'CompanyName', u'Jesewe'), 22 | StringStruct(u'FileDescription', u'CS2 Triggerbot'), 23 | StringStruct(u'FileVersion', u'1.2.4.4'), 24 | StringStruct(u'InternalName', u'CS2 Triggerbot'), 25 | StringStruct(u'LegalCopyright', u'(C) 2025 Jesewe. MIT License'), 26 | StringStruct(u'OriginalFilename', u'CS2 Triggerbot.exe'), 27 | StringStruct(u'ProductName', u'CS2 Triggerbot'), 28 | StringStruct(u'ProductVersion', u'1.2.4.4') 29 | ] 30 | ) 31 | ] 32 | ), 33 | VarFileInfo([VarStruct(u'Translation', [1033, 1200])]) 34 | ] 35 | ) --------------------------------------------------------------------------------