diff --git a/Cargo.lock b/Cargo.lock index 23c7bbd..a640e1d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -31,9 +31,11 @@ name = "battlefield-anti-afk-script" version = "0.1.0" dependencies = [ "chrono", + "confy", "env_logger", "log", "serde", + "serde_derive", "winapi", ] @@ -55,6 +57,12 @@ version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d" +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + [[package]] name = "cfg-if" version = "1.0.0" @@ -86,6 +94,17 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "confy" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2913470204e9e8498a0f31f17f90a0de801ae92c8c5ac18c49af4819e6786697" +dependencies = [ + "directories", + "serde", + "toml", +] + [[package]] name = "core-foundation-sys" version = "0.8.3" @@ -136,6 +155,27 @@ dependencies = [ "syn", ] +[[package]] +name = "directories" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "551a778172a450d7fc12e629ca3b0428d00f6afa9a43da1b630d54604e97371c" +dependencies = [ + "cfg-if 0.1.10", + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + [[package]] name = "env_logger" version = "0.10.0" @@ -170,6 +210,17 @@ dependencies = [ "libc", ] +[[package]] +name = "getrandom" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", +] + [[package]] name = "hermit-abi" version = "0.2.6" @@ -267,7 +318,7 @@ version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", ] [[package]] @@ -319,6 +370,26 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +dependencies = [ + "getrandom", + "redox_syscall", + "thiserror", +] + [[package]] name = "regex" version = "1.7.0" @@ -362,6 +433,17 @@ version = "1.0.130" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913" +[[package]] +name = "serde_derive" +version = "1.0.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "255abe9a125a985c05190d687b320c12f9b1f0b99445e608c21ba0782c719ad8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "syn" version = "1.0.107" @@ -382,6 +464,26 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "thiserror" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "time" version = "0.1.45" @@ -389,10 +491,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" dependencies = [ "libc", - "wasi", + "wasi 0.10.0+wasi-snapshot-preview1", "winapi", ] +[[package]] +name = "toml" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1333c76748e868a4d9d1017b5ab53171dfd095f70c712fdb4653a406547f598f" +dependencies = [ + "serde", +] + [[package]] name = "unicode-ident" version = "1.0.6" @@ -411,13 +522,19 @@ version = "0.10.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + [[package]] name = "wasm-bindgen" version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "wasm-bindgen-macro", ] diff --git a/Cargo.toml b/Cargo.toml index 2fee2cc..5221cc8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,10 +9,12 @@ authors = ["iiTzArcur "] [dependencies] serde = "1.0.130" +serde_derive = "1.0.130" winapi = { version = "0.3.9", features = ["winuser"] } log = "0.4" env_logger = "0.10.0" chrono = "*" +confy = "0.4.0" [[bin]] diff --git a/config.txt b/config.txt index 7e76677..e8d307d 100644 --- a/config.txt +++ b/config.txt @@ -1,4 +1,5 @@ -group_id = '0fda8e4c-5be3-11eb-b1da-cd4ff7dab605' -game_location = 'F:\Program Files (x86)\Origin Games\Battlefield V\bfv.exe' -hostname = 'Jobse-Laptop' -allow_shutdown = false +send_messages = false +message = 'Join our discord, we are always recruiting: discord.gg/BoB' +message_start_time_utc = '12:00' +message_stop_time_utc = '23:00' +message_timeout_mins = 8 diff --git a/src/actions.rs b/src/actions.rs new file mode 100644 index 0000000..b002a08 --- /dev/null +++ b/src/actions.rs @@ -0,0 +1,141 @@ +use std::ffi::OsStr; +use std::iter::once; +use std::os::windows::prelude::OsStrExt; +use std::ptr; +use std::thread::sleep; +use std::time::Duration; +use winapi::shared::windef::HWND__; +use winapi::um::winuser::{FindWindowW, SetForegroundWindow, ShowWindow, SendMessageW, GetForegroundWindow}; + +use crate::{send_keys, structs}; +use crate::chars::{DXCode, char_to_dxcodes}; + +fn make_l_param(lo_word: i32, hi_word: i32) -> i32 { + return (hi_word << 16) | (lo_word & 0xffff); +} + +pub fn anti_afk(game_name: &str, mut run_once_no_game: bool) -> bool { + let game_info = is_running(game_name); + if game_info.is_running { + unsafe { + let current_forground_window = GetForegroundWindow(); + let l_param = make_l_param(20, 20); + SendMessageW(game_info.game_process, 0x201, 0, l_param as isize); + SendMessageW(game_info.game_process, 0x202, 0, l_param as isize); + SetForegroundWindow(current_forground_window); + // reset no game check + run_once_no_game = true; + } + log::info!("Running anti-idle for {}.", game_name); + } else { + if run_once_no_game { + log::info!("No game found, idleing..."); + run_once_no_game = false; + } + } + return run_once_no_game; +} + +fn message_action(cfg: &structs::SeederConfig) { + unsafe { + send_keys::key_enter(0x24, 8); + sleep(Duration::from_millis(800)); + let mut message: Vec = Vec::new(); + for char in cfg.message.chars() { + match char_to_dxcodes(char) { + Some(dx) => message.push(dx), + None => {}, + } + } + send_keys::send_string(message); + sleep(Duration::from_millis(100)); + send_keys::key_enter(0x1C, 8); + sleep(Duration::from_millis(100)); + } +} + +fn bf2042_message_action(cfg: &structs::SeederConfig) { + unsafe { + // println!("Open pause menu"); + send_keys::key_enter(0x01, 80); // ESC + sleep(Duration::from_millis(800)); + // println!("Most left menu"); + send_keys::spam_keys(0x10, 80, 3); // Q + sleep(Duration::from_millis(800)); + // println!("Top of list"); + send_keys::spam_keys(0xD0, 80, 5); // DOWN + sleep(Duration::from_millis(800)); + // println!("from bottom second item"); + send_keys::key_enter(0xC8, 80); // UP + sleep(Duration::from_millis(800)); + // println!("Click!"); + send_keys::key_enter(0x39, 80); // SPACE + sleep(Duration::from_millis(800)); + // println!("broadcast menu"); + send_keys::key_enter(0x39, 80); // SPACE + sleep(Duration::from_millis(800)); + + // println!("fill in"); + send_keys::spam_keys(0xC8, 8, 2); // UP + sleep(Duration::from_millis(800)); + // println!("fill mode"); + send_keys::key_enter(0x39, 80); // SPACE + sleep(Duration::from_millis(800)); + let mut message: Vec = Vec::new(); + for char in cfg.message.chars() { + match char_to_dxcodes(char) { + Some(dx) => message.push(dx), + None => {}, + } + } + send_keys::send_string(message); + sleep(Duration::from_millis(800)); + // println!("done with message"); + send_keys::key_enter(0x1C, 80); // ENTER + sleep(Duration::from_millis(800)); + // println!("done button"); + send_keys::key_enter(0xD0, 80); // DOWN + sleep(Duration::from_millis(800)); + // println!("broadcast!"); + send_keys::key_enter(0x39, 80); // SPACE + sleep(Duration::from_millis(800)); + + // println!("Back to spawn screen"); + send_keys::spam_keys(0x01, 8, 2); // ESC + sleep(Duration::from_millis(800)); + } +} + +// https://gist.github.com/dretax/fe37b8baf55bc30e9d63 +pub fn send_message(cfg: &structs::SeederConfig, game_name: &str) { + let game_info = is_running(game_name); + if game_info.is_running { + unsafe { + SetForegroundWindow(game_info.game_process); + ShowWindow(game_info.game_process, 9); + sleep(Duration::from_millis(1808)); + if game_name == "" { + bf2042_message_action(cfg); + } else { + message_action(cfg); + } + ShowWindow(game_info.game_process, 6); + } + } +} + + +pub fn is_running(game_name: &str) -> structs::GameInfo { + unsafe { + let window: Vec = OsStr::new(game_name) + .encode_wide() + .chain(once(0)) + .collect(); + let window_handle = FindWindowW(std::ptr::null_mut(), window.as_ptr()); + let no_game: *mut HWND__ = ptr::null_mut(); + structs::GameInfo { + is_running: window_handle != no_game, + game_process: window_handle, + } + } +} \ No newline at end of file diff --git a/src/bf1.rs b/src/bf1.rs index 8f6013d..030b652 100644 --- a/src/bf1.rs +++ b/src/bf1.rs @@ -1,4 +1,8 @@ mod shared_main; +mod chars; +mod send_keys; +mod actions; +mod structs; fn main() { shared_main::anti_afk_runner("Battlefield™ 1"); diff --git a/src/bf2042.rs b/src/bf2042.rs index 8f6013d..27ef5d5 100644 --- a/src/bf2042.rs +++ b/src/bf2042.rs @@ -1,5 +1,9 @@ mod shared_main; +mod chars; +mod send_keys; +mod actions; +mod structs; fn main() { - shared_main::anti_afk_runner("Battlefield™ 1"); + shared_main::anti_afk_runner("Battlefield™ 2042"); } \ No newline at end of file diff --git a/src/bfv.rs b/src/bfv.rs index a7903c4..3d54689 100644 --- a/src/bfv.rs +++ b/src/bfv.rs @@ -1,5 +1,9 @@ mod shared_main; +mod chars; +mod send_keys; +mod actions; +mod structs; fn main() { - shared_main::anti_afk_runner("Battlefield™ 2042"); + shared_main::anti_afk_runner("Battlefield™ V"); } \ No newline at end of file diff --git a/src/chars.rs b/src/chars.rs new file mode 100644 index 0000000..62bd835 --- /dev/null +++ b/src/chars.rs @@ -0,0 +1,90 @@ +const CHAR_MAPPING: [u16; 47] = [ + 0x33, //, + 0x0C, //- + 0x34, //. + 0x35, //\/ + 0x0B, //0 + 0x02, //1 + 0x03, //2 + 0x04, //3 + 0x05, //4 + 0x06, //5 + 0x07, //6 + 0x08, //7 + 0x09, //8 + 0x0A, //9 + 0x0, + 0x27, //; + 0x0, + 0x0D, //= + 0x0, + 0x0, + 0x0, + 0x1E, //A + 0x30, //B + 0x2E, //C + 0x20, //D + 0x12, //E + 0x21, //F + 0x22, //G + 0x23, //H + 0x17, //I + 0x24, //J + 0x25, //K + 0x26, //L + 0x32, //M + 0x31, //N + 0x18, //O + 0x19, //P + 0x10, //Q + 0x13, //R + 0x1F, //S + 0x14, //T + 0x16, //U + 0x2F, //V + 0x11, //W + 0x2D, //X + 0x15, //Y + 0x2C, //Z +]; + +#[derive(Debug)] +pub enum DXCode { + Symbol(u16), + Shifted(u16) +} + +/** + Convert ASCII char into DirectX key code + */ +pub fn char_to_dxcodes(c: char) -> Option { + let mut c_u8 = c as u8; + + if c.is_ascii_lowercase() { + c_u8 &= 0xdf; + } + + if c.is_ascii_whitespace() { + return Some(DXCode::Symbol(0x39)); + } + + if c == ":".chars().next().unwrap() { + return Some(DXCode::Shifted(0x27)); + } + + if c_u8 < 0x5B && c_u8 > 0x2B { + let index = c_u8 - 0x2C; + let code = CHAR_MAPPING[index as usize]; + // println!("{} {}", index, code); + if code == 0x0 { + None + } else if c.is_ascii_uppercase() { + // Press SHIFT + Some(DXCode::Shifted(code)) + } else { + Some(DXCode::Symbol(code)) + } + } else { + None + } +} \ No newline at end of file diff --git a/src/send_keys.rs b/src/send_keys.rs new file mode 100644 index 0000000..f3d9bf4 --- /dev/null +++ b/src/send_keys.rs @@ -0,0 +1,67 @@ +use std::{mem, thread::sleep, time::Duration}; +use winapi::um::winuser::{INPUT, INPUT_KEYBOARD, KEYEVENTF_EXTENDEDKEY, KEYEVENTF_KEYUP, KEYEVENTF_SCANCODE, SendInput}; + +use crate::chars::DXCode; +// key codes: https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes + +unsafe fn create_input(key_code: u16, wvk: u16, flags: u32) -> INPUT { + let mut input = mem::zeroed::(); + input.type_ = INPUT_KEYBOARD; + let mut ki = input.u.ki_mut(); + ki.wVk = wvk; + ki.wScan = key_code; + ki.dwExtraInfo = 0; + ki.dwFlags = flags; + ki.time = 0; + input +} + +unsafe fn key_down(key_code: u16) { + let mut input = create_input(key_code, 0, KEYEVENTF_SCANCODE); + SendInput(1, &mut input, mem::size_of::() as i32); +} + +unsafe fn key_up(key_code: u16) { + let mut input = create_input(key_code, 0, KEYEVENTF_KEYUP); + SendInput(1, &mut input, mem::size_of::() as i32); +} + +unsafe fn special_down(key_code: u16) { + let mut input = create_input(0, key_code, KEYEVENTF_EXTENDEDKEY); + SendInput(1, &mut input, mem::size_of::() as i32); +} + +unsafe fn special_up(key_code: u16) { + let mut input = create_input(0, key_code, KEYEVENTF_EXTENDEDKEY|KEYEVENTF_KEYUP); + SendInput(1, &mut input, mem::size_of::() as i32); +} + +pub unsafe fn spam_keys(key_code: u16, timeout: u64, amount: i16) { + for _ in 0..amount { + key_enter(key_code, timeout); + sleep(Duration::from_millis(timeout)); + } +} + +pub unsafe fn key_enter(key_code: u16, timeout: u64) { + key_down(key_code); + sleep(Duration::from_millis(timeout)); + key_up(key_code); +} + +pub unsafe fn send_string(keys: Vec) { + for key in keys { + match key { + DXCode::Shifted(code)=>{ + sleep(Duration::from_millis(10)); + special_down(0x10); + sleep(Duration::from_millis(10)); + key_enter(code, 8); + sleep(Duration::from_millis(10)); + special_up(0x10); + sleep(Duration::from_millis(10)); + }, + DXCode::Symbol(code) => key_enter(code, 8), + } + } +} \ No newline at end of file diff --git a/src/shared_main.rs b/src/shared_main.rs index 4a7e3fd..dc3e57f 100644 --- a/src/shared_main.rs +++ b/src/shared_main.rs @@ -1,25 +1,18 @@ -use std::ffi::OsStr; -use std::iter::once; -use std::os::windows::prelude::OsStrExt; -use std::ptr; use std::{ - thread::sleep, + sync::{atomic, Arc}, + thread::{sleep}, time::Duration, }; -use winapi::shared::windef::HWND__; -use winapi::um::winuser::{FindWindowW, SetForegroundWindow, SendMessageW, GetForegroundWindow}; use std::io::Write; use chrono::Local; use env_logger::Builder; use log::LevelFilter; - -struct GameInfo { - is_running: bool, - game_process: *mut HWND__ -} +use crate::actions; +use crate::structs; pub fn anti_afk_runner(game_name: &str) { let mut run_once_no_game = true; + let message_timeout = Arc::new(atomic::AtomicU32::new(0)); Builder::new() .format(|buf, record| { @@ -32,44 +25,39 @@ pub fn anti_afk_runner(game_name: &str) { }) .filter(None, LevelFilter::Info) .init(); - + log::info!("Script started."); - loop { - let game_info = is_running(game_name); - if game_info.is_running { - unsafe { - let current_forground_window = GetForegroundWindow(); - let l_param = make_l_param(20, 20); - SendMessageW(game_info.game_process, 0x201, 0, l_param as isize); - SendMessageW(game_info.game_process, 0x202, 0, l_param as isize); - SetForegroundWindow(current_forground_window); - // reset no game check - run_once_no_game = true; + let cfg: structs::SeederConfig = match confy::load_path("config.txt") { + Ok(config) => config, + Err(e) => { + println!("error in config.txt: {}", e); + println!("changing back to default.."); + structs::SeederConfig { + send_messages: false, + message: "Join our discord, we are always recruiting: discord.gg/BoB".into(), + message_start_time_utc: "12:00".into(), + message_stop_time_utc: "23:00".into(), + message_timeout_mins: 8, } - log::info!("Running anti-idle for {}.", game_name); + } + }; + confy::store_path("config.txt", cfg.clone()).unwrap(); + + log::info!("Config loaded."); + + loop { + let timeout = message_timeout.load(atomic::Ordering::Relaxed); + if (timeout >= (cfg.message_timeout_mins / 2)) && cfg.send_messages { + log::info!("sending message..."); + actions::send_message(&cfg, game_name); + message_timeout.store(0, atomic::Ordering::Relaxed); } else { - if run_once_no_game { - log::info!("No game found, idleing..."); - run_once_no_game = false; + run_once_no_game = actions::anti_afk(game_name, run_once_no_game); + if cfg.send_messages { + message_timeout.store(timeout + 1, atomic::Ordering::Relaxed); } } sleep(Duration::from_secs(120)); }; -} - -fn make_l_param(lo_word: i32, hi_word: i32) -> i32 { - return (hi_word << 16) | (lo_word & 0xffff); -} - -fn is_running(game_name: &str) -> GameInfo { - unsafe { - let window: Vec = OsStr::new(game_name) - .encode_wide() - .chain(once(0)) - .collect(); - let window_handle = FindWindowW(std::ptr::null_mut(), window.as_ptr()); - let no_game: *mut HWND__ = ptr::null_mut(); - GameInfo{ is_running: window_handle != no_game, game_process: window_handle } - } } \ No newline at end of file diff --git a/src/structs.rs b/src/structs.rs new file mode 100644 index 0000000..2c737d5 --- /dev/null +++ b/src/structs.rs @@ -0,0 +1,29 @@ +use serde_derive::{Deserialize, Serialize}; +use winapi::shared::windef::HWND__; + +#[derive(Serialize, Deserialize, Clone)] +pub struct SeederConfig { + pub send_messages: bool, + pub message: String, + pub message_start_time_utc: String, + pub message_stop_time_utc: String, + pub message_timeout_mins: u32, +} + +pub struct GameInfo { + pub is_running: bool, + pub game_process: *mut HWND__, +} + +/// `SeederConfig` implements `Default` +impl ::std::default::Default for SeederConfig { + fn default() -> Self { + Self { + send_messages: false, + message: "Join our discord, we are always recruiting: discord.gg/BoB".into(), + message_start_time_utc: "12:00".into(), + message_stop_time_utc: "23:00".into(), + message_timeout_mins: 8, + } + } +}