คืนชีพเกม RTS Dune ที่ตายไปแล้ว: EmperorLauncher
(wheybags.com)- เกม RTS ปี 2001
Emperor: Battle for Duneมีปัญหาทั้งการรัน การติดตั้ง และการเล่นออนไลน์บน Windows สมัยใหม่ ส่วน EmperorLauncher คือแพตช์ที่ชุบชีวิตให้กลับมาเล่นได้อีกครั้ง - การปรับปรุงหลักมุ่งไปที่ การรองรับความละเอียดสูง, การจำกัดที่ 60 FPS, มัลติเพลเยอร์ออนไลน์แบบ IP โดยตรง, โหมดแคมเปญร่วมมือ และการข้ามขั้นตอนติดตั้งที่เสีย
- การทำงานประกอบด้วยลอนเชอร์ที่แทนที่
Emperor.exe, การฉีด DLL ใส่Game.exe, การแพตช์ฟังก์ชันด้วย Microsoft Detours, การ hook การเรนเดอร์ Direct3D 7, การดัก winsock และเซิร์ฟเวอร์ WOL แบบง่าย - การเล่นออนไลน์เปลี่ยนโครงสร้างเดิมที่เป็น P2P, ใช้พอร์ตสุ่ม และ NAT punching ให้ tunnel ผ่านการเชื่อมต่อเดียวแบบไคลเอนต์→เซิร์ฟเวอร์ ทำให้มีเพียงโฮสต์เซิร์ฟเวอร์ที่ต้องสนใจการตั้งค่าเครือข่าย
- เครื่องมือนี้ดูแลตั้งแต่การคัดลอกไฟล์จาก CD ต้นฉบับ, แตกไฟล์
.cab, ใช้แพตช์ทางการ v1.09, ข้ามการลงทะเบียน COM ของWOLAPI.DLLไปจนถึง UI ลอนเชอร์ Win32 สำหรับติดตั้งและรันเกม
จุดที่ Emperor: Battle for Dune ติดขัด
Emperor: Battle for Duneเป็นเกมวางแผนแบบเรียลไทม์ที่ Westwood Studios สร้างในปี 2001 และเป็นภาคต่อของDune 2000- บนระบบสมัยใหม่ยังมีปัญหาหลายจุดที่ขัดขวางการเล่น
- ไม่สามารถรันแบบ ความละเอียดสูง ให้เหมาะกับหน้าจอสมัยใหม่ได้
- ในมัลติเพลเยอร์ ความเร็วการจำลองเกมไม่ได้ถูกจำกัด ทำให้เกมเร็วเกินไป
- Westwood Online(WOL) ไม่ทำงานอีกแล้ว ทำให้มัลติเพลเยอร์นอก LAN ทำได้ยาก
- แคมเปญร่วมมือเป็นฟีเจอร์เฉพาะออนไลน์ จึงใช้บน LAN ไม่ได้
- โปรแกรมติดตั้งที่อยู่ในดิสก์เสีย
- เอฟเฟกต์ภาพหลายอย่างพังเมื่อรันบน PC สมัยใหม่ที่มีเฟรมเรตสูง
- EmperorLauncher เป็นแพตช์สำหรับแก้ปัญหาเหล่านี้ และมีไฟล์ดาวน์โหลดกับซอร์สโค้ดเผยแพร่แล้ว
การแทนที่ Emperor.exe และการเริ่มต้น Game.exe
Emperor.exeของเกมไม่ใช่ไฟล์รันเกมจริง แต่เป็น wrapper บาง ๆ ที่เปิดGame.exe- ถ้ารัน
Game.exeโดยตรงจะไม่มีอะไรเกิดขึ้น จึงต้องวิเคราะห์ขั้นตอน initialization ที่Emperor.exeเคยทำ แล้วจำลองใหม่ในลอนเชอร์ตัวแทน - ใช้ IDA ในการวิเคราะห์
- IDA สามารถ disassemble ไฟล์ executable และ decompile โค้ดบางส่วนให้อยู่ในรูปแบบ C ได้
- ต้องไล่ตามการเรียกฟังก์ชันและการใช้ Windows API จากไบนารีที่ข้อมูล type และข้อมูล struct หายไปแล้ว
- ก่อนรัน
Game.exe,Emperor.exeจะสร้าง mutex กับ handle ของ anonymous file mapping จากนั้นประมวลผลข้อมูลที่อ่านจากEmperor.datแล้วคัดลอกลง mapping - โปรเซสแม่ส่งข้อความ Windows ไปยัง ID ของเธรดหลักของโปรเซสลูกที่ได้จาก
CreateProcessAเพื่อส่งค่าของ file mapping handle- ใช้
0xBEEFเป็น ID ข้อความแบบกำหนดเอง - ข้อมูลใน file mapping คือสตริงสามตัว
"UIDATA,3DDATA,MAPS"และถูกส่งต่อให้โค้ดโหลด asset ของเกม
- ใช้
- แทนที่จะ reimplement โค้ดถอดรหัสเอง ผู้พัฒนาใส่เครื่องมือ dump แทนตำแหน่ง
Game.exeเพื่อบันทึกข้อมูลที่ถูกส่งมาลงดิสก์ แล้วให้ลอนเชอร์ทำ sequence เดียวกัน
วิธีฉีด DLL และแพตช์ฟังก์ชัน
- เพื่อใช้แพตช์ ต้องรันโค้ดของผู้ใช้ภายในโปรเซส
Game.exeและใช้วิธี CreateRemoteThread + LoadLibrary เพื่อทำสิ่งนี้ - ขั้นตอนการฉีดดำเนินตามลำดับนี้
- จัดสรร buffer ในหน่วยความจำของโปรเซสเป้าหมายด้วย
VirtualAllocEx - คัดลอกสตริง path ของ DLL ด้วย
WriteProcessMemory - ส่ง address ของ
LoadLibraryและ buffer path ของ DLL ให้CreateRemoteThreadเพื่อโหลด DLL ในโปรเซสเป้าหมาย - เมื่อ
DllMainของ DLL ทำงาน โค้ดแพตช์ก็เริ่มทำงาน
- จัดสรร buffer ในหน่วยความจำของโปรเซสเป้าหมายด้วย
- เริ่มโปรเซสในสถานะ suspended แล้วฉีด DLL เพื่อให้แน่ใจว่าโค้ดทำงานก่อน
main - ใช้ Microsoft Detours เพื่อแก้ไขฟังก์ชันเดิม
- Detours เปลี่ยนคำสั่งเริ่มต้นของฟังก์ชันเดิมเป็นคำสั่ง jump เพื่อส่งการเรียกไปยังฟังก์ชันทดแทน
- คำสั่งเดิมที่ถูกเขียนทับจะถูกคัดลอกไปยังหน่วยความจำแยกต่างหาก แล้วสร้าง wrapper ที่ jump ต่อไปยังตำแหน่งที่เหลือของฟังก์ชันเดิม ทำให้ยังเรียกฟังก์ชันต้นฉบับได้
- หน้าโค้ดของฟังก์ชันเขียนไม่ได้ด้วยเหตุผลด้านความปลอดภัย จึงต้องเปลี่ยน permission ด้วย
VirtualProtectและเรียกFlushInstructionCacheหลังแก้ไข
การกู้คืน debug log
- ในไบนารีมี call ที่ดูเหมือน debug log แต่ฟังก์ชันปลายทางจริงเป็นฟังก์ชันว่างที่มีแค่
ret - ดูเหมือนว่าใน release build มีฟังก์ชันว่างหลายตัวถูก merge ให้เป็นโค้ดเดียวกัน และหนึ่งในนั้นคือ debug logger
- ตอนแรกใช้ heuristic โดยตีความ argument แรกเป็น pointer ไปยังสตริง แล้วตรวจว่ามีอักขระ ASCII ที่พิมพ์ได้หรือไม่
- การเข้าถึง pointer ที่ผิดพลาดถูกจับและข้ามด้วย SEH exception ของ Windows
- วิธีนี้ใช้งานได้ในระดับหนึ่ง แต่ยังมี false positive และ false negative
- ต่อมาใช้ฟีเจอร์ patch ของ IDA และสคริปต์ Python เพื่อย้าย call site ของ log ไปยังฟังก์ชันว่างอีกตัวหนึ่ง
- บางส่วนหาได้ด้วย heuristic และบางส่วนหาจาก pattern ที่ push string constant แล้วตามด้วย call
- call site ที่เหลืออีกหลายร้อยจุดต้องใส่ comment ด้วยมือ
- log ที่กู้คืนได้ช่วยในการ debug มัลติเพลเยอร์ WOL
- ขณะประมวลผล
SC_MESSAGE_YOUR_DETAILSพบ assert log"MyId == INVALID_ID"และตรวจสอบจาก dump ของ Wireshark ได้ว่ากำลังส่งคำสั่งGAMEOPTไปยังผู้เล่นทุกคนอย่างผิดพลาด
- ขณะประมวลผล
แพตช์กราฟิก Direct3D 7
- Emperor เป็นเกมที่ใช้ Direct3D 7 และการรองรับ Direct3D 7 บน Windows สมัยใหม่ไม่สมบูรณ์
- ปัญหาความละเอียดสูงเกี่ยวข้องกับขีดจำกัดขนาด texture สูงสุด 2048 ในชั้น wrapper ของ Direct3D 7 และแก้ได้โดยใช้โค้ด LegacyD3DResolutionHack ของ UCyborg
- เกมจัดการหน้าจอที่ไม่ใช่อัตราส่วน 4:3 ได้ไม่ถูกต้อง
- ตัวการเรนเดอร์เองทำงานได้ แต่ UI พังเหมือนถูกขยายมากเกินไป
- offset การเรนเดอร์เมาส์ในเกมก็คลาดเคลื่อนตามระยะจากกึ่งกลางหน้าจอ
- วิธีแก้คือ letterboxing แบบ 4:3
- เมื่อรันเกมในโหมดหน้าต่าง จะใช้ความละเอียดใดก็ได้
- ลบสไตล์ขอบหน้าต่างออก แล้วกำหนด parent ของหน้าต่างเกมใหม่ให้อยู่บนหน้าต่างสีดำแบบเต็มจอ
- เพิ่ม mouse capture เพื่อไม่ให้ edge scrolling พังในโหมดหลายจอหรือโหมดหน้าต่าง
- การจำกัดเฟรมเรตทำโดยแพตช์
IDirect3DDevice7::EndSceneให้ตรงกับ 60 FPSEndSceneถูกเรียกหนึ่งครั้งตอนท้ายเฟรม จึงเหมาะกับการคำนวณเวลาหน่วงและ sleep เธรด- pointer ของ
EndSceneไม่ได้ export โดยตรง จึง hook การเรียกDirectDrawCreateExและIDirect3D7::CreateDeviceตามลำดับเพื่อดึง function pointer จาก vtable
มัลติเพลเยอร์ออนไลน์และการแทนที่ WOL
- เป้าหมายคือ มัลติเพลเยอร์แบบ IP โดยตรง ที่เชื่อมต่อได้ด้วย port forwarding และการกรอก IP โดยไม่ต้องมี lobby หรือโครงสร้างพื้นฐานสำหรับโฮสต์
- โหมด LAN ทำงานได้ แต่ใช้ UDP broadcast เพื่อหาเซิร์ฟเวอร์ จึงไม่เหมาะกับการเล่นผ่านอินเทอร์เน็ต
- เมนู LAN ไม่มีฟีเจอร์กรอก IP ด้วยมือ
- ตอนแรกลองแพตช์ LAN chat เพื่อระบุ IP แต่หยุดหลังพบว่าแคมเปญร่วมมือเป็นฟีเจอร์เฉพาะ WOL
- ถ้าจะชุบชีวิต WOL ต้องมีสองสิ่ง
- WOL master server ปลอม เพื่อให้เกมรู้ว่าต้องเชื่อมต่อที่ไหนและเริ่มเกมใด
- proxy เพื่อให้ packet ของเกมทำงานบนการเชื่อมต่อ IP โดยตรง
- โครงสร้าง WOL เดิมมี master server และเซิร์ฟเวอร์ “mangler” โดย mangler น่าจะทำหน้าที่ประสาน NAT punching
- เซิร์ฟเวอร์ mangler เดิมหายไปแล้ว และเกมค้างขณะรอ response ดังกล่าว
- ในแพตช์จึงลบการเรียก mangler ออก
- Emperor ใช้โมเดลเครือข่ายแบบ P2P เปิดการเชื่อมต่อสองทิศทางสำหรับผู้เล่นแต่ละคู่ และเลือกพอร์ตแบบสุ่ม
- เป็นโครงสร้างที่ไคลเอนต์ทุกคนต้องรับพอร์ตที่เปิดอยู่ จึงไม่เหมาะกับสภาพแวดล้อมอินเทอร์เน็ตสมัยใหม่
- วิธีแก้คือดักฟังก์ชัน winsock ทั้งหมด แล้ว tunnel การเชื่อมต่อทุกอย่างผ่าน การเชื่อมต่อเดียวแบบไคลเอนต์→เซิร์ฟเวอร์
- ดักข้อความที่ไคลเอนต์พยายามส่งไปยังเซิร์ฟเวอร์หรือไคลเอนต์อื่น ห่อด้วย header แล้วส่งผ่านการเชื่อมต่อเดียว
- เธรดฝั่งเซิร์ฟเวอร์รับข้อความแล้วกระจายไปยังปลายทาง
- เกมยังเชื่อว่าตัวเองทำงานแบบ P2P แต่ในความเป็นจริงมีเพียงโฮสต์เซิร์ฟเวอร์ที่ต้องจัดการการตั้งค่าเครือข่าย
- ด้วยโครงสร้างนี้ สามารถเริ่มและเข้าร่วมเกมแคมเปญร่วมมือได้
การทำเซิร์ฟเวอร์ WOL แบบง่าย
- WOL master server มีโครงสร้างใกล้เคียงกับเซิร์ฟเวอร์ IRC
xwis.netมีเซิร์ฟเวอร์ที่ดูเหมือนชุมชนแฟน ๆ เป็นผู้ดูแล และในเวลาที่เขียน ดูเหมือนว่าจะมีสิทธิ์เข้าถึง DNS entry ดั้งเดิมของเกมservserv.westwood.comด้วย- Emperor ไม่ได้ทำงานบน xwis ได้สมบูรณ์แบบเดิม แต่ใช้เป็นข้อมูลอ้างอิงสำหรับการสร้างและเข้าร่วม lobby ได้
- ใช้ implementation ของเซิร์ฟเวอร์ WOL แบบเปิดเผย handle_wol.cpp ของ pvpgn-server เป็นเอกสารอ้างอิงด้วย
- เหตุผลที่สร้างเซิร์ฟเวอร์เองคือไม่มีการรับประกันว่าเซิร์ฟเวอร์ภายนอกจะถูกดูแลต่อไป
- เป้าหมายไม่ใช่การดำเนินงานชุมชนแข่งขัน แต่คือการให้ฟังก์ชันขั้นต่ำที่จำเป็นต่อการรันเกมมัลติเพลเยอร์
- WOL ผสมระหว่าง IRC มาตรฐานกับพฤติกรรม custom
- lobby เกมเป็น channel พิเศษ
- ข้อมูล lobby ใช้ IRC topic
- แชตใน lobby ใช้
PAGEแทนPRIVMSG - การ sync การตั้งค่าเกมใช้ข้อความ
GAMEINFOที่มีเนื้อหาไม่ใช่ ASCII
- implementation พื้นฐานของเซิร์ฟเวอร์ WOL ทำสำเร็จด้วย trial and error และถึงแม้จะไม่ robust เมื่อออกนอกเส้นทางปกติ แต่ก็ใช้งานได้
โปรแกรมติดตั้งและการใช้แพตช์ v1.09
- โปรแกรมติดตั้งต้นฉบับเสีย ผู้ใช้จึงต้องเลี่ยงด้วยการคัดลอกเนื้อหา CD ลงฮาร์ดดิสก์แล้วเขียนทับด้วย setup ทดแทน
- EmperorLauncher มีฟีเจอร์ติดตั้งที่คัดลอกไฟล์จาก CD ต้นฉบับและแตกไฟล์
.cab.cabเป็นรูปแบบ archive คล้าย zip และ Windows มี interface สำหรับแตกไฟล์
- การใช้ v1.09 ซึ่งเป็นแพตช์ทางการตัวสุดท้ายยุ่งยากกว่า
- วิธีแตก
EM109EN.EXEด้วย 7zip แบบตรง ๆ เพื่อให้ได้ไบนารีล่าสุดใช้ไม่ได้ - พบ resource ขนาดใหญ่ใน Windows resource ที่เห็น header ของไฟล์ executable
- 4 ไบต์แรกของ resource นั้นคือขนาดไฟล์ และหลังจากนั้นคือไฟล์จริง
- วิธีแตก
EM109EN.EXEแตก DLL ที่ฝังอยู่เป็นไฟล์ชั่วคราวแล้วโหลด จากนั้นรันฟังก์ชันRTPatch32@12ภายใน DLLRTPatchเป็นเครื่องมือ binary diff patch- อ้างอิงเครื่องมือ myRTP เพื่อโหลดและรัน DLL ที่ฝังอยู่โดยตรง
- เนื่องจาก DLL อ่าน path จาก registry ไม่ใช่ path ติดตั้งที่รับมาเป็น argument จึงสร้าง registry key ปลอมตามที่คาดไว้เพื่อใช้แพตช์
การจัดการ Westwood Online Shared Internet Components
- การติดตั้งต้นฉบับมี Westwood Online Shared Internet Components แยกจากตัว Emperor หลัก
- หากไม่มี component นี้ WOL จะไม่ทำงาน และไฟล์หลักคือ
WOLAPI.DLL WOLAPI.DLLเป็น COM class library และ Emperor สร้าง COM object ภายใน DLL ด้วยCoCreateClass- การลงทะเบียน COM ปกติจะลงทะเบียน CLSID และ path ของ DLL แบบทั้งระบบไว้ใต้
HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID- วิธีนี้ต้องใช้สิทธิ์ผู้ดูแลระบบ
- ส่งผลต่อทั้งระบบเกินขอบเขตของโปรเซสเกม
- ในแพตช์ใช้ registry redirect และ
OaEnablePerUserTLibRegistrationเพื่อจัดการแบบลงทะเบียนรายผู้ใช้- ไม่พบวิธีลงทะเบียน class library เฉพาะในโปรเซสเดียว
- ความพยายามใช้
DllGetClassObjectโดยตรงไม่ทำงาน
UI ลอนเชอร์และผลลัพธ์สุดท้าย
- ขั้นตอนสุดท้ายคือ UI ลอนเชอร์เรียบง่ายสำหรับกรอก IP และเปลี่ยนการตั้งค่าพื้นฐาน
- UI เขียนด้วย Win32 controls แบบ raw
- เพียงพอสำหรับ UI แบบ static และเรียบง่าย แต่ประสบการณ์การทำ Win32 UI โดยตรงค่อนข้างสมบุกสมบัน
- สุดท้าย EmperorLauncher กลายเป็นเครื่องมือที่ครอบคลุมการรันบนระบบสมัยใหม่, ความละเอียดสูง, การจำกัด 60 FPS, มัลติเพลเยอร์แบบ IP โดยตรง, แคมเปญร่วมมือ รวมถึงการติดตั้งและการใช้แพตช์
ยังไม่มีความคิดเห็น