< 返回版块

zhuxiujia 发表于 2020-07-13 01:02

Tags:硬核教程 - 使用Rust编写网游FPS外挂辅助

优势: Rust没有GC 效率和C++一样快 本贴子主要用于观摩和学习调用windows api,禁止用于某些用途

已测试游戏(cf) 注意关闭防火墙

目标 - 扫描屏幕敌人出现红色的名字,达到自动开枪的目的。 效果 - 爆破模式和狙击枪使用的时候 瞬间秒杀敌人。程序的反应时间为1秒/60fps=0.01秒。这性能保证了 只要准心略过敌人身体,永远都是你先开第一枪

具体步骤: 1屏幕截BitMap图片数据(本教程屏幕尺寸为1920*1080.具体屏幕尺寸需要自行测量),找图找色,找名字颜色 2根据扫描到的RGB颜色值矩阵找红色+黑色描边名字 3模拟鼠标按下和抬起

用到的库:

winapi-rs

用到的windows API:

mouse_event //模拟鼠标点击 CreateCompatibleBitmap //创建位图 SelectObject BitBlt GetDIBits //取得屏幕图片 DeleteDC
ReleaseDC //释放句柄 DeleteObject

#[cfg(windows)]
extern crate winapi;

use std::ffi::CString;
use std::io::{Bytes, Error};
use std::ptr::null_mut;
use std::thread::sleep;
use std::time::SystemTime;

use winapi::_core::mem::size_of;
use winapi::_core::str::Chars;
use winapi::_core::time::Duration;
use winapi::ctypes::{c_char, c_void};
use winapi::shared::minwindef::BYTE;
use winapi::shared::windef::{HBITMAP, HBITMAP__, HGDIOBJ, HWND, POINT, RECT, SIZE};
use winapi::um::errhandlingapi::GetLastError;
use winapi::um::wingdi;
use winapi::um::wingdi::{BI_RGB, BitBlt, BITMAPINFO, BITMAPINFOHEADER, CreateCompatibleBitmap, CreateCompatibleDC, DeleteDC, DeleteObject, DIB_RGB_COLORS, GetBValue, GetDIBits, GetGValue, GetPixel, GetRValue, GetTextColor, RGBQUAD, SelectObject, SRCCOPY};
use winapi::um::winuser::{GetCursorPos, GetDC, GetDesktopWindow, GetTopWindow, GetWindowDC, GetWindowRect, mouse_event, MOUSEEVENTF_LEFTDOWN, MOUSEEVENTF_LEFTUP, ReleaseDC};

use crate::util::{click_mouse_event, click_send_input, is_red, pixel_to_rgb, rgb_is_black, rgb_is_red, write_file};

pub mod util;


/// step: 步长
#[cfg(windows)]
unsafe fn find_color(left: u32, top: u32, right: u32, bottom: u32, step: usize) -> bool {
    let h_screen_dc = GetDC(null_mut());//获得屏幕的HDC
    let mem_dc = CreateCompatibleDC(h_screen_dc);//创建一个内存中的DC
    let mut rect: RECT = RECT {
        left: left as i32,
        top: top as i32,
        right: right as i32,
        bottom: bottom as i32,
    };
    //获取屏幕尺寸
    // GetWindowRect(h_desk_top_wnd, &mut rect);
    let mut screensize = SIZE { cx: 0, cy: 0 };
    screensize.cx = rect.right - rect.left;
    screensize.cy = rect.bottom - rect.top;

    let mut h_bitmap: HBITMAP;
    h_bitmap = CreateCompatibleBitmap(h_screen_dc, screensize.cx, screensize.cy);
    let h_old_bmp = SelectObject(mem_dc, h_bitmap as HGDIOBJ);
    let bit_blt_success = BitBlt(mem_dc, 0, 0, screensize.cx, screensize.cy, h_screen_dc, rect.left, rect.top, SRCCOPY);
    if bit_blt_success as i32 == 0 {
        //注意释放资源
        DeleteDC(h_screen_dc);
        DeleteDC(mem_dc);
        ReleaseDC(h_screen_dc as HWND, mem_dc);
        ReleaseDC(null_mut(), h_screen_dc);
        DeleteObject(mem_dc as HGDIOBJ);
        DeleteObject(h_old_bmp);
        DeleteObject(h_bitmap as HGDIOBJ);
        return false;
    }
    let mut bit_info: BITMAPINFO = BITMAPINFO {
        bmiHeader: BITMAPINFOHEADER {
            biSize: size_of::<BITMAPINFOHEADER>() as u32,
            biWidth: screensize.cx,
            biHeight: screensize.cy,
            biPlanes: 1,
            biBitCount: 32,
            biCompression: BI_RGB,
            biSizeImage: 0,
            biXPelsPerMeter: 0,
            biYPelsPerMeter: 0,
            biClrUsed: 0,
            biClrImportant: 0,
        },
        bmiColors: [RGBQUAD {
            rgbBlue: 0,
            rgbGreen: 0,
            rgbRed: 0,
            rgbReserved: 0,
        }; 1],
    };

    let mut is_find_color = false;

    let mut result = 0;
    //第一次调用GetDIBits获得图片的大小
    result = GetDIBits(mem_dc, h_bitmap, 0, screensize.cy as u32, null_mut(), &mut bit_info, DIB_RGB_COLORS);
    if result != 0 {
        //第二次调用GetDIBits取图片流数据
        // 位图信息及调色板大小
        let info_size = bit_info.bmiHeader.biSize + bit_info.bmiHeader.biClrUsed * size_of::<RGBQUAD>() as u32;
        //缓冲区大小
        let size: usize = bit_info.bmiHeader.biSizeImage as usize + info_size as usize;

        //缓存
        let mut buffer = vec![0u8; size];
        let ptr = buffer.as_mut_ptr().cast();
        result = GetDIBits(mem_dc, h_bitmap, 0, screensize.cy as u32, ptr, &mut bit_info, DIB_RGB_COLORS);

        if result == 0 {
            println!("2设置图片信息出错");
            is_find_color = false;
        }else{
            let mut have_black = false;
            let mut last_i = 0;

            let len = size / 4;
            for i in 0..len {
                if step != 0 && i % step != 0 {
                    continue;
                }
                let rv = buffer[(size - i * 4) - 2] as i32;
                let gv = buffer[(size - i * 4) - 3] as i32;
                let bv = buffer[(size - i * 4) - 4] as i32;

                if rgb_is_black(rv, gv, bv) {
                    have_black = true;
                    last_i = i;
                }

                if rgb_is_red(rv, gv, bv) && have_black && last_i + 1 == i {
                    //println!("find  red   r:{},g:{},b:{}", rv, gv, bv);
                    is_find_color = true;
                    break;
                }
            }
        }
    } else {
        let e = GetLastError();
        println!("1设置图片信息出错,code: {}", e);
    }
    //gc
    DeleteDC(h_screen_dc);
    DeleteDC(mem_dc);
    ReleaseDC(h_screen_dc as HWND, mem_dc);
    ReleaseDC(null_mut(), h_screen_dc);
    DeleteObject(mem_dc as HGDIOBJ);
    DeleteObject(h_old_bmp);
    DeleteObject(h_bitmap as HGDIOBJ);
    return is_find_color;
}


#[cfg(windows)]
unsafe fn print_message() {
    // let pixel=  GetPixel(null_mut(),0,512);
    // println!("{}",pixel);
    let mut p = POINT {
        x: 0,
        y: 0,
    };
    let hDeskTopWnd = GetDesktopWindow();//获得屏幕的HWND
    let hScreenDC = GetDC(hDeskTopWnd);//获得屏幕的HDC
    loop {
        GetCursorPos(&mut p);
        println!("point: {},{}", &p.x, &p.y);

        let pixel = GetPixel(hScreenDC, p.x, p.y);
        println!("pixel: {}", pixel);

        pixel_to_rgb(pixel as u32);

        sleep(Duration::from_secs(1));
    }
}



unsafe fn loop_find_color() {
    loop {
        find_color(0, 0, 10, 100, 0);
    }
}


unsafe fn loop_find_cf_color() {
    println!("done");
    loop {
        //let find =  find_color(0, 0, 10, 100, 0);;
        let find = find_color(918, 570, 918 + 100, 570 + 57, 0);
        if find {
            click_mouse_event(0, 0);
            println!("click");
        }
    }
}

fn main() {
    println!("now start!");
    //bench_rate();
    unsafe { loop_find_cf_color(); }
}

封装util.rs


use std::fs::File;
use std::io::Write;
use std::thread::sleep;
use std::time::Duration;

use winapi::_core::mem::size_of;
use winapi::um::winuser::{GetCursorPos, GetDC, INPUT, INPUT_MOUSE, INPUT_u, mouse_event, MOUSEEVENTF_ABSOLUTE, MOUSEEVENTF_LEFTDOWN, MOUSEEVENTF_LEFTUP, MOUSEEVENTF_MOVE, MOUSEINPUT, SendInput};

pub unsafe fn click_send_input(dx: u32, dy: u32) {
    let mut input = INPUT { type_: INPUT_MOUSE, u: INPUT_u::default() };
    let mi = input.u.mi_mut();
    mi.dx = 0;
    mi.dy = 0;
    mi.dwFlags =   MOUSEEVENTF_LEFTDOWN;
    SendInput(1, &mut input, size_of::<INPUT>() as i32);
    sleep(Duration::from_millis(50));

    let mut input = INPUT { type_: INPUT_MOUSE, u: INPUT_u::default() };
    let mi = input.u.mi_mut();
    mi.dx = 0;
    mi.dy = 0;
    mi.dwFlags =  MOUSEEVENTF_LEFTUP;
    SendInput(1, &mut input, size_of::<INPUT>() as i32);
    sleep(Duration::from_millis(100));
}

pub unsafe fn click_mouse_event(dx: u32, dy: u32) {
    mouse_event(MOUSEEVENTF_LEFTDOWN, dx, dy, 0, 0);
    sleep(Duration::from_millis(50));
    mouse_event(MOUSEEVENTF_LEFTUP, dx, dy, 0, 0);
}

pub fn pixel_to_rgb(pixel: u32) -> (i32, i32, i32) {
    let rv = pixel & 0xFF;
    let gv = (pixel & 0xFF00) / 256;
    let bv = (pixel & 0xFF0000) / 65536;

    let rv = rv as i32;
    let gv = gv as i32;
    let bv = bv as i32;

    //mouse::click(p.x as u32,p.y as u32);
    rgb_is_red(rv, gv, bv);
    return (rv, gv, bv);
}

pub fn rgb_is_red(rv: i32, gv: i32, bv: i32) -> bool {
    if rv >= 140 && gv <= 70 && bv <= 70 {
       // println!("Is Red   r:{},g:{},b:{}", rv, gv, bv);
        return true;
    } else {
       // println!("Not Red  R_:{},G_:{},B_:{}", rv, gv, bv);
        return false;
    }
}

pub fn rgb_is_black(rv: i32, gv: i32, bv: i32) -> bool {
    if rv == 0 && gv == 0 && bv== 0 {
        // println!("Is Red   r:{},g:{},b:{}", rv, gv, bv);
        return true;
    } else {
        // println!("Not Red  R_:{},G_:{},B_:{}", rv, gv, bv);
        return false;
    }
}


pub fn is_red(rv: i32, gv: i32, bv: i32, r_diff: i32, gb_diff: i32) -> bool {
    let rg: i32 = (rv - gv);
    let rb: i32 = (rv - bv);
    let gb: i32 = (gv - bv);
    if rg.abs() >= r_diff && rb.abs() >= r_diff && gb.abs() <= gb_diff {
        return true;
    }
    return false;
}

cargo.toml

[package]
name = "cf"
version = "0.1.0"
authors = ["zhuxiujia <zhuxiujia@qq.com>"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]

[target.'cfg(windows)'.dependencies]
winapi = { version = "0.3", features = ["errhandlingapi","impl-default","winuser","wingdi","windef","minwindef"] }

最后,留给大家一个扩展思考🤔
1 我们基于屏幕扫描的策略,其实消耗了 系统的带宽,剪切板资源,影响显卡FPS刷新率。
有木有更加高效的办法呢? 
2 我们自动开枪可以设置线程sleep,模拟放开鼠标压枪。那么能否使用移动鼠标压枪呢?
3 我们对仗当然是喜欢一枪爆头,那么有什么高效的办法锁定敌人头部呢?

欢迎评论

评论区

写评论
tokyohuang123 2020-08-17 16:53

有没有不用winapi的方法

demodeom 2020-07-22 16:20

虽然看不懂, 但是很摩拜, 弱弱问下, 文章开头的应该不是系统防火墙吧。。。

作者 zhuxiujia 2020-07-17 23:00

内挂容易被封,技术要求也高。思路是不错。 如果一定要外挂的思路,其实可以走opencv快速截屏,opencv依赖显卡速度会干上去很多

--
👇
phper-chen: 楼主你有测试的具体游戏吗 比如单机游戏 周末想试试 更高效的估计就是内挂了 拦截运行时敌人的坐标数据?这个就不用扫描图像帧了

作者 zhuxiujia 2020-07-17 22:59

cf知道吗,测试过腾讯的cf

👇
phper-chen: 楼主你有测试的具体游戏吗 比如单机游戏 周末想试试 更高效的估计就是内挂了 拦截运行时敌人的坐标数据?这个就不用扫描图像帧了

phper-chen 2020-07-16 10:30

楼主你有测试的具体游戏吗 比如单机游戏 周末想试试 更高效的估计就是内挂了 拦截运行时敌人的坐标数据?这个就不用扫描图像帧了

作者 zhuxiujia 2020-07-15 10:51

cargo就依赖一个 winapi 其他啥也没依赖

--
👇
songlinshu: 大神cargo.toml看一下呢?

songlinshu 2020-07-14 08:18

大神cargo.toml看一下呢?

imoegirl 2020-07-13 13:19

牛逼啊

phper-chen 2020-07-13 09:54

相当可以啊 这样用技术才算好玩呢

1 共 9 条评论, 1 页