Skip to content

Commit

Permalink
init
Browse files Browse the repository at this point in the history
  • Loading branch information
RicoRodriges committed May 1, 2023
0 parents commit 7e31c5e
Show file tree
Hide file tree
Showing 26 changed files with 2,009 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/target
12 changes: 12 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[package]
name = "cyberbot2077"
version = "0.1.0"
edition = "2021"

[dependencies]
winapi = { version = "0.3", features = ["winuser"] }
clipboard-win = "4.4"
bmp = "0.5"
winput = "0.2"

#tesseract = "0.12"
115 changes: 115 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# CyberBot 2077

Bot, who solves breach protocol games from Cyberpunk 2077.

[![Preview](assets/preview.gif)](assets/preview.mp4)

## Usage

- [Download](#download) or [build](#build) the bot
- Launch `cyberbot2077.exe`
- Launch a breach protocol game
- Move mouse cursor away. It must not obstruct the game field
- Press `PrintScreen` keyboard button
- Wait a second
- ???
- PROFIT

> Bot may consume `bmp` image path as last command line argument to print all possible solutions
> ```
> cyberbot2077.exe path/to/image.bmp
> ```
## How it works
This bot uses [Tesseract](https://github.com/tesseract-ocr/tesseract) library as OCR engine.
`PrintScreen` keyboard button triggers the bot, and it grabs screenshot
to recognize game field (matrix, conditions, max step count and screen coordinates).
![Recognize example](assets/recognize.jpg)
```
Matrix:
0xbd 0x55 0x55 0x7a 0xe9 0x7a 0x55
0x55 0xe9 0x55 0xbd 0x55 0x55 0xe9
0xe9 0x55 0xbd 0x7a 0x1c 0x55 0x7a
0x55 0x1c 0x55 0x55 0x7a 0x1c 0xff
0x1c 0x7a 0x7a 0x1c 0xbd 0x1c 0xbd
0x1c 0x7a 0xe9 0xff 0x1c 0xe9 0xff
0x1c 0x7a 0x7a 0xbd 0x7a 0x55 0xbd

Conditions:
0x1c 0x7a
0x7a 0x1c 0x1c
0x7a 0x7a 0xbd 0x7a

Steps: 6
```
Then bot finds all solutions for each condition
![All solution](assets/all.jpg)
Tries to merge solutions together to cover as many conditions as possible:
- One solution may be small piece of other
- Ending of one solution may be beginning of other
- 1-3 additional steps are required to merge them
- ...
Then all merged solutions must be finalized:
first step is always vertical and contains any top item.
Current example has 303 completed solutions.
Bot filters the shortest solutions and applies solution,
which covers as many conditions as possible (last conditions have greater score).
![Best solutions](assets/best.jpg)
```
Found 303 solutions
6 best solutions:
Solution #1, conditions: ✔ ✖ ✖, steps: 0xe9 0x1c 0x7a
Solution #2, conditions: ✖ ✔ ✖, steps: 0x7a 0x1c 0x1c
Solution #3, conditions: ✖ ✖ ✔, steps: 0x7a 0x7a 0xbd 0x7a
Solution #4, conditions: ✔ ✔ ✖, steps: 0x7a 0x1c 0x1c 0x7a
Solution #5, conditions: ✔ ✖ ✔, steps: 0x55 0x1c 0x7a 0x7a 0xbd 0x7a
Solution #6, conditions: ✖ ✔ ✔, steps: 0x7a 0x7a 0xbd 0x7a 0x1c 0x1c
```
Last solution `#6` will be applied.
## Download
Download bot [here](https://github.com/ricorodriges/cyberbot2077/releases).
Bot uses `tesseract/tesseract.exe` binary. So please [download binaries](https://github.com/UB-Mannheim/tesseract/wiki) and extract to `tesseract` directory
```
|- tesseract
| |- tesseract.exe
|- cyberbot2077.exe
```
Then run `cyberbot2077.exe` and enjoy!
## Build
Since this project uses [Tesseract](https://github.com/tesseract-ocr/tesseract),
you need to build it yourself or [download binaries](https://github.com/UB-Mannheim/tesseract/wiki).
Bot uses `tesseract/tesseract.exe` binary. So please extract binaries to `tesseract` directory
```
|- tesseract
| |- tesseract.exe
|- src
|- test
|- Cargo.toml
```
Then you may run tests to make sure everything is ok and build
```sh
cargo test
cargo build --release
./target/release/cyberbot2077.exe
```
Binary file added assets/all.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/best.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/preview.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/preview.mp4
Binary file not shown.
Binary file added assets/recognize.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
117 changes: 117 additions & 0 deletions src/img.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
use std::path::Path;

use bmp::{Image, Pixel, px};
use clipboard_win::{formats, get_clipboard};

pub fn load_img_from_clipboard() -> Option<Image> {
return if let Ok(bytes) = get_clipboard(formats::Bitmap) {
if bytes[0x1C] == 32 {
// 32 bits per pixel :(
// `bmp` crate does not support it. I don't want to include other crates.
// convert it to 24 bits per pixel, ignore alpha channel
let width = (bytes[0x12] as u32) | ((bytes[0x13] as u32) << 8);
let height = (bytes[0x16] as u32) | ((bytes[0x17] as u32) << 8);

let mut img = Image::new(width, height);
for y in 0..height {
for x in 0..width {
let b = bytes[(0x36 + (x + y * width) * 4 + 0) as usize];
let g = bytes[(0x36 + (x + y * width) * 4 + 1) as usize];
let r = bytes[(0x36 + (x + y * width) * 4 + 2) as usize];
img.set_pixel(x, height - y - 1, px![r, g, b]);
}
}
Some(img)
} else {
Some(bmp::from_reader(&mut &bytes[..]).unwrap())
}
} else {
None
};
}

pub fn load_img_from_file<P: AsRef<Path>>(path: P) -> Image {
return bmp::open(path).unwrap();
}

/// Clear image colors. Target `color` transforms to about black, other colors are white
pub fn filter_img(img: &mut Image, color: &Pixel, range: u8) {
for (x, y) in img.coordinates() {
let src = img.get_pixel(x, y);
let dest = Pixel::new(dif(src.r, color.r), dif(src.g, color.g), dif(src.b, color.b));

let result = if dest.r <= range && dest.g <= range && dest.b <= range {
dest // about BLACK
} else {
bmp::consts::WHITE
};
img.set_pixel(x, y, result);
}
}

#[inline]
fn dif(a: u8, b: u8) -> u8 {
return (a as i16 - b as i16).abs() as u8;
}

pub fn crop_img(img: &Image, left: u32, top: u32, right: u32, bottom: u32) -> Image {
let mut dest = Image::new(right - left, bottom - top);
for (x, y) in dest.coordinates() {
dest.set_pixel(x, y, img.get_pixel(x + left, y + top));
}
return dest;
}


#[cfg(test)]
mod tests {
use bmp::{Pixel, px};

use crate::img::{crop_img, dif, filter_img};

#[test]
fn test_dif() {
assert_eq!(0, dif(0, 0));
assert_eq!(1, dif(0, 1));
assert_eq!(1, dif(1, 0));
assert_eq!(0, dif(255, 255));
assert_eq!(1, dif(255, 254));
assert_eq!(1, dif(254, 255));
assert_eq!(255, dif(0, 255));
assert_eq!(255, dif(255, 0));
}

#[test]
fn test_filter_img() {
let mut img = bmp::Image::new(4, 4);
img.set_pixel(0, 0, px!(0, 10, 20));
img.set_pixel(1, 0, px!(10, 20, 30));
img.set_pixel(0, 1, px!(15, 25, 35));
img.set_pixel(1, 1, px!(20, 30, 40));

filter_img(&mut img, &px!(10, 20, 30), 5);

assert_eq!(px!(255, 255, 255), img.get_pixel(0, 0));
assert_eq!(px!(0, 0, 0), img.get_pixel(1, 0));
assert_eq!(px!(5, 5, 5), img.get_pixel(0, 1));
assert_eq!(px!(255, 255, 255), img.get_pixel(1, 1));

assert_eq!(4, img.get_width());
assert_eq!(4, img.get_height());
}

#[test]
fn test_crop_img() {
let mut img = bmp::Image::new(4, 4);
img.set_pixel(0, 0, px!(0, 0, 0));
img.set_pixel(1, 0, px!(10, 20, 30));
img.set_pixel(0, 1, px!(0, 0, 0));
img.set_pixel(1, 1, px!(0, 0, 0));

let crop = crop_img(&img, 1, 0, 2, 1);

assert_eq!(1, crop.get_width());
assert_eq!(1, crop.get_height());
assert_eq!(px!(10, 20, 30), crop.get_pixel(0, 0));
}
}
17 changes: 17 additions & 0 deletions src/input.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
use std::thread::sleep;
use std::time::Duration;
use winput::{Action, Button, Input, Mouse};

pub fn click(dx: i32, dy: i32) {
Mouse::move_relative(dx, dy);
sleep(Duration::from_millis(300));

let input = Input::from_button(Button::Left, Action::Press);
winput::send_inputs(&[input]);

sleep(Duration::from_millis(30));

let input = Input::from_button(Button::Left, Action::Release);
winput::send_inputs(&[input]);
sleep(Duration::from_millis(200));
}
Loading

0 comments on commit 7e31c5e

Please sign in to comment.