diff --git a/Readme.md b/Readme.md index 402fec0..650990e 100644 --- a/Readme.md +++ b/Readme.md @@ -1,21 +1,29 @@ -# Battlefield anti-idle -This tool will prevent you from getting kicked for idle for Battlefield 4, 1, 5 and 2042, you only have to run it and it will work even if the game is minimized. -It can also send messages on specified times based on what you set in the config. - -download it here: https://github.com/community-network/battlefield-anti-idle/releases/latest - -Config file -```bash -# It will autogenerate one of these files when you run the script when it doesn't exist already. - -# It will send messages based on timeout when set to true -send_messages = false -# Message it will send -message = 'Join our discord, we are always recruiting: discord.gg/BoB' -# When it will start sending messages, based on the UTC timezone -message_start_time_utc = '12:00' -# When it will stop sending messages, based on the UTC timezone -message_stop_time_utc = '23:00' -# Timeout used when sending messages -message_timeout_mins = 8 -``` +# Battlefield anti-idle + +This tool will prevent you from getting kicked for idle for Battlefield 4, 1, 5 and 2042, you only have to run it and it will work even if the game is minimized. +It can also send messages on specified times based on what you set in the config. + +download it here: https://github.com/community-network/battlefield-anti-idle/releases/latest + +Config file + +```bash +# It will autogenerate one of these files when you run the script when it doesn't exist already. + +# It will send messages based on timeout when set to true +send_messages = false +# Message it will send +messages = [ + 'test1', + 'test2', + 'test3', +] +# In which chat it has to send the messages (can be 'Public', 'Team' or 'Squad') +chat_type = 'Public' +# When it will start sending messages, based on the UTC timezone +message_start_time_utc = '12:00' +# When it will stop sending messages, based on the UTC timezone +message_stop_time_utc = '23:00' +# Timeout used when sending messages +message_timeout_mins = 8 +``` diff --git a/config.txt b/config.txt index e8d307d..d41af44 100644 --- a/config.txt +++ b/config.txt @@ -1,5 +1,10 @@ -send_messages = false -message = 'Join our discord, we are always recruiting: discord.gg/BoB' +send_messages = true +messages = [ + 'test1', + 'test2', + 'test3', +] +chat_type = 'Public' message_start_time_utc = '12:00' message_stop_time_utc = '23:00' -message_timeout_mins = 8 +message_timeout_mins = 0 diff --git a/src/actions.rs b/src/actions.rs index 6b474d9..28ea4b5 100644 --- a/src/actions.rs +++ b/src/actions.rs @@ -1,130 +1,156 @@ -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, GetForegroundWindow, SendMessageW, SetForegroundWindow, ShowWindow, -}; - -use crate::chars::{char_to_dxcodes, DXCode}; -use crate::{send_keys, structs}; - -fn make_l_param(lo_word: i32, hi_word: i32) -> i32 { - (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; - } - run_once_no_game -} - -fn message_action(cfg: &structs::SeederConfig) { - unsafe { - send_keys::key_enter(0x24, 50); // J - sleep(Duration::from_secs(3)); - let mut message: Vec = Vec::new(); - for char in cfg.message.chars() { - if let Some(dx) = char_to_dxcodes(char) { - message.push(dx) - } - } - send_keys::send_string(message); - sleep(Duration::from_secs(1)); - send_keys::key_enter(0x1C, 8); // ENTER - sleep(Duration::from_secs(1)); - } -} - -fn bf2042_message_action(cfg: &structs::SeederConfig) { - unsafe { - // println!("Open pause menu"); - send_keys::key_enter(0x01, 80); // ESC - sleep(Duration::from_secs(1)); - // println!("Most left menu"); - send_keys::spam_keys(0x10, 80, 3); // Q - sleep(Duration::from_secs(1)); - // println!("Top of list"); - send_keys::spam_keys(0xD0, 80, 5); // DOWN - sleep(Duration::from_secs(1)); - // println!("from bottom second item"); - send_keys::key_enter(0xC8, 80); // UP - sleep(Duration::from_secs(1)); - // println!("Click!"); - send_keys::key_enter(0x39, 80); // SPACE - sleep(Duration::from_secs(1)); - // println!("Move to top item for broadcast menu!"); - send_keys::spam_keys(0xC8, 80, 5); // UP - sleep(Duration::from_secs(1)); - // println!("broadcast menu"); - send_keys::key_enter(0x39, 80); // SPACE - sleep(Duration::from_secs(1)); - // println!("fill in"); - send_keys::spam_keys(0xC8, 8, 2); // UP - sleep(Duration::from_secs(1)); - // println!("fill mode"); - send_keys::key_enter(0x39, 80); // SPACE - sleep(Duration::from_secs(1)); - let mut message: Vec = Vec::new(); - for char in cfg.message.chars() { - if let Some(dx) = char_to_dxcodes(char) { - message.push(dx) - } - } - send_keys::send_string(message); - sleep(Duration::from_secs(1)); - // println!("done with message"); - send_keys::key_enter(0x1C, 80); // ENTER - sleep(Duration::from_secs(1)); - // println!("Back to spawn screen"); - send_keys::spam_keys(0x01, 8, 3); // ESC - sleep(Duration::from_secs(1)); - } -} - -// 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 == "Battlefield™ 2042" { - 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, - } - } -} +use std::ffi::OsStr; +use std::iter::once; +use std::os::windows::prelude::OsStrExt; +use std::ptr; +use std::sync::atomic::{self, AtomicU32}; +use std::sync::Arc; +use std::thread::sleep; +use std::time::Duration; +use winapi::shared::windef::HWND__; +use winapi::um::winuser::{ + FindWindowW, GetForegroundWindow, SendMessageW, SetForegroundWindow, ShowWindow, +}; + +use crate::chars::{char_to_dxcodes, DXCode}; +use crate::{send_keys, structs}; + +fn make_l_param(lo_word: i32, hi_word: i32) -> i32 { + (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; + } + run_once_no_game +} + +fn message_action(message_to_send: &str, open_chat_key: u16) { + unsafe { + send_keys::key_enter(open_chat_key, 50); // J + sleep(Duration::from_secs(3)); + let mut current_message: Vec = Vec::new(); + for char in message_to_send.chars() { + if let Some(dx) = char_to_dxcodes(char) { + current_message.push(dx) + } + } + send_keys::send_string(current_message); + sleep(Duration::from_secs(1)); + send_keys::key_enter(0x1C, 8); // ENTER + sleep(Duration::from_secs(1)); + } +} + +fn bf2042_message_action(message_to_send: &str) { + unsafe { + // println!("Open pause menu"); + send_keys::key_enter(0x01, 80); // ESC + sleep(Duration::from_secs(1)); + // println!("Most left menu"); + send_keys::spam_keys(0x10, 80, 3); // Q + sleep(Duration::from_secs(1)); + // println!("Top of list"); + send_keys::spam_keys(0xD0, 80, 5); // DOWN + sleep(Duration::from_secs(1)); + // println!("from bottom second item"); + send_keys::key_enter(0xC8, 80); // UP + sleep(Duration::from_secs(1)); + // println!("Click!"); + send_keys::key_enter(0x39, 80); // SPACE + sleep(Duration::from_secs(1)); + // println!("Move to top item for broadcast menu!"); + send_keys::spam_keys(0xC8, 80, 5); // UP + sleep(Duration::from_secs(1)); + // println!("broadcast menu"); + send_keys::key_enter(0x39, 80); // SPACE + sleep(Duration::from_secs(1)); + // println!("fill in"); + send_keys::spam_keys(0xC8, 8, 2); // UP + sleep(Duration::from_secs(1)); + // println!("fill mode"); + send_keys::key_enter(0x39, 80); // SPACE + sleep(Duration::from_secs(1)); + let mut current_message: Vec = Vec::new(); + for char in message_to_send.chars() { + if let Some(dx) = char_to_dxcodes(char) { + current_message.push(dx) + } + } + send_keys::send_string(current_message); + sleep(Duration::from_secs(1)); + // println!("done with message"); + send_keys::key_enter(0x1C, 80); // ENTER + sleep(Duration::from_secs(1)); + // println!("Back to spawn screen"); + send_keys::spam_keys(0x01, 8, 3); // ESC + sleep(Duration::from_secs(1)); + } +} + +// https://gist.github.com/dretax/fe37b8baf55bc30e9d63 +pub fn send_message( + cfg: &structs::SeederConfig, + game_name: &str, + current_message_id: &Arc, +) { + let game_info = is_running(game_name); + if game_info.is_running { + let mut message_id = current_message_id.load(atomic::Ordering::Relaxed); + let current_message: &String = &cfg.messages[message_id as usize]; + + unsafe { + SetForegroundWindow(game_info.game_process); + ShowWindow(game_info.game_process, 9); + sleep(Duration::from_millis(1808)); + + match cfg.chat_type { + structs::ChatType::Public => { + if game_name == "Battlefield™ 2042" { + bf2042_message_action(current_message); + } else { + message_action(current_message, 0x24); + } + } + structs::ChatType::Team => message_action(current_message, 0x25), + structs::ChatType::Squad => message_action(current_message, 0x26), + } + + ShowWindow(game_info.game_process, 6); + } + + if message_id + 1 >= cfg.messages.len() as u32 { + message_id = 0; + } else { + message_id += 1; + } + + // save + current_message_id.store(message_id, atomic::Ordering::Relaxed); + } +} + +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, + } + } +} diff --git a/src/shared_main.rs b/src/shared_main.rs index 4d60097..5f9cb38 100644 --- a/src/shared_main.rs +++ b/src/shared_main.rs @@ -1,63 +1,67 @@ -use std::{ - sync::{atomic, Arc}, - thread::{sleep}, - time::Duration, -}; -use std::io::Write; -use chrono::Local; -use env_logger::Builder; -use log::LevelFilter; -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| { - writeln!(buf, - "{} [{}] - {}", - Local::now().format("%Y-%m-%dT%H:%M:%S"), - record.level(), - record.args() - ) - }) - .filter(None, LevelFilter::Info) - .init(); - - log::info!("Script started."); - - 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, - } - } - }; - 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)) && cfg.send_messages { - log::info!("sending message..."); - actions::send_message(&cfg, game_name); - message_timeout.store(0, atomic::Ordering::Relaxed); - } else { - 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(60)); - }; -} \ No newline at end of file +use crate::actions; +use crate::structs; +use crate::structs::ChatType; +use chrono::Local; +use env_logger::Builder; +use log::LevelFilter; +use std::io::Write; +use std::{ + sync::{atomic, Arc}, + thread::sleep, + time::Duration, +}; + +pub fn anti_afk_runner(game_name: &str) { + let mut run_once_no_game = true; + let message_timeout = Arc::new(atomic::AtomicU32::new(0)); + let current_message_id = Arc::new(atomic::AtomicU32::new(0)); + + Builder::new() + .format(|buf, record| { + writeln!( + buf, + "{} [{}] - {}", + Local::now().format("%Y-%m-%dT%H:%M:%S"), + record.level(), + record.args() + ) + }) + .filter(None, LevelFilter::Info) + .init(); + + log::info!("Script started."); + + 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, + messages: vec!["Join our discord, we are always recruiting: discord.gg/BoB".into()], + chat_type: ChatType::Public, + message_start_time_utc: "12:00".into(), + message_stop_time_utc: "23:00".into(), + message_timeout_mins: 8, + } + } + }; + 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)) && cfg.send_messages { + log::info!("sending message..."); + actions::send_message(&cfg, game_name, ¤t_message_id); + message_timeout.store(0, atomic::Ordering::Relaxed); + } else { + 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(60)); + } +} diff --git a/src/structs.rs b/src/structs.rs index 2c737d5..c72fa1c 100644 --- a/src/structs.rs +++ b/src/structs.rs @@ -1,29 +1,38 @@ -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, - } - } -} +use serde_derive::{Deserialize, Serialize}; +use winapi::shared::windef::HWND__; + +#[derive(Serialize, Deserialize, Clone)] +pub enum ChatType { + Public, + Team, + Squad, +} + +#[derive(Serialize, Deserialize, Clone)] +pub struct SeederConfig { + pub send_messages: bool, + pub messages: Vec, + pub chat_type: ChatType, + 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, + messages: vec!["Join our discord, we are always recruiting: discord.gg/BoB".into()], + chat_type: ChatType::Public, + message_start_time_utc: "12:00".into(), + message_stop_time_utc: "23:00".into(), + message_timeout_mins: 8, + } + } +} diff --git a/src/test_msg.rs b/src/test_msg.rs index 0e5c060..4f80002 100644 --- a/src/test_msg.rs +++ b/src/test_msg.rs @@ -1,10 +1,12 @@ mod actions; mod chars; mod send_keys; -mod shared_main; mod structs; +use crate::structs::ChatType; +use std::sync::{atomic, Arc}; fn main() { + let current_message_id = Arc::new(atomic::AtomicU32::new(0)); let cfg: structs::SeederConfig = match confy::load_path("config.txt") { Ok(config) => config, Err(e) => { @@ -12,7 +14,8 @@ fn main() { println!("changing back to default.."); structs::SeederConfig { send_messages: true, - message: "testmessage1".into(), + messages: vec!["testmessage1".into()], + chat_type: ChatType::Public, message_start_time_utc: "12:00".into(), message_stop_time_utc: "23:00".into(), message_timeout_mins: 8, @@ -20,5 +23,5 @@ fn main() { } }; confy::store_path("config.txt", cfg.clone()).unwrap(); - actions::send_message(&cfg, "Battlefield™ 2042"); + actions::send_message(&cfg, "Battlefield™ 2042", ¤t_message_id); }