Aug 1, 2011
Monday, August 01, 2011

เขียนบอทเกมด้วย AutoIt : Bot 6 การอ่านเขียน Memory ในเกม


     นอกจาการใช้ AutoIt เขียนสคริปต์สำหรับอ่านค่าสี เพื่อกำหนดเงื่อนไขเขียนคำสั่งแล้ว คุณยังสามารถใช้ AutoIt อ่านค่าที่อยู่ในหน่วยความจำ (Memory) เพื่อนำมาแสดงผล หรือกระทั่งเปลี่ยนแปลงค่าที่อยู่ในหน่วยความจำได้ด้วย ซึ่งวิธีการดังกล่าวนั้นจะอยู่ในบทนี้

     อย่างไรก็ตามเนื่องจากเรื่องดังกล่าวผมไม่มีความชำนาญ แต่เห็นว่าสมาชิกในฟอรั่มมีความสนใจเกี่ยวกับเรื่องนี้กัน จึงทำการแปลและเรียบเรียงวิธีการ เพื่อให้เกิดความสะดวกในการเรียนรู้ ดังนั้นหากคุณมีชำนาญ หรือมีวิธีการอื่นที่น่าสนใจกว่าก็สามารถแนะนำเข้ามาได้ครับ

#เตรียมเครื่องมือในการอ่านและเขียน Memory

- โปรแกรม Cheat Engine สำหรับใช้หาค่า Pointer ในหน่วยความจำคลิก คลิกดาวน์โหลด สำหรับวิธีการใช้งานโปรแกรมนี้ผมจะไม่สอน เพราะถือว่าหากคุณสนใจในเรื่องนี้ก็ต้องหาทางศึกษามาก่อนแล้ว หากยังใช้ไม่เป็นก็เข้าไปถามในฟอรั่มได้ครับ http://pssix.forumotion.com/

- คลิกดาวน์โหลดไฟล์ NomadMemoryPSsix.au3 (นำไฟล์ไปวางไว้ในโฟลเดอร์เดียวกับสคริปต์) เป็นไฟล์ที่รวมฟังก์ชัน สำหรับอ่านและเขียน Memory ไฟล์นี้ผมแก้ไขมาจากไฟล์ต้นฉบับ NomadMemory.au3 ผมได้รวมเอาฟังก์ชันและการตั้งค่าการใช้งานอย่างอื่น ที่จำเป็นต้องใช้ในการอ่านเขียน Memory เข้าไปเพิ่มเติม

ลิงก์ไฟล์ด้านบนเป็นเวอร์ชันเก่า หากคุณใช้ AutoIt เวอร์ชันใหม่ดาวน์โหลดตามลิงก์ด้านล่างนี้ครับ
NomadMemoryPSsix.au3 สำหรับเวอร์ชันที่สูงกว่า 3.3.6.1


#เขียนสคริปต์ AutoIt อ่านเขียน Memory ทั้ง 3 แบบ
    การอ่านเขียนหน่วยความจำโดยการเขียนสคริปต์ AutoIt จะมี 3 แบบด้วยกัน โดยแยกประเภทตามวิธีการหาข้อมูลใน Cheat Engine อย่างไรก็ตามอย่างที่ผมได้บอกไปแล้ว เนื่องจากผมไม่มีความชำนาญการแบ่งออกเป็น 3 แบบนี้จึงเป็นแค่การแยกแยะเพื่อให้สะดวกต่อการใช้งาน ในอนาคตหากพบว่ามีวิธีการอื่นนอกเหนือจากนี้ ผมก็จะเขียนแก้ไขในภายหลังให้ครับ

     สำหรับในตอนต้นนี้จะเป็นการเสนอสคริปต์ AutoIt เพื่อใช้กับการอ่านเขียนแบบต่างๆ ส่วนในตอนท้ายจะเป็นวิธีการหาค่าทั้ง 3 แบบ เพื่อนำเอาค่ามาเขียนสคริปต์ AutoIt โดยใช้ Cheat Engine
การอ่านเขียนข้อมูลในหน่วยความจำทั้ง 3 แบบดังนี้

     1. อ่านเขียนจากแอดเดรส (Address) ที่กำหนด วิธีการนี้จะเป็นเขียนคำสั่งเพื่ออ่านและเขียนข้อมูลใน Address ที่กำหนด สคริปต์ที่ใช้ด้านล่างนี้ ตัวหนังสือสีเขียวคือจุดที่คุณต้องเปลี่ยน เมื่อนำไปเขียนสคริปต์เอง อย่างไรก็ตามการเขียนสคริปต์แบบนี้จะใช้ได้กับโปรแกรมที่ไม่มีการเปลี่ยนตำแหน่งแอดเดรสเท่านั้น หากโปรแกรมใดก็ตามที่เปิดขึ้นมาแล้วตำแหน่งแอดเดรสที่เก็บค่าเปลี่ยนไปทุกครั้ง จะใช้กับสคริปต์นี้ไม่ได้

#AutoIt3Wrapper_UseX64=n
#RequireAdmin
#include <NomadMemoryPSsix.au3>

Global $address = "0x0026B2C0" ;ใส่ตำแหน่งแอดเดรส ที่ต้องการจะอ่านค่า
$memopen = _MEMORYOPEN(ProcessExists("sol.exe")) ;เปลี่ยนชื่อไฟล์เกม           
If $memopen = 0 Then
    ConsoleWrite('ผิดพลาดไม่มีโปรแกรม =' & $memopen & @CRLF)
    Exit
EndIf


;;  1 อ่านค่า address  ในตำแหน่งที่แน่นอน
$value = _MEMORYREAD(($address), $memopen) ;ค่าที่อ่านได้จะเก็บไว้ในตัวแปร  $value
ConsoleWrite('ค่าที่อ่านได้จากแอดเดรส ' & $address & ' = ' & $value & @CRLF) ;แสดงผลค่าออกมา


;;  1 เขียนค่า address  ในตำแหน่งที่แน่นอน
_MemoryWrite($address, $memopen, "9999") ;เขียนค่าเป็น 9999 ลงไป
ConsoleWrite('เปลี่ยนแปลงค่าเป็น 9999 เรียบร้อยแล้ว ' & @CRLF)
_MEMORYCLOSE($memopen) ;ปิดการอ่านหน่วยความจำ นำไปวางไว้ท้ายสุดเมื่อไม่ต้องการอ่านหรือเขียนแล้ว  เช่น ตอนปิดโปรแกรม

     2. อ่านเขียนจากค่าพ้อยเตอร์ (Pointer) และออฟเซ็ต (Offset) วิธีการนี้ใช้ในกรณีที่แอดเดรสเก็บค่าเปลี่ยนแปลงอยู่ตลอดเวลา เมื่อมีการเปิดโปรแกรมขึ้นมาใหม่ทุกครั้ง ถ้ายังไม่เข้าใจก็ลองนึกเอาว่าแอดเดรสคือรถคันหนึ่งซึ่งเคลื่อนอยู่ตลอดเวลา ค่าพ้อยเตอร์และออฟเซ็ตเปรียบเสมือตัวบอกพิกัด GPS ของรถว่าอยู่ตำแหน่งไหนของแผนที่ ดังนั้นเวลาเราจะหาตำแหน่งของรถเราก็จะดูจากค่า GPS นี้แทน สคริปต์ที่ใช้ด้านล่างนี้ ตัวหนังสือสีเขียว คือจุดที่คุณต้องเปลี่ยน เมื่อนำไปเขียนสคริปต์เอง

#AutoIt3Wrapper_UseX64=n
#RequireAdmin
#include <NomadMemoryPSsix.au3>


Global $address = "0x01007170" ;ค่าพ้อยเตอร์ที่ต้องนำไปใช้คู่กับค่าออฟเซ็ต
Global $Offset[2]
$Offset[0] = 0 ; ใส่ 0 เป็นค่าเริ่ต้นทุกทครั้ง
$Offset[1] = 0x30 ;   0x30 เป็นค่าออฟเซ็ตเลขฐาน 16
$memopen = _MEMORYOPEN(ProcessExists("sol.exe")) ;เปลี่ยนชื่อไฟล์เกม           
If $memopen = 0 Then
    ConsoleWrite('ผิดพลาดไม่มีโปรแกรม =' & $memopen & @CRLF)
    Exit
EndIf


;;  2 อ่านค่า address  แบบ Pointer ชั้นเดียว
$value = _MemoryPointerRead($address, $memopen, $Offset) ; อ่านค่าจากพ้อยเตอร์จะเก็บไว้ในตัวแปร $value[1]
ConsoleWrite('ค่าที่อ่านได้จากแอดเดรส ' & $value[0] & ' = ' & $value[1] & @CRLF)


;;  2 เขียน ค่า ลง address  แบบ Pointer ชั้นเดียว
_MemoryPointerWrite($address, $memopen, $Offset, "666666") ;เขียนค่า 666666 ลงไป
ConsoleWrite('เปลี่ยนแปลงค่าเป็น 666666 เรียบร้อยแล้ว ' & @CRLF)
_MEMORYCLOSE($memopen) ;ปิดการอ่านหน่วยความจำ นำไปวางไว้ท้ายสุดเมื่อไม่ต้องการอ่านหรือเขียนแล้ว  เช่น ตอนปิดโปรแกรม

     3. อ่านและเขียนค่าแบบมัลติพ้อยเตอร์ (Multilevel Pointer) วิธีการนี้จะคล้ายกับการอ่านและเขียนค่าจากพ้อยเตอร์และออฟเซ็ตในแบบที่ 2 แต่มีความซับซ้อนขึ้นไปอีก เนื่องจากจะต้องใช้ค่า พ้อยเตอร์และออฟเซ็ต จำนวนหลายชุดด้วยกันเพื่อให้เข้าถึงยังตำแหน่งแอดเดรสในหน่วยความจำที่ต้องการ สคริปต์ที่ใช้ด้านล่างนี้ ตัวหนังสือสีเขียวคือจุดที่คุณต้องเปลี่ยน เมื่อนำไปเขียนสคริปต์เอง
   
#AutoIt3Wrapper_UseX64=n
#RequireAdmin
#include <NomadMemoryPSsix.au3>


Global $StaticOffset = "0x001CD0F8" ;ค่าออฟเซ็ตคงที่ ;~ "StarDefender3.RWG"+001CD0F8

Global $Offset[6]
$Offset[0] = 0 ; ใส่ 0 เป็นค่าเริ่มต้นทุกครั้ง ห้ามเปลี่ยน 
$Offset[1] = 0x528 ;  ค่าออฟเซ็ต ตำแหน่งฐานล่างสุด
$Offset[2] = 0x408 ;  ค่าออฟเซ็ตในตำแหน่งบน อีกชั้น
$Offset[3] = 0x540 ;  ค่าออฟเซ็ตในตำแหน่งบน อีกชั้น
$Offset[4] = 0x570 ;  ค่าออฟเซ็ตในตำแหน่งบน อีกชั้น
$Offset[5] = 0x6B4 ;  ค่าออฟเซ็ตในตำแหน่งบน สุด

$memopen = _MEMORYOPEN(ProcessExists("StarDefender3.RWG")) ;เปลี่ยนชื่อไฟล์เกม           
If $memopen = 0 Then
    ConsoleWrite('ผิดพลาดไม่มีโปรแกรม =' & $memopen & @CRLF)
    Exit
EndIf


$BaseAddr = _MemoryGetBaseAddress($memopen, 1) ; หา BaseAddresse
$FinalAddr = $BaseAddr + $StaticOffset


;; 3 อ่านค่า address แบบ Multilevel Pointer
$value = _MemoryPointerRead($FinalAddr, $memopen, $Offset) ; อ่านค่าจากพ้อยเตอร์จะเก็บไว้ในตัวแปร $value[1]
ConsoleWrite('ค่าที่อ่านได้จากแอดเดรส ' & $value[0] & ' = ' & $value[1] & @CRLF)


;; 3 เขียน ค่า ลง address แบบ  Multilevel Pointer
_MemoryPointerWrite($FinalAddr, $memopen, $Offset, "666666") ;เขียนค่าที่ต้องการเปลี่ยนลงไป
ConsoleWrite('เปลี่ยนแปลงค่าเป็น 666666 เรียบร้อยแล้ว ' & @CRLF)
 
_MEMORYCLOSE($memopen) ;ปิดการอ่านหน่วยความจำ นำไปวางไว้ท้ายสุดเมื่อไม่ต้องการอ่านหรือเขียนแล้ว  เช่น ตอนปิดโปรแกรม

วิธีนำเอาค่ามัลติพ้อยเตอร์ที่หาได้มาใส่ในสคริปต์ AutoIt ดูตามรูปตัวอย่างนี้เลยครับ




    ***การใส่ค่าแบบ Multilevel Pointer บางครั้งค่าที่คุณต้องการมีไม่ถึง 5 ชั้น ก็ต้องลดทอนอาร์เรย์ลงไป เช่น มี 3 ชั้นก็เขียนเป็นแบบนี้

Global $Offset[4]
$Offset[0] = 0 ; ใส่ 0 เป็นค่าเริ่มต้นทุกครั้ง
$Offset[1] = 0x528 ; ค่าออฟเซ็ต ตำแหน่งฐานล่างสุด
$Offset[2] = 0x408 ; ค่าออฟเซ็ตในตำแหน่งบน อีกชั้น
$Offset[3] = 0x540 ; ค่าออฟเซ็ตในตำแหน่งบนบนสุด


#จะรู้ได้อย่างไรว่าแอดเดรสที่จะนำมาเขียนสคริปต์เป็นแบบไหน
     มาถึงตรงจุดนี้คุณอาจจะสับสนในการเลือกใช้สคริปต์ทั้ง 3 แบบว่าควรใช้แบบไหน เช่น หากคุณหาค่าแต้มสักอย่างหนึ่งในเกม จะเขียนสคริปต์แบบไหนเพื่ออ่านหรือเขียนแก้ไขไป คำตอบก็คือ ไล่หาจากแบบที่ 1 ไปก่อนครับ ตัวอย่างเช่น
    พอได้ค่าแอดเดรสของแต้มแล้ว ก็ลองปิดหน้าต่างเกมแล้วเปิดขึ้นมาใหม่ ดูว่าค่าแอดเดรสเดิมยังใช้ได้อยู่หรือเปล่า ถ้าไม่ได้ก็แสดงว่าค่าแอดเดรสที่เจอนั้นไม่ใช่แบบที่ 1
    จากนั้นก็ลองหาพ้อยเตอร์และออฟเซ็ตในแบบที่ 2 ทำแบบเดิมอีกคือ ปิดแล้วเปิดเกมใหม่ ดูว่าค่าพ้อยเตอร์ชี้ไปยังแต้มได้ถูกต้องหรือไม่ ถ้าไม่ถูกต้องก็ต้องไปทำในแบบที่ 3
    

#ตัวอย่างการหาค่าแอดเดรสทั้ง 3 แบบ
     สำหรับตัวอย่างการหาค่าใน Cheat Engine เพื่อนำมาเขียนสคริปต์นั้น ผมจะอธิบายตัวอย่างเป็นขั้นตอน 2 แบบเท่านั้นคือ แบบที่ 1 และ 2 ส่วนในแบบที่ 3 หากแบ่งออกเป็นขั้นตอนจะมีขั้นตอนเยอะเกินไป และเสียเวลาในการเขียนมาก ผมจึงใช้คลิปวิดีโอที่มีผู้โฟสใน Youtube เพื่อให้คุณดูเป็นตัวอย่างในการทำ

#ตัวอย่างการหาแอดเดรสในแบบที่ 1

1. เปิดเกมขึ้นมา (ไฟล์เกมนี้อยู่โฟลเดอร์เดียวกับไฟล์ NomadMemoryPSsix.au3) จากนั้นคลิกเล่นเกมเพื่อให้คะแนนเปลี่ยนแปลง



2. เปิดโปรแกรม Cheat Engine คลิกปุ่มรูปไอคอนคอมพิวเตอร์ (อยู่มุมซ้ายบนของโปรแกรม) ทำการสแกนหาค่าที่ต้องการในเกมโดยคลิกปุ่ม New Scan ถ้าเห็นแอดเดรสขึ้นมาหลายอันเกินไป ก็ทำการเปลี่ยนค่าในตัวเกม แล้วพิมพ์ค่าใหม่ลงไป จากนั้นก็คลิกปุ่ม Next Scan เพื่อให้ได้ค่าที่ต้องการ (หากสงสัยวิธีหาค่าเข้าไปถามในฟอรั่มครับ)

3. เมื่อหาค่าที่ต้องการได้แล้ว ดับเบิลคลิกที่ค่านั้น รายละเอียดจะปรากฏที่ด้านล่างของหน้าต่าง Cheat Engine

4. ในหน้าต่างด้านล่างดับเบิลคลิกที่ค่า Address หน้าต่าง Change address: ปรากฏขึ้นมา ทำการก๊อปปี้ค่าแอดเดรสนั้น มาใช้ในการเขียนสคริปต์ AutoIt ได้เลยครับ



#ตัวอย่างการหาค่าพ้อยเตอร์และออฟเซ็ตในแบบที่ 2

      ต่อไปจะเป็นวิธีการหาค่าพ้อยเตอร์(Pointer) และค่า ออฟเซ็ต (Offset) ของค่าที่เราต้องการ ไฟล์ตัวอย่างเกมผมแนบมากับสคริปต์ที่ให้ดาวน์โหลดตอนต้นแล้วนะครับ  ทีนี้มาดูวิธีการหาค่าพ้อยเตอร์ (Pointer) และค่า ออฟเซ็ต (Offset)ของหน่วยความจำเพื่อแก้ไข ดังนี้

    1. เปิดเกมขึ้นมาหาค่าที่คุณต้องการจะเขียนสคริปต์ให้อ่านหรือแก้ไขค่า ด้วยโปรแกรม Cheat Engine (ถ้าหาค่าไม่เป็น ลองเข้าไปถามสมาชิกในฟอรั่มดูครับ) เมื่อได้ค่าที่ต้องการแล้วก็ดับเบิลคลิก เพื่อให้โปรแกรมเก็บค่า Address ดังกล่าวไว้ในแถวด้านล่าง



   2. คลิกขวาที่ค่า Address นั้นเลือกคำสั่ง Find out what accesses this address



     3. จะปรากฏหน้าต่างการทำงานขึ้นมา คลิกที่แถวแรก (ไม่ทราบว่าทำไม ไม่มีข้อมูลจากที่แปล) และถ้าไม่มีข้อมูลอะไรปรากฏขึ้นมาในหน้าต่าง คุณต้องเข้าไปเปลี่ยนแปลงค่าในเกม เช่น ค่าในเกมคือ 26 คุณต้องไปทำให้ค่านี้ลดหรือเพิ่ม ให้เกิดการทำงานเพื่อโปรแกรมจะได้ตรวจจับและแสดงข้อมูลออกมา

   4. ทำการแปลงค่า edi (อยู่ในวงเล็บ) เป็นค่า Address ตามตัวอย่างนี้คือ 002CB738 ส่วน +30 คือค่าออฟเซ็ต (Offset) ต้องนำมาใช้กับเลขพ้อยเตอร์ภายหลัง เมื่อจดค่าพ้อยเตอร์เรียบร้อย ก็คลิกปิดหน้าต่างย่อยไป จนเหลือแค่หน้าต่างหลักของโปรแกรม



    5.  ที่หน้าต่างหลักคลิกกาถูกที่ช่อง Hex (เพื่อหาค่าเป็นเลขฐาน 16 Hex ย่อมาจาก Hexadecimal)

   6. พิมพ์ตัวเลข Address ที่จดเอาไว้คือ 002CB738 ลงไปในช่อง Value:

   7. คลิกปุ่ม New Scan

   8. ตัวเลขจะปรากฏขึ้นมา หาตัวเลขที่เป็นสีเขียว เมื่อเจอก็ดับเบิลคลิกเพื่อให้ไปอยู่แถวล่าง ตัวเลขสีเขียวนี้คือค่า Address เริ่มต้นของพ้อยเตอร์ครับ ตามตัวอย่างนี้คือ 01007170
เราก็จะได้ค่าพ้อยเตอร์คือ 01007170 และได้ค่าออฟเซ็ตคือ 30 (เป็นเลขฐาน 16)

หมายเหตุ สำคัญมาก   
    ถ้าไม่เจอตัวเลขที่เป็นสีเขียว แสดงว่าค่าที่คุณต้องการหานั้น อาจเป็นค่าแบบมัลติพ้อยเตอร์ (ต้องเขียนสคริปต์แบบที่ 3)  แต่อย่างไรก็ตามบางครั้งแม้จะไม่ใช่สีเขียว ค่าที่ได้ก็เป็นค่าที่ถูกต้องได้เหมือนกัน
     เนื่องจากเกมบางเกมอาจไม่ได้โหลดข้อมูลเข้าไปยังแอดเดรสนั้นตลอดเวลา ทำให้โปรแกรม Cheat Engine มองว่าค่าแอดเดรสดังกล่าวไม่ใช่ค่าคงที่ (Static) จึงไม่แสดงเป็นสีเขียว วิธีตรวจให้แน่ใจว่าค่าที่ได้ถูกต้องหรือไม่ ก็คือปิดแล้วเปิดเกมใหม่ ดูว่าค่ายังใช้พ้อยเตอร์และออฟเซ็ตเดิมได้หรือไม่ ถ้ายังใช้ได้อยู่ก็แสดงว่าค่าดังกล่าวสามารถนำมาเขียนสคริปต์ในแบบที่ 2 ได้ (ไม่จำเป็นต้องไปทำแบบมัลติพ้อยเตอร์)



9. ขั้นตอนต่อไป เป็นการตรวจสอบความถูกต้องของค่าพ้อยเตอร์และค่าออฟเซ็ตที่ได้มา คลิกปุ่ม Add Address Manually

10. หน้าต่างย่อยจะปรากฏขึ้นมาคลิกถูกที่ตัวเลือก Pointer

11. พิมพ์ค่าพ้อยเตอร์ในช่องแรก ตามตัวอย่างนี้คือ 01007170

12. พิมพ์ค่าออฟเซ็ตในช่องถัดไป ตามตัวอย่างนี้คือ 30

13. คลิกปุ่ม OK

14. ตำแหน่งพ้อยเตอร์จะปรากฏในแถวด้านล่างของหน้าต่างโปรแกรมมี P-> นำหน้า ค่าตัวเลขที่อยู่ใน Value จะเป็นค่าตัวเลขเดียวกับค่าที่หาได้จากขั้นตอนที่ 1 (ถ้าหากไม่ผิด)
     เมื่อตรวจสอบเรียบร้อย ก็นำเอาค่าพ้อยเตอร์และออฟเซ็ตไปเขียนลงสคริปต์ AutoIt ได้เลยครับ






#ตัวอย่างการหาค่าแบบมัลติพ้อยเตอร์ (Multilevel Pointer) ในแบบที่ 3
     
ตัวอย่างการหาค่าแบบมัลติพ้อยเตอร์ (Multilevel Pointer)

ตัวอย่างการหาค่าแบบมัลติพ้อยเตอร์ (Multilevel Pointer)

การนำเอามัลติพ้อยเตอร์ (Multilevel Pointer) ที่หาได้มาใส่สคริปต์ AutoIt

     สุดท้ายขอขอบคุณสมาชิก rolovely, DarkWarrior, loveapple244, aut, flapjack, scanditionx ที่มีส่วนร่วมในการศึกษาสคริปต์ครั้งนี้ จากกระทู้ http://pssix.forumotion.com/t363-topic

###จบแล้วครับ###

2 comments:

  1. กระจ่างขึ้นเยอะเลยครับ

    ReplyDelete
  2. รับสอนแบบ ตัวต่อตัวไหมคับ อยากเรียนแบบละเอียดคับ

    ReplyDelete

    ส่วนนี้สำหรับแสดงความคิดเห็นทั่วไป สอบถามปัญหาตั้งถามได้ที่ฟอรั่ม


>>> [โปรดอ่าน] เนื่องจาก บทความการใช้งานบางโปรแกรมได้โฟสไปนานแล้ว
โปรแกรมอาจมีการอัปเดท วิธีการใช้งาน อาจใช้ไม่ได้ หรือมีวิธีที่ง่ายกว่าในเวอร์ชั่นใหม่
หากคุณพบว่าวิธีการใช้งานไม่ตรงกับบทความในบล็อกนี้ สามารถแนะนำเพิ่มเติมได้ครับ