Chơi co-op Wing Commander trên DOS sau 10 năm reverse
Một lập trình viên đã dành gần một thập kỷ reverse-engineering bản DOS của Wing Commander để biến nó thành game co-op hai phi công. Giờ đây trò chơi chạy trực tiếp trên trình duyệt nhờ emscripten, WebRTC và hệ thống lobby tự viết.

Sau gần một thập kỷ mày mò dịch ngược (reverse engineering) bản DOS của Wing Commander, một lập trình viên đã đưa chế độ chơi co-op hai phi công vào game. Thành quả giờ chạy được ngay trên trình duyệt, không cần cài đặt phức tạp.
Điều đáng chú ý không chỉ là một bản mod game cũ. Đây là câu chuyện về việc tìm ra vị trí chính xác của dữ liệu trong 640K bộ nhớ DOS, rồi chèn lệnh gọi hàm vào đúng chỗ để điều khiển một con tàu vũ trụ từ xa qua mạng.
Xuất phát từ một giấc mơ thời thơ ấu
Tác giả kể rằng hồi nhỏ, anh và em trai Patrick cùng chơi Wing Commander: một người điều khiển tay lái, người kia bắn laser và kích hoạt afterburner. Cả hai luôn mơ được chơi co-op với tư cách hai phi công wingman, thay vì "một con hydra hai đầu" ngồi chung một buồng lái.
Ngày 4/10/2015, khi đang chơi Wing Commander trong DOSBox, anh nhận ra toàn bộ bộ nhớ game có lẽ nằm gọn trong 640K — con số mà Bill Gates từng bị gán nhầm câu nói "thế là đủ cho mọi người". Ý tưởng nảy ra: nếu dump bộ nhớ và xác định được vị trí lưu tọa độ X, Y, Z của phi công và wingman, ta có thể dùng gói tin mạng để điều khiển wingman.
Minh họa mục tiêu của dự án
Bài toán đặt ra rất rõ: với chưa đến một triệu byte, làm sao tìm ra đúng địa chỉ đó. Anh chọn cách đơn giản hóa bằng việc "dọn sạch" các thực thể khác trong game. Một phi công đứng yên thì không nên thay đổi tọa độ, nên có thể chụp ảnh bộ nhớ hai lần để lọc ra phần "nhiễu nền". Sau đó tăng tốc về phía trước và đo lại.
Sau một nhiệm vụ thảm họa, để Paladin — người đồng đội — bị hạ bởi lũ mèo không gian rồi quay về tàu mẹ Claw, nhiệm vụ kế tiếp có một vùng không gian trống. Anh bay thẳng cho đến khi mọi thứ biến mất và dùng lệnh debug MEMDUMPBIN của DOSBox để chụp bộ nhớ.
Lọc ra 132 byte trong hơn một triệu
Phép giao hai tập dữ liệu cho ra 413 byte ứng viên. Kết hợp thêm góc nhìn camera bám đuôi (chase view), con số thu về chỉ còn 132 byte. Trong số 35 cụm byte thay đổi trong data segment, anh khoanh vùng được khoảng chục địa chỉ khả nghi.
Bước ngoặt đến từ địa chỉ 13D3:A9C2. Dùng lệnh SM của DOSBox để cộng số nguyên vào đó, con tàu lập tức nhảy sang vị trí mới. Kỹ thuật tương tự xác định được vận tốc tại CAE2, nơi có ba số nguyên 32-bit về 0 khi tàu dừng.
Ảnh chụp bộ nhớ khi tàu xoay hướng
Việc tìm vector xoay khó hơn nhiều. Các vector gồm các số vô hướng 4 byte, trong đó giá trị 256 tương ứng 1.0, nên phần lớn phép xoay nhỏ chỉ làm thay đổi một byte. May mắn đến từ một "trò đùa lập trình viên": Wing Commander dành 64 slot cho thuộc tính thực thể, đánh số 0x00 đến 0x3F. Nhưng nhóm phát triển lại dùng 0x3F cho biến tạm và đặt camera ở 0x3D — đúng kiểu chơi chữ "3D" thời đồ họa ba chiều còn sơ khai. Nhờ vậy, vector up và forward nằm sát nhau trong một vùng rộng, dễ khoanh vùng.
Từ watchpoint bộ nhớ, anh lần theo chuỗi hàm: hàm cộng vector, hàm vật lý theo từng tàu, hàm mỗi khung hình và cuối cùng là vòng lặp chính.
Không chỉ ghi bộ nhớ, mà gọi cả hàm gốc
Tháng 12/2016 phá vỡ ảo tưởng rằng chỉ cần ghi vào bộ nhớ toàn cục là điều khiển được game. Việc tạo ra tia laser hay tên lửa nhiệt quá phức tạp để "múa rối" chỉ bằng địa chỉ. Nhóm phải mượn chính các hàm của game gốc.
Khó khăn nằm ở chỗ nhiều hàm nằm trong các overlay — cơ chế của trình biên dịch Borland, mỗi đơn vị biên dịch (file C++) có một overlay riêng, được nạp động vào vị trí khác nhau tùy đường đi của người chơi trước đó. Giải pháp là hệ thống trampoline: dùng chuỗi "Loading WING COMMANDER" tại 13D3:0187 làm chỗ đặt mã, đẩy CS:IP vào stack, rồi trỏ con trỏ lệnh tới đó. Đoạn mã chèn gọi hàm qua stub, chạy xong thì retf quay về đúng chỗ game đang dừng.
Một lỗi dai dẳng từ tháng 10 đến tháng 12/2017: khi overlay loader của Borland hoán đổi trang mã, nó duyệt chuỗi frame BP trên stack và vá các địa chỉ trả về trỏ vào overlay. Stub thủ công không có frame nên quá trình duyệt đọc phải rác, khiến game chết ngẫu nhiên. Cách sửa là làm cho trampoline trông giống hàm Borland chuẩn với push bp, mov bp, sp, một lệnh nop, rồi pop bp, retf.
Mạng, đồng bộ slot và chat trong buồng lái
Tháng 8/2017, nhóm viết giao thức mạng: kiến trúc TCP client/server đơn giản với message protobuf. Mỗi khung hình, vị trí mọi con tàu được gửi từ máy chủ kèm sự kiện bắn, biến mất và sát thương; client phản hồi vị trí của mình cùng các sự kiện như phóng ghế, hạ cánh hay tử trận.
Vấn đề nảy sinh khi vật thể tạm thời nằm ở slot khác nhau trên từng máy. Nhóm sửa vị trí spawn để gom chúng vào slot 12–15, rồi tháng 12/2017 chuyển client sang lưu bảng ánh xạ ID tàu giữa server và máy cục bộ. Nhờ vậy thiên thạch (asteroid) mới hoạt động được trong chế độ nhiều người.
Năm 2019, để tìm điểm tính thắng thua đầy khó chịu, họ chép toàn bộ data segment 64K qua mạng rồi chia đôi dần. Sau nhiều lần chia, họ khoanh được 80 byte tại 13D3:D248 chứa cây nhiệm vụ, và tới tháng 10/2019 tìm ra bộ đếm điểm thắng tại 13D3:C280 cùng số nhiệm vụ tại C255 và C256 — về sau cho phép chọn nhiệm vụ từ dropdown.
Đầu năm 2020, họ xác định được hàm phát comm text, từ đó gửi chat qua mạng và hiển thị trên màn hình VDU của wingman.
Giao diện chat giữa hai phi công
Emscripten, WebRTC và lobby tự viết
Đến năm 2026, ba yếu tố hội tụ: WebRTC phổ biến rộng rãi, emscripten hỗ trợ ổn định, và hệ thống lobbylink do chính tác giả xây dựng. Đây là một lobby server viết bằng Go, chỉ phụ thuộc thư viện websocket, điều phối các kết nối WebRTC ngang hàng. Server chạy được trên máy chủ nhỏ, có allowlist origin tĩnh, và dùng mã phòng (room code) để ghép người chơi. Nó cũng chạy coturn và cấp thông tin xác thực TURN trong phản hồi join, giúp người sau tường lửa khắt khe vẫn kết nối được.
Tác giả chuyển phần transport sang lobbylink, rồi nhờ một LLM cấu hình build script nhắm tới emscripten, đồng thời giải nén gói cài đặt Inno Setup của Wing Commander từ GOG để dùng làm file dữ liệu. Kết quả: tải game từ GOG, kéo thả vào bộ nhớ trình duyệt, nhập mã phòng là chơi được — có cả voice chat qua kết nối WebRTC thứ hai nếu người chơi muốn.
Góc nhìn
Với các kỹ sư phần mềm Việt Nam, dự án này là bài học thực chiến về kỹ thuật reverse engineering và tư duy hệ thống. Việc chia đôi bộ nhớ để tìm vị trí dữ liệu, hay đoán ý đồ lập trình viên qua cách đánh số slot, đều là những phương pháp có thể áp dụng khi phải làm việc với hệ thống cũ không có mã nguồn — tình huống không hiếm trong các dự án bảo trì nội bộ.
Câu chuyện cũng cho thấy một ý tưởng hay có thể "ngủ đông" nhiều năm và sống lại khi hạ tầng đã sẵn sàng. WebRTC và emscripten đã biến một thứ trước đây đòi hỏi cấu hình mạng thủ công thành trải nghiệm chỉ cần mở trình duyệt. Và việc dùng LLM để viết build script, cùng với hàng chục năm kinh nghiệm đọc mã assembly, cho thấy AI đang hữu ích nhất khi kết hợp với người hiểu rõ gốc rễ vấn đề.
Nguồn: fireflyk64.github.io · Quy trình biên tập
Bài viết liên quan

Công nghệ
Tự chơi Contra NES ngay trên trình duyệt: dự án port từ hợp ngữ 6502 sang C rồi WebAssembly
01 tháng 11, 2026

Phần mềm
Dungeons & Desktops: Biến mã nguồn thành hầm ngục Roguelike với GitHub Copilot CLI
12 tháng 5, 2026

Chuyển game Amiga 1993 của tôi sang Godot, với LLM đọc assembly 68000
03 tháng 9, 2026