Bỏ qua để đến nội dung

Kiến trúc

Stack chia làm ba phần: tầng game viết bằng C++ không giữ state, tầng Java giữ toàn bộ state, và hai dịch vụ nhỏ tự viết mà bản backup gốc thiếu.

flowchart LR
  subgraph may["Máy người chơi"]
    launcher["launcher"]
    clientexe["client.exe"]
  end

  subgraph native["Tầng C++, không giữ state"]
    login["loginserver"]
    proxy["proxyserver"]
    channel["channelserver ×4"]
    gameserver["gameserver, mỗi trận một tiến trình"]
  end

  subgraph ours["Hai dịch vụ tự viết"]
    authsvc["auth (SOAP)"]
    patch["patch host"]
  end

  subgraph java["Tầng Java, giữ toàn bộ state"]
    go["go"]
  end

  mysqlauth[("MySQL.auth")]
  mysqlgo[("MySQL.go")]

  launcher -->|9531| login
  launcher -->|8899| patch
  launcher -.->|ticket| clientexe
  login --> authsvc
  authsvc --> mysqlauth
  login -->|9530| proxy
  clientexe -->|9500| proxy
  proxy -->|8082| go
  proxy -->|9501| channel
  channel --> gameserver
  channel -->|8082| go
  go --> mysqlgo

Launcher gọi login server ở 9531 rồi nhận về một ticket. client.exe cầm ticket đó nối vào proxy ở 9500. Bốn channel server ở 9511-9514 lo các phòng, mỗi trận đẻ ra một gameserver riêng.

Cả tầng này không giữ state. Người chơi, item, quest, shop, guild, ranking, mail, đấu giá đều nằm ở tầng Java (server/go). Tầng C++ gọi sang đó bằng RPC trên 8082, còn MySQL thì thuộc về tầng Java.

Hai dịch vụ còn lại không có trong backup nên phải tự viết: credential service để login server gọi thay vì tin lời client, và patch host để launcher lấy manifest.

Đo bằng netstat -tlnp trong container ngày 2026-08-07. Tầng C++ mở mười một listener, bản chỉ phục vụ người chơi chỉ publish bảy trong số đó.

Cổng Process Vì sao cần đến được
843 authenticationserver Flash socket-policy server
9500 proxyserver điểm vào của client
9505 socialserver bạn bè và social, có token gate từ proxy
9511-9514 channelserver ×4 bốn kênh game
9531 loginserver phía client cấp ticket
8899 patch host RemoteURL trong launch.ini

Cổng 8899 nằm ngay trên đường đăng nhập chứ không phải thứ có cũng được. Bấm LOGIN là kéo ServerList/recommend.xml, bấm ENTER là kéo GameUpdate/update.xml. Không tới được cổng này thì launcher bắn CURLE_COULDNT_CONNECT và game không khởi động nổi, nên nó cần uptime ngang login server.

Nó cũng phải giữ nguyên HTTP thường. libcurl trong launcher không có TLS, đưa https:// vào là nhận CURLE_UNSUPPORTED_PROTOCOL.

Cổng Process Kẻ tấn công lấy được gì
8082 infoserver (Java) trọn bộ servlet bus. Mọi bean s_* nằm ở đây, kể cả gmUserService. Nối thẳng vào 8082 là đi vòng qua proxy, mất luôn ranh giới RPC
8080 infoserver web (Jetty) GM console: sửa người chơi, ban, và một màn CRUD cho mọi bảng nội dung
9560 watchserver phía game luồng frame của trận đang diễn ra. Không auth, không token
9550 watchserver phía client spectate, chỉ GM dùng được. Publish khi nào thật sự deploy client GM, và firewall về đúng địa chỉ GM
9501-9504, 9506, 9520, 9530 proxy nội bộ, apexproxy, matchingserver, loginserver phía proxy đã bind 127.0.0.1 theo as_config. Đừng publish, và đừng sửa thành 0.0.0.0
3306, 11211, 8090 MySQL, memcached, auth SOAP chỉ dùng ở back-end

Trong stack Docker thì chính các container đóng vai firewall: chỉ publish cổng phía người chơi, còn lại để hết trên bridge nội bộ asnet, nơi as_config gọi nhau bằng IP container.

Muốn kiểm một bản deploy thì quét cổng từ ngoài vào. Đúng ra chỉ mở 843, 9500, 9505, 9511, 9512, 9513, 9514, 9531, 8899. Thấy thêm cổng nào là sai.

Client, mười một server C++ và tầng Java đều là binary gốc của nhà phát hành. Thứ dựng lại chỉ gồm database nội dung, hai dịch vụ còn thiếu, và đống cấu hình để cả bộ khởi động được.

Cái đó chi phối mọi cách làm trong tài liệu này. Không vá được client thì thêm nội dung phải đi qua đúng những kênh client vốn đã tin: delta.dat cho tài nguyên, các bảng SYS_* cho phần còn lại.

Bản nháp đang review. Nội dung nội bộ, đừng chia sẻ ra ngoài nhóm.