给客户制作了一个刷游戏任务的PC外挂,利用C#获取窗口句柄,进行操作实现。
此方法,只适用于可以获取到句柄的游戏,获取不到的可以用按键精灵制作,手机也一样。
现在年代的外挂基本都基于简化操作,而不是以前的那种攻击数据,现在技术很难突破,除非自己人,实现核心代码如下,记录一下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Runtime.InteropServices;
using System.Drawing;
using System.Windows.Forms;
using System.IO;
using System.Drawing.Imaging;
using Newtonsoft.Json;
using System.Text.RegularExpressions;
namespace LSWZ
{
/// <summary>
/// 游戏控制
/// </summary>
public class GamePlay
{
/// <summary>
/// 获取窗体句柄
/// </summary>
/// <param name="lpClassName"></param>
/// <param name="lpWindowName"></param>
/// <returns></returns>
[DllImport("User32.dll", EntryPoint = "FindWindow")]
public extern static IntPtr FindWindow(string lpClassName, string lpWindowName);
/// <summary>
/// 获取窗体句柄
/// </summary>
/// <param name="hwnd">目标句柄</param>
/// <param name="nCmdShow">参考相关文档</param>
/// <returns></returns>
[DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
public static extern int ShowWindow(IntPtr hwnd, int nCmdShow);
/// <summary>
/// 设置目标窗体大小,位置
/// </summary>
/// <param name="hWnd">目标句柄</param>
/// <param name="x">目标窗体新位置X轴坐标</param>
/// <param name="y">目标窗体新位置Y轴坐标</param>
/// <param name="nWidth">目标窗体新宽度</param>
/// <param name="nHeight">目标窗体新高度</param>
/// <param name="BRePaint">是否刷新窗体</param>
/// <returns></returns>
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern int MoveWindow(IntPtr hWnd, int x, int y, int nWidth, int nHeight, bool BRePaint);
[DllImport("user32.dll", EntryPoint = "SendMessage", SetLastError = true, CharSet = CharSet.Auto)] //发送消息
private static extern int SendMessage(IntPtr hwnd, uint wMsg, int wParam, int lParam);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", EntryPoint = "SendMessage")]
private static extern int SendMessage(IntPtr hwnd, int wMsg, int wParam, StringBuilder lParam);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool GetWindowRect(IntPtr hWnd, ref RECT lpRect);
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int Left;//最左坐标
public int Top;//最上坐标
public int Right;//最右坐标
public int Bottom;//最下坐标
}
//消息发送API
[DllImport("user32.dll", EntryPoint = "PostMessage")]
public static extern int PostMessage(
IntPtr hWnd, // 信息发往的窗口的句柄
int Msg, // 消息ID
int wParam, // 参数1
int lParam // 参数2
);
IntPtr hWnd;
GameConfig config;
Baidu.Aip.Ocr.Ocr client;
WindowInfo info;
LoginForm log;
public void setLog(string s)
{
log.SetDtText("窗口【" + info.aWnd + "】:" + DateTime.Now);
log.SetDtText("窗口【" + info.aWnd + "】:" + s);
}
public void start(GameConfig c, WindowInfo i, Baidu.Aip.Ocr.Ocr x, LoginForm cur)
{
log = cur;
info = JsonConvert.DeserializeObject<WindowInfo>(JsonConvert.SerializeObject(i));
hWnd = info.hWnd;
config = c;
client = x;
string word = GetWinText();
setLog("已启动");
// 显示窗口
ShowWindow(info.aWnd, 4);
// 设置窗口分辨率 450 * 760
MoveWindow(info.aWnd, 0, 0, 450, 760, true);
setLog("设置分辨率完成");
// 启动任务
while (true)
{
setLog("开始执行任务");
goHome();
threadPlay();
setLog("执行任务完毕");
Thread.Sleep(60 * 60 * 1000);
}
}
/// <summary>
/// 出城
/// </summary>
public void PlayChuCheng()
{
string word = GetWinText();
SendMsg(44, 695);
Thread.Sleep(5000);
if (word.IndexOf("\"回\"") > -1)
{
SendMsg(44, 695);
Thread.Sleep(5000);
}
setLog("出城");
}
/// <summary>
/// 回到左上角
/// </summary>
public void DingWei()
{
setLog("恢复默认坐标");
//没有固定坐标依靠,创造坐标
SendTuoDongMsg(100, 360, 100, 720);
SendTuoDongMsg(100, 360, 100, 720);
SendTuoDongMsg(100, 360, 100, 720);
SendTuoDongMsg(100, 360, 100, 720);
SendTuoDongMsg(100, 360, 100, 720);
SendTuoDongMsg(100, 360, 100, 720);
SendTuoDongMsg(200, 100, 400, 100);
SendTuoDongMsg(200, 100, 400, 100);
SendTuoDongMsg(200, 100, 400, 100);
SendTuoDongMsg(200, 100, 400, 100);
SendTuoDongMsg(200, 100, 400, 100);
SendTuoDongMsg(200, 100, 400, 100);
SendTuoDongMsg(200, 100, 400, 100);
SendTuoDongMsg(200, 100, 400, 100);
SendTuoDongMsg(200, 100, 400, 100);
SendTuoDongMsg(200, 100, 400, 100);
setLog("恢复默认坐标完成");
}
/// <summary>
/// 回城
/// </summary>
public void PlayHuiCheng()
{
string word = GetWinText();
SendMsg(44, 695);
Thread.Sleep(5000);
//回城恢复默认缩放,别影响滑动定位
if (word.IndexOf("\"出\"") > -1)
{
SendMsg(44, 695);
Thread.Sleep(5000);
}
setLog("执行回城");
}
/// <summary>
/// 截取字符串中开始和结束字符串中间的字符串
/// </summary>
/// <param name="source">源字符串</param>
/// <param name="startStr">开始字符串</param>
/// <param name="endStr">结束字符串</param>
/// <returns>中间字符串</returns>
public string SubstringSingle(string source, string startStr, string endStr)
{
Regex rg = new Regex("(?<=(" + startStr + "))[.\\s\\S]*?(?=(" + endStr + "))", RegexOptions.Multiline | RegexOptions.Singleline);
return rg.Match(source).Value;
}
public int SubstringSingle(string source)
{
string r = @"[0-9]+";
Regex reg = new Regex(r, RegexOptions.IgnoreCase | RegexOptions.Singleline);
MatchCollection mc = reg.Matches(source);
if (mc.Count > 0)
{
try
{
return Convert.ToInt32(mc[0].Groups[0].Value);
}
catch (Exception)
{
return 0;
}
}
return 0;
}
public void goHome()
{
setLog("判断是否处于城内");
string word = GetWinText();
if (word.IndexOf("\"出\"") > -1 || word.IndexOf("\"回\"") > -1)
{
setLog("已处于城内");
return;
}
//关闭
setLog("关闭");
SendMsg(26, 48);
goHome();
}
public void goDuiLie()
{
setLog("判断是否处于城外");
string word = GetWinText();
if (word.IndexOf("队列") > -1)
{
setLog("已处于城外");
return;
}
//关闭
setLog("关闭");
SendMsg(26, 48);
goDuiLie();
}
public void threadPlay()
{
//日常
RiChangPlay();
WaPlay();
//打野
DaYePlay();
//采集
CaiJiPlay();
}
/// <summary>
/// 打野级别调整
/// </summary>
public bool DaYeLv()
{
setLog("打野,正在降低打野等级");
//点击查找
SendMsg(327, 600);
//获取当前打野级别
string word = GetWinText(1);
string w = SubstringSingle(word, "等级", "/");
int lv = SubstringSingle(w);
if (lv > config.daye.lv)
{
setLog("打野,降低打野等级成功");
SendMsg(53, 620);
//关闭
SendMsg(26, 48);
return true;
}
return false;
}
/// <summary>
/// 获取体力
/// </summary>
public int getTiLi()
{
setLog("打野,获取当前体力");
SendMsg(26, 48);
SendMsg(135, 615);
string word = GetWinText();
//退出头像界面
SendMsg(33, 41);
SendMsg(33, 41);
//获取当前体力
string w = SubstringSingle(word, "当前体力:", "\"");
return SubstringSingle(w);
}
/// <summary>
/// 打野
/// </summary>
public bool DaYe(int tili)
{
setLog("执行打野");
bool isOK = true;
//有体力就打野
while (tili > 5)
{
//表示不在主界面
goDuiLie();
//判断是否有空闲队列
string kongxian = GetWinText();
if (kongxian.IndexOf("空") > -1)
{
setLog("打野,有空闲队列");
//点击查找
SendMsg(327, 600);
//搜索
SendMsg(200, 700);
kongxian = GetWinText();
//判定是否找到野怪,如果没找到
if (kongxian.IndexOf("目标现在搜索不到") > -1)
{
setLog("打野,野怪没了");
//关闭提示
SendMsg(200, 420);
//如果调整打野级别失败
if (!DaYeLv())
{
setLog("停止打野,低于打野最低等级");
//打野停止
isOK = false;
break;
}
}
else
{
setLog("打野中");
//点击野怪
SendMsg(200, 360);
SendMsg(210, 495);
//使用编队一
SendMsg(78, 85);
//出征
SendMsg(320, 690);
//不管有没有打倒都扣除体力
tili = tili - 5;
setLog("恭喜你打了一只野,血赚");
}
}
else
{
setLog("打野,等待空闲队列");
}
//等待五秒
Thread.Sleep(5000);
}
return isOK;
}
/// <summary>
/// 撤队
/// </summary>
public void CheLiDuiWu()
{
setLog("执行撤回队伍操作");
string kongxian = GetWinText();
//如果有行军
if (kongxian.IndexOf("行军") > -1)
{
string w = SubstringSingle(kongxian, "行军X", "空");
int xingjun = SubstringSingle(w);
if (xingjun == 0)
{
xingjun = 5;
}
int[] xingjun_list = { 600, 566, 530, 495, 460 };
for (int i = 0; i < xingjun; i++)
{
//撤队
SendMsg(148, xingjun_list[i]);
SendMsg(243, 420);
SendMsg(372, 257);
}
}
}
public void CaiJiPlay()
{
if (config.caiji.enable)
{
setLog("执行采集任务");
//出城
PlayChuCheng();
goDuiLie();
//判断空闲队伍数量
string kongxian = GetWinText();
//如果有行军
int kx = 5;
if (kongxian.IndexOf("行军") > -1)
{
string w = SubstringSingle(kongxian, "行军X", "空");
kx = kx - SubstringSingle(w);
}
//点击查找
SendMsg(327, 600);
//推动回到最后
SendTuoDongMsg(359, 528, 230, 528);
//关闭面板
SendMsg(26, 48);
setLog("采集,初始化");
List<int> caiji_list = new List<int>();
//{ 180, 246, 310, 370 }
//如果选择了粮食
if (config.caiji.type_ls)
{
caiji_list.Add(180);
}
if (config.caiji.type_mt)
{
caiji_list.Add(246);
}
if (config.caiji.type_tk)
{
caiji_list.Add(310);
}
if (config.caiji.type_bs)
{
caiji_list.Add(370);
}
if (caiji_list.Count == 0)
{
caiji_list.Add(180);
caiji_list.Add(246);
caiji_list.Add(310);
caiji_list.Add(370);
}
for (int i = 0; i < kx; i++)
{
setLog("采集,执行中");
//点击查找
SendMsg(327, 600);
Random rd = new Random();
int chooes = rd.Next(caiji_list.Count);
SendMsg(caiji_list[chooes], 530);
SendMsg(200, 700);
SendMsg(200, 360);
SendMsg(296, 360);
SendMsg(320, 690);
setLog("采集,执行完毕");
}
}
}
/// <summary>
/// 打野控制
/// </summary>
public void DaYePlay()
{
//如果可以打野
if (config.daye.enable)
{
setLog("执行打野任务");
//出城
PlayChuCheng();
setLog("打野,初始化");
//初始化
//点击查找
SendMsg(327, 600);
//推动回到第一
SendTuoDongMsg(30, 528, 200, 528);
//点击打野
SendMsg(30, 528);
//调整打野等级
SendMsg(255, 620);
SendMsg(255, 620);
SendMsg(255, 620);
SendMsg(255, 620);
SendMsg(255, 620);
SendMsg(255, 620);
SendMsg(255, 620);
//关闭面板
SendMsg(26, 48);
//获取体力
int tili = getTiLi();
//执行打野,默认大于等于100点体力就干活
setLog("打野,判断体力是否过多");
bool isdd = false;
if (tili >= 100)
{
isdd = true;
setLog("打野,体力过多");
//撤离队伍
CheLiDuiWu();
//打野
DaYe(tili);
}
//当前设定是否使用体力
setLog("打野,判断是否可以使用体力");
if (config.daye.tili)
{
int tiliNew = 0;
//撤离队伍
if (config.daye.tili_num > 0)
{
isdd = true;
setLog("打野,根据体力上限执行打野");
CheLiDuiWu();
}
//达到体力上限
while (config.daye.tili_num > 0)
{
//使用体力
shiyongTili();
//获取当前体力
tiliNew = getTiLi();
//扣除上限
config.daye.tili_num = config.daye.tili_num - (tiliNew - tili);
//标记当前体力
tili = tiliNew;
//执行打野
if (!DaYe(tili))
{
//表示目前打野等级不满足最低要求
break;
}
//打野完成,矫正体力
tili = getTiLi();
}
}
if (isdd)
{
//休息五分钟等待队伍回来
Thread.Sleep(5 * 60 * 1000);
}
}
}
/// <summary>
/// 使用体力
/// </summary>
public void shiyongTili()
{
setLog("正在使用体力");
//打开体力界面
SendMsg(26, 48);
SendMsg(135, 615);
//使用体力
SendMsg(343, 237);
SendMsg(200, 460);
//退出头像界面
SendMsg(33, 41);
SendMsg(33, 41);
setLog("使用体力完成");
}
/// <summary>
/// 联盟挖
/// </summary>
public void WaPlay()
{
// 执行日常
if (config.richang.enable)
{
////////////////////////////联盟宝藏///////////////////////////////////
if (config.richang.bcNum > 0)
{
setLog("执行联盟宝藏任务");
//打开联盟
SendMsg(315, 695);
//打开宝藏
SendMsg(203, 465);
setLog("正在挖联盟宝藏");
//刷新执行两次
SendMsg(196, 680);
SendMsg(196, 680);
//给老子挖
SendMsg(330, 290);
setLog("恭喜你,挖了一个宝藏,天美血亏");
//切换
SendMsg(206, 87);
SendMsg(206, 87);
SendMsg(325, 290);
SendMsg(325, 290);
setLog("联盟宝藏请求盟友");
//切换
SendMsg(311, 87);
SendMsg(327, 292);
SendMsg(327, 292);
SendMsg(202, 185);
setLog("联盟宝藏帮助盟友");
// 退出界面
SendMsg(377, 42);
setLog("退出联盟宝藏");
}
////////////////////////////联盟宝藏 - end/////////////////////////////
}
}
/// <summary>
/// 进入神将台
/// </summary>
public bool goShenJiang()
{
DingWei();
setLog("正在进入神将台界面");
SendTuoDongMsg(200, 360, 0, 360);
SendTuoDongMsg(200, 360, 0, 360);
SendTuoDongMsg(200, 360, 0, 360);
SendTuoDongMsg(200, 360, 0, 360);
SendTuoDongMsg(200, 360, 200, 0);
SendTuoDongMsg(200, 360, 200, 300);
SendMsg(270, 370);
SendMsg(248, 452);
string word = GetWinText();
// 进入神将台
if (word.IndexOf("点将") > -1)
{
return true;
}
return false;
}
/// <summary>
/// 进入神将台
/// </summary>
public bool goXunLong()
{
DingWei();
setLog("正在进入寻龙界面");
SendTuoDongMsg(200, 360, 0, 360);
SendTuoDongMsg(200, 360, 0, 360);
SendMsg(294, 434);
SendMsg(215, 557);
string word = GetWinText();
// 进入神将台
if (word.IndexOf("连抽必得紫色龙魂") > -1)
{
return true;
}
return false;
}
/// <summary>
/// 日常控制
/// </summary>
public void RiChangPlay()
{
// 执行日常
if (config.richang.enable)
{
setLog("执行常规任务");
PlayHuiCheng();
////////////////////////////执行寻龙////////////////////////////
if (config.richang.xunlongNum > 0 || config.richang.xunlongGaoJiNum > 0)
{
bool isOK = goXunLong();
//第二次调用
if (!isOK)
{
setLog("重新进入寻龙界面");
isOK = goXunLong();
}
// 进入神将台
if (isOK)
{
string word = GetWinText(1);
setLog("已进入寻龙界面");
// 执行日常免费寻龙
if (word.IndexOf("今日剩余免费次数") > -1 && word.IndexOf("今日剩余免费次数:0/3") == -1)
{
setLog("正在执行寻龙每日免费抽取任务");
//寻龙
SendMsg(110, 631);
//确定
SendMsg(303, 575);
}
setLog("检查是否指定寻龙");
// 执行指定寻龙
while (config.richang.xunlongGaoJiNum > 0)
{
//10连抽 寻龙
SendMsg(303, 633);
// 等待10连抽动画
Thread.Sleep(5000);
//确定
SendMsg(303, 575);
config.richang.xunlongGaoJiNum--;
}
setLog("指定寻龙执行完毕");
// 退出寻龙界面
SendMsg(33, 41);
setLog("退出寻龙");
}
}
////////////////////////////执行寻龙 - end////////////////////////////
////////////////////////////执行神将台////////////////////////////////
if (config.richang.shenjiangNum > 0 || config.richang.shenjiangGaoJiNum > 0 || config.richang.jinengGaoJiNum > 0 || config.richang.jiceGaoJiNum > 0)
{
bool isOK = goShenJiang();
//第二次调用
if (!isOK)
{
setLog("重新进入神将台界面");
isOK = goShenJiang();
}
// 进入神将台
if (isOK)
{
string word = GetWinText(1);
setLog("已进入神将台界面");
// 执行日常免费寻龙
if (word.IndexOf("今日剩余免费次数") > -1 && word.IndexOf("今日剩余免费次数:0/5") == -1)
{
setLog("正在执行武将每日免费抽取任务");
//武将
SendMsg(98, 357);
//中间确定
SendMsg(204, 577);
//确定
SendMsg(303, 575);
}
//切换
SendMsg(249, 93);
word = GetWinText(1);
if (word.IndexOf("今日剩余免费次数") > -1 && word.IndexOf("今日剩余免费次数:0/5") == -1)
{
setLog("正在执行技能每日免费抽取任务");
//技能
SendMsg(100, 576);
//中间确定
SendMsg(204, 577);
//确定
SendMsg(303, 575);
}
//切换
SendMsg(345, 95);
word = GetWinText(1);
if (word.IndexOf("今日剩余免费次数") > -1 && word.IndexOf("今日剩余免费次数:0/5") == -1)
{
setLog("正在执行计策每日免费抽取任务");
//计策
SendMsg(100, 576);
//中间确定
SendMsg(204, 577);
//确定
SendMsg(303, 575);
}
setLog("检查指定高级招募抽取任务");
if (config.richang.shenjiangGaoJiNum > 0)
{
//切换
SendMsg(158, 95);
// 执行指定高招
while (config.richang.shenjiangGaoJiNum > 0)
{
//10连抽 高招
SendMsg(303, 645);
// 等待10连抽动画
Thread.Sleep(5000);
//中间确定
SendMsg(204, 577);
//确定
SendMsg(303, 575);
config.richang.shenjiangGaoJiNum--;
}
}
setLog("检查指定技能抽取任务");
if (config.richang.jinengGaoJiNum > 0)
{
//切换
SendMsg(249, 93);
// 执行指定技能
while (config.richang.jinengGaoJiNum > 0)
{
//10连抽 技能
SendMsg(307, 580);
// 等待10连抽动画
Thread.Sleep(5000);
//确定
SendMsg(303, 575);
config.richang.jinengGaoJiNum--;
}
}
setLog("检查指定计策抽取任务");
if (config.richang.jiceGaoJiNum > 0)
{
//切换
SendMsg(345, 95);
// 执行指定计策
while (config.richang.jiceGaoJiNum > 0)
{
//10连抽 计策
SendMsg(308, 590);
// 等待10连抽动画
Thread.Sleep(5000);
//确定
SendMsg(303, 575);
config.richang.jiceGaoJiNum--;
}
}
setLog("完成神将台指定抽取任务");
// 退出界面
SendMsg(33, 41);
setLog("退出神将台");
}
else
{
setLog("进入神将台失败");
}
}
////////////////////////////执行神将台 - end///////////////////////////
}
}
/// <summary>
/// 获取OCR识别后内容
/// </summary>
public string GetWinText(int type = 0)
{
setLog("OCR识别");
var image = GetWindowCapture();
Newtonsoft.Json.Linq.JObject result;
if (type == 1)
{
result = client.AccurateBasic(image);
}
else
{
result = client.GeneralBasic(image);
}
setLog("OCR识别完成");
return result.ToString();
}
[DllImport("user32.dll")]
public static extern bool PrintWindow(
IntPtr hwnd, // Window to copy,Handle to the window that will be copied.
IntPtr hdcBlt, // HDC to print into,Handle to the device context.
UInt32 nFlags // Optional flags,Specifies the drawing options. It can be one of the following values.
);
[DllImport("user32.dll")]
public static extern IntPtr GetWindowDC(
IntPtr hwnd
);
/// <summary>
/// 截图返回byte[]
/// </summary>
public byte[] GetWindowCapture()
{
//RECT windowRect = new RECT();
//GetWindowRect(info.aWnd, ref windowRect);
//int width = windowRect.Right - windowRect.Left;
//int height = windowRect.Bottom - windowRect.Top;
Bitmap image = new Bitmap(450, 760);
Graphics gp = Graphics.FromImage(image);
IntPtr dc = gp.GetHdc();
PrintWindow(info.aWnd, dc, 0);
gp.ReleaseHdc();
gp.Dispose();
string path = "F:\\LSWZ\\LSWZ\\bin\\Debug\\test.jpg";
image.Save(path, System.Drawing.Imaging.ImageFormat.Jpeg);
return Bitmap2Byte(image);
}
/// <summary>
/// 转义
/// </summary>
public byte[] Bitmap2Byte(Bitmap bitmap)
{
using (MemoryStream stream = new MemoryStream())
{
bitmap.Save(stream, ImageFormat.Jpeg);
byte[] data = new byte[stream.Length];
stream.Seek(0, SeekOrigin.Begin);
stream.Read(data, 0, Convert.ToInt32(stream.Length));
return data;
}
}
/// <summary>
/// 发送点击事件
/// </summary>
public void SendMsg(int x, int y, int time = 3000)
{
PostMessage(hWnd, 0x0201, 1, (y * 65536 + x));
PostMessage(hWnd, 0x0202, 0, (y * 65536 + x));
//SendMessage(hWnd, 0x0201, (IntPtr)1, (IntPtr)(y * 65536 + x));
//Thread.Sleep(300);
//SendMessage(hWnd, 0x0202, (IntPtr)0, (IntPtr)(y * 65536 + x));
// 为防止电脑卡,每次点完等待5秒
setLog("点击事件");
Thread.Sleep(time);
}
/// <summary>
/// 发送拖动事件
/// </summary>
public void SendTuoDongMsg(int x, int y, int x2, int y2)
{
PostMessage(hWnd, 0x0201, 1, (y * 65536 + x));
PostMessage(hWnd, 0x0200, 1, (y2 * 65536 + x2));
PostMessage(hWnd, 0x0202, 0, (y2 * 65536 + x2));
setLog("拖动事件");
Thread.Sleep(1000);
}
}
}