├── .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 |   
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 |

3 |
CS2 TriggerBot
4 |
Your ultimate aiming assistant for Counter-Strike 2
5 |
6 | 
7 | 
8 | 
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 | )
--------------------------------------------------------------------------------