大學宿舍玩遊戲的時候,爲了簡化重複的鍵鼠動做,有學習過按鍵精靈和TC腳本開發工具,並作了一些小腳本,基本達到了當時的需求。不知不覺,已經畢業了3年了,無聊之餘又玩起了遊戲,對於一些無趣的重複行爲,因而又想寫個腳原本處理下。好比跑任務,自動補血等,沒想到如今的遊戲對於按鍵精靈和TC基本上都是封殺。對於我這種小白,過遊戲安全檢測這種棘手的事,也許花費不少時間,都沒有結果。常常測試,發現遊戲不會對本身寫的C#腳本進行檢測,因此決定用C#來寫。算法
研究了幾天,忽然間又不想玩遊戲了,因此把這幾天的研究成果分享給你們,但願對後來的人有啓發。我玩的是一款QQ的遊戲,我想要作的腳本就是 掃貨腳本(當有人擺攤價格低於本身預設的價格時,自動購買下來,倒賣)。安全
通過分析,最難的步驟是怎麼識別攤位上的價格,第一感受,這不就是文字識別嗎,因而找了一個.Net 惟一開源的Tesseract-ocr。通過測試,發現Tesseract-ocr只適合白底黑字的文字識別,因而對圖片進行了如下處理ide
/// <summary> /// 反像 /// </summary> /// <param name="bitmapImage"></param> /// <returns></returns> public static Bitmap ApplyInvert(Bitmap source) { //create a blank bitmap the same size as original Bitmap newBitmap = new Bitmap(source.Width, source.Height); //get a graphics object from the new image Graphics g = Graphics.FromImage(newBitmap); // create the negative color matrix ColorMatrix colorMatrix = new ColorMatrix(new float[][] { new float[] {-1, 0, 0, 0, 0}, new float[] {0, -1, 0, 0, 0}, new float[] {0, 0, -1, 0, 0}, new float[] {0, 0, 0, 1, 0}, new float[] {1, 1, 1, 0, 1} }); // create some image attributes ImageAttributes attributes = new ImageAttributes(); attributes.SetColorMatrix(colorMatrix); g.DrawImage(source, new Rectangle(0, 0, source.Width, source.Height), 0, 0, source.Width, source.Height, GraphicsUnit.Pixel, attributes); //dispose the Graphics object g.Dispose(); return newBitmap; } /// <summary> /// 圖片變成灰度 /// </summary> /// <param name="b"></param> /// <returns></returns> public static Bitmap ToGray(Bitmap b) { for (int x = 0; x < b.Width; x++) { for (int y = 0; y < b.Height; y++) { Color c = b.GetPixel(x, y); int luma = (int)(c.R * 0.3 + c.G * 0.59 + c.B * 0.11);//轉換灰度的算法 b.SetPixel(x, y, Color.FromArgb(luma, luma, luma)); } } return b; } /// <summary> /// 圖像變成黑白 /// </summary> /// <param name="b"></param> /// <returns></returns> public static Bitmap ToBlackWhite(Bitmap b) { for (int x = 0; x < b.Width; x++) { for (int y = 0; y < b.Height; y++) { Color c = b.GetPixel(x, y); if (c.R < (byte)255) { b.SetPixel(x, y, Color.FromArgb(0, 0, 0)); } } } return b; } /// <summary> /// 圖像亮度調整 /// </summary> /// <param name="b"></param> /// <param name="degree"></param> /// <returns></returns> public static Bitmap KiLighten(Bitmap b, int degree) { if (b == null) { return null; } if (degree < -255) degree = -255; if (degree > 255) degree = 255; try { int width = b.Width; int height = b.Height; int pix = 0; BitmapData data = b.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb); unsafe { byte* p = (byte*)data.Scan0; int offset = data.Stride - width * 3; for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { // 處理指定位置像素的亮度 for (int i = 0; i < 3; i++) { pix = p[i] + degree; if (degree < 0) p[i] = (byte)Math.Max(0, pix); if (degree > 0) p[i] = (byte)Math.Min(255, pix); } // i p += 3; } // x p += offset; } // y } b.UnlockBits(data); return b; } catch { return null; } } /// <summary> /// 圖像對比度調整 /// </summary> /// <param name="b">原始圖</param> /// <param name="degree">對比度[-100, 100]</param> /// <returns></returns> public static Bitmap KiContrast(Bitmap b, int degree) { if (b == null) { return null; } if (degree < -100) degree = -100; if (degree > 100) degree = 100; try { double pixel = 0; double contrast = (100.0 + degree) / 100.0; contrast *= contrast; int width = b.Width; int height = b.Height; BitmapData data = b.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb); unsafe { byte* p = (byte*)data.Scan0; int offset = data.Stride - width * 3; for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { // 處理指定位置像素的對比度 for (int i = 0; i < 3; i++) { pixel = ((p[i] / 255.0 - 0.5) * contrast + 0.5) * 255; if (pixel < 0) pixel = 0; if (pixel > 255) pixel = 255; p[i] = (byte)pixel; } // i p += 3; } // x p += offset; } // y } b.UnlockBits(data); return b; } catch { return null; } }
通過以上處理,發現識別率高了不少,但是不知道什麼緣由對單個價格,如9,6,5 這種沒法識別,並且對於3,8,0,很容易混淆,對於這種掃貨的腳原本說,價格識別率必須是100%對的。後來又去學習怎麼訓練字庫,花了不少時間,最終得出一個結論,OCR訓練識別率的前提是 文字能被識別,可是識別錯了,若是連文字都識別不出,那麼沒有訓練的必要了,就這樣,放棄了。工具
當天晚上,看了一篇別人識別網站驗證碼的文章,又看了國內的腳本開發的文字識別,看到大漠插件的字庫,是一個個像素組成的字。靈光一閃,每一個價格的筆畫不一樣,位置不一樣,一樣大小的圖片,Base64值確定不同啊,次日作了實驗,證實本身的想法是對的,哪怕一個像素不對,都是不同的。因而寫了個腳本,把攤位裏1-2000的價格都抓下來,而後處理成黑白後分割成小圖片。學習
/// <summary> /// 圖像轉Base字符串 /// </summary> /// <returns></returns> public static string ToBaseMd5(this Bitmap img) { if (img == null) return string.Empty; else return Convert.ToBase64String(ToByte(img)); }
作腳本嘛,最重要的截取指定區域的圖片嘛,直接上代碼。開發工具
Bitmap image = new Bitmap(26, 18); Graphics imgGraphics = Graphics.FromImage(image); //設置截屏區域 imgGraphics.CopyFromScreen(X, Y, 0, 0, new Size(26, 18)); image.Save(path, ImageFormat.Tiff);
以上的技術,基本上能夠把識別文字的價格問題解決了,固然中途花了不少時間來作重複的事。測試
接下來有個問題,怎麼定位價格啊,各類按鈕的位置,所以要找個參照物,簡單的說就是,截取一個參考物的圖片,而後其餘元素的位置相對這個參照物進行設置。轉化成技術來講,就是一張小圖在另外一張大圖裏面找到位置,並返回相對座標。嘗試了幾種方法,最終使用 AForge 這個開源項目來處理,代碼以下網站
/// <summary> /// 判斷圖像是否存在 /// </summary> /// <param name="template"></param> /// <param name="bmp"></param> /// <returns></returns> public static bool ContainsImg(this Bitmap template, Bitmap bmp) { // create template matching algorithm's instance // (set similarity threshold to 92.1%) ExhaustiveTemplateMatching tm = new ExhaustiveTemplateMatching(0.921f); // find all matchings with specified above similarity TemplateMatch[] matchings = tm.ProcessImage(template, bmp); // highlight found matchings return matchings.Length > 0; } /// <summary> /// 判斷圖像是否存在另外的圖像中,並返回座標 /// </summary> /// <param name="template"></param> /// <param name="bmp"></param> /// <returns></returns> public static Point ContainsGetPoint(this Bitmap template, Bitmap bmp) { // create template matching algorithm's instance // (set similarity threshold to 92.1%) ExhaustiveTemplateMatching tm = new ExhaustiveTemplateMatching(0.921f); // find all matchings with specified above similarity TemplateMatch[] matchings = tm.ProcessImage(template, bmp); // highlight found matchings BitmapData data = template.LockBits(new Rectangle(0, 0, template.Width, template.Height), ImageLockMode.ReadWrite, template.PixelFormat); Point p = new Point(); if (matchings.Length > 0) { Drawing.Rectangle(data, matchings[0].Rectangle, Color.White); p = matchings[0].Rectangle.Location; template.UnlockBits(data); } return p; }
如今價格能夠識別了,經過找圖,界面的各個座標都肯定了,如今就是寫模擬鼠標和鍵盤的操做了。這個網上不少,個人很簡單ui
對於個人遊戲來講鼠標操做,就是移動和左擊this
public class MouseHelper { [DllImport("user32.dll")] private static extern bool SetCursorPos(int X, int Y); [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)] private static extern void mouse_event(uint dwFlags, uint dx, uint dy, uint cButtons, UIntPtr dwExtraInfo); /// <summary> /// 鼠標左擊 /// </summary> public static void LeftClick() { mouse_event(0x02, 0, 0, 0, UIntPtr.Zero); mouse_event(0x04, 0, 0, 0, UIntPtr.Zero); } /// <summary> /// 鼠標移動到指定的位置 /// </summary> /// <param name="x"></param> /// <param name="y"></param> public static void MovePoint(Point p) { SetCursorPos(p.X, p.Y); } }
鍵盤能夠用C#自帶的方法 SendKeys
SendKeys.Send("輸入文本");//用於輸入文字 SendKeys.SendWait("{ENTER}");用於輸入按鍵命令
基本上就這些了,另外附上一些可能會用到的技能
找到遊戲句柄
/// <summary> /// 獲取遊戲句柄 /// </summary> /// <returns></returns> public static int GetFFoHandle() { Process[] processes = Process.GetProcessesByName("進程名稱"); var p = processes.FirstOrDefault(); if (p == null) { return 0; } else { return p.MainWindowHandle.ToInt32(); } }
根據句柄獲取遊戲的位置
[DllImport("user32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] public 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; }
根據句柄將遊戲窗體移動到某個位置
/// <summary> /// 根據句柄移動窗體 /// </summary> /// <param name="hWnd"></param> /// <param name="hWndInsertAfter"></param> /// <param name="x"></param> /// <param name="Y"></param> /// <param name="cx"></param> /// <param name="cy"></param> /// <param name="wFlags"></param> /// <returns></returns> [DllImport("user32.dll", EntryPoint = "SetWindowPos")] public static extern IntPtr SetWindowPos(IntPtr hWnd, int hWndInsertAfter, int x, int Y, int cx, int cy, int wFlags);
其餘什麼快捷鍵啊,啥的,網上一大堆就不寫了。
好了,就這些,經過以上的代碼,能夠完成大部分簡單的前臺腳本了,寫的比較亂,可是對於正在研究中的人,我想必定省了很多事。