Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions CODE_OF_CONDUCT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Code of Conduct

We want this project to be a welcoming and collaborative space for everyone who contributes code, documentation, or discussion. To keep interactions positive and focused on the project, please follow these guidelines:

1. Be respectful and courteous.
2. Use clear, constructive language when giving feedback.
3. Avoid personal attacks, insults, or derogatory remarks.
4. Keep discussions and contributions relevant to the project; avoid political or ideological topics.
5. Do not politicize issues; proposals should focus on technical merit and project goals.
6. If you disagree, discuss ideas, not people.

Any behavior that violates these principles—harassment, hate speech, trolling, or attempts to push unrelated political agendas—may result in removal of comments, denial of contributions, or blocking from the project.

Thank you for helping maintain a collaborative and focused community.
16 changes: 16 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,19 @@ once_cell = "1.17"

[dev-dependencies]
criterion = "0.4"

[[example]]
name = "generic_usage"
path = "examples/generic_usage.rs"

[[example]]
name = "fps_game_analysis"
path = "examples/fps_game_analysis.rs"

[[example]]
name = "multi_game_analysis"
path = "examples/multi_game_analysis.rs"

[[example]]
name = "train_model_example"
path = "examples/train_model_example.rs"
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2025

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
126 changes: 116 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ NoCheat is a fast, machine learning-based anti-cheat library designed to detect
- C-compatible FFI for integration with game engines
- UE5 plugin integration ready
- DataFrame-based feature engineering
- **Generic struct support for custom data analysis**

## How It Works

Expand Down Expand Up @@ -43,7 +44,7 @@ For better results, you can train a model with your own labeled data:

```rust
use nocheat::{train_model};
use nocheat::types::PlayerStats;
use nocheat::types::{DefaultPlayerData, PlayerStats};
use std::collections::HashMap;

// Prepare your training data
Expand All @@ -56,14 +57,18 @@ shots.insert("rifle".to_string(), 100);
let mut hits = HashMap::new();
hits.insert("rifle".to_string(), 50);

training_data.push(PlayerStats {
player_id: "normal_player".to_string(),
let player_data = DefaultPlayerData {
shots_fired: shots.clone(),
hits: hits.clone(),
headshots: 10,
shot_timestamps_ms: None,
training_label: None,
});
};

training_data.push(PlayerStats::new(
"normal_player".to_string(),
player_data
));
labels.push(0.0); // Not a cheater

// Example: Add a cheating player
Expand All @@ -72,20 +77,90 @@ shots.insert("rifle".to_string(), 100);
let mut hits = HashMap::new();
hits.insert("rifle".to_string(), 95); // Suspiciously high accuracy

training_data.push(PlayerStats {
player_id: "cheater".to_string(),
let cheater_data = DefaultPlayerData {
shots_fired: shots,
hits: hits,
headshots: 70, // Very high headshot ratio
shot_timestamps_ms: None,
training_label: None,
});
};

training_data.push(PlayerStats::new(
"cheater".to_string(),
cheater_data
));
labels.push(1.0); // Labeled as a cheater

// Train and save model
train_model(training_data, labels, "cheat_model.bin").expect("Failed to train model");
```

## Using Generic Types for Custom Data Analysis

NoCheat now supports generic data structures, giving you the flexibility to work with any JSON structure for player statistics. This is particularly useful when:

1. Your game has unique metrics to track
2. You want to analyze different aspects of player behavior
3. You need to integrate with existing analytics systems

### Example: Creating a Custom Data Structure

```rust
use nocheat::types::{PlayerStats, PlayerResult, AnalysisResponse};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

// Define a custom data structure for your game
#[derive(Clone, Debug, Deserialize, Serialize)]
struct CustomPlayerData {
accuracy: f32,
reaction_time_ms: Vec<u32>,
movement_patterns: HashMap<String, u32>,
mouse_acceleration: Option<f32>,
}

// Create stats with your custom data structure
let mut movement = HashMap::new();
movement.insert("jumps".to_string(), 50);
movement.insert("crouches".to_string(), 30);

let custom_data = CustomPlayerData {
accuracy: 0.75,
reaction_time_ms: vec![250, 220, 230, 210, 240],
movement_patterns: movement,
mouse_acceleration: Some(1.5),
};

// Create a PlayerStats instance with custom data
let custom_stats = PlayerStats::new("custom_player".to_string(), custom_data);
```

### Custom Result Types

You can also define custom result types for your analysis:

```rust
#[derive(Debug, PartialEq, Serialize)]
struct CustomAnalysisResult {
cheating_probability: f32,
abnormal_patterns: Vec<String>,
confidence_score: f32,
recommended_action: String,
}

// Create a custom result
let custom_result = CustomAnalysisResult {
cheating_probability: 0.85,
abnormal_patterns: vec!["AimSnap".to_string(), "RecoilControl".to_string()],
confidence_score: 0.92,
recommended_action: "Review gameplay footage".to_string(),
};

let player_result = PlayerResult::new("custom_player".to_string(), custom_result);
```

For more detailed examples, see the [Generic Usage Guide](docs/generic_usage.md).

## Integration with Unreal Engine 5

### Prerequisites
Expand Down Expand Up @@ -149,7 +224,7 @@ train_model(training_data, labels, "cheat_model.bin").expect("Failed to train mo

2. Copy the compiled library to the appropriate platform folder in `ThirdParty/NoCheatLibrary/lib/`

3. Create a C interface header in `ThirdParty/NoCheatLibrary/include/nocheat.h`:
3. Create a C interface header in `ThirdParty/NoCheatLibrary/include/nocheat.h` or generate one with `cbindgen --config cbindgen.toml --crate nocheat --output ue_plugin\ThirdParty\NoCheatLib\include\nocheat.h`:
```c
#pragma once

Expand Down Expand Up @@ -516,8 +591,39 @@ To customize the detection for your specific game, you can:

## License

[Include your license information here]
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)

## Contributing

[Include contribution guidelines here]
We welcome contributions from the community! By participating, you agree to abide by our [Code of Conduct](CODE_OF_CONDUCT.md).

### Reporting Issues

- Please search existing issues before opening a new one.
- Provide a clear, descriptive title and detailed steps to reproduce.
- Include rustc version, OS, and any relevant logs or outputs.

### Pull Requests

1. Fork the repository and create your branch from `main`.
2. Follow the Rust style guidelines (run `cargo fmt` and `cargo clippy`).
3. Write tests for your changes and ensure all existing tests pass (`cargo test`).
4. Update documentation in `README.md` or `docs/` as needed.
5. Submit a pull request with a clear description and link to any related issue.

### Development Setup

1. Install Rust via [rustup](https://rustup.rs/).
2. Clone the repo: `git clone https://github.com/yourusername/nocheat.git`.
3. Navigate into the project: `cd nocheat`.
4. Build and test: `cargo build && cargo test`.
5. Generate documentation: `cargo doc --open`.

### Code Style

- We use `rustfmt` for formatting and `clippy` for linting.
- Please adhere to the existing code patterns and naming conventions.

### License

All contributions will be made under the MIT License, ensuring that the project remains free and open source.
43 changes: 22 additions & 21 deletions benches/benchmark.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use nocheat::types::PlayerStats;
use nocheat::types::{DefaultPlayerData, LegacyPlayerStats, PlayerStats};
use nocheat::{build_dataframe, df_to_ndarray, generate_default_model, train_model};
use polars::prelude::{col, DataType, IntoLazy};
use std::collections::HashMap;

fn make_dummy_stats(n: usize) -> Vec<PlayerStats> {
fn make_dummy_stats(n: usize) -> Vec<LegacyPlayerStats> {
let mut result = Vec::with_capacity(n);

for i in 0..n {
Expand All @@ -25,20 +25,21 @@ fn make_dummy_stats(n: usize) -> Vec<PlayerStats> {
let total_hits = (150.0 * accuracy) as u32;
let headshots = (total_hits as f32 * headshot_ratio) as u32;

result.push(PlayerStats {
player_id: format!("player_{}", i),
let player_data = DefaultPlayerData {
shots_fired: shots,
hits: hits,
headshots: headshots,
hits,
headshots,
shot_timestamps_ms: None,
training_label: None,
});
};

result.push(PlayerStats::new(format!("player_{}", i), player_data));
}

result
}

fn create_training_data(n: usize) -> (Vec<PlayerStats>, Vec<f64>) {
fn create_training_data(n: usize) -> (Vec<LegacyPlayerStats>, Vec<f64>) {
let mut players = Vec::with_capacity(n);
let mut labels = Vec::with_capacity(n);

Expand All @@ -56,19 +57,18 @@ fn create_training_data(n: usize) -> (Vec<PlayerStats>, Vec<f64>) {
let headshot_ratio = 0.1 + (i % 15) as f32 * 0.01; // 10-25% headshots
let headshots = ((100.0 * accuracy) as f32 * headshot_ratio) as u32;

players.push(PlayerStats {
player_id: format!("normal_{}", i),
let player_data = DefaultPlayerData {
shots_fired: shots,
hits: hits,
headshots: headshots,
hits,
headshots,
shot_timestamps_ms: None,
training_label: None,
});
};

labels.push(0.0);
}
players.push(PlayerStats::new(format!("normal_{}", i), player_data));

// Create half cheaters
labels.push(0.0);
} // Create half cheaters
for i in 0..(n / 2) {
let mut shots = HashMap::new();
let mut hits = HashMap::new();
Expand All @@ -82,14 +82,15 @@ fn create_training_data(n: usize) -> (Vec<PlayerStats>, Vec<f64>) {
let headshot_ratio = 0.4 + (i % 40) as f32 * 0.01; // 40-80% headshots
let headshots = ((100.0 * accuracy) as f32 * headshot_ratio) as u32;

players.push(PlayerStats {
player_id: format!("cheater_{}", i),
let player_data = DefaultPlayerData {
shots_fired: shots,
hits: hits,
headshots: headshots,
hits,
headshots,
shot_timestamps_ms: None,
training_label: None,
});
};

players.push(PlayerStats::new(format!("cheater_{}", i), player_data));

labels.push(1.0);
}
Expand Down
Loading