PangYa Packet Map
Referência completa dos opcodes do Game Server. Cada linha foi checada contra o binário do cliente no IDA Pro, contra um servidor que roda de verdade, e só então contra o enum público da comunidade — que, como dá pra ver na seção de divergências, erra em bastante coisa.
Como o cliente despacha os pacotes
O cliente não tem um switch gigante. Cada task/widget ativo tenta tratar o pacote
primeiro e, se não conhecer o opcode, rebobina o cursor e cai no handler comum. Os nove
dispatchers foram encontrados por cross-reference ao global dword_F02494
— o "último opcode recebido" que aparece no log de crash. O nome de cada classe saiu do RTTI.
| Função | Classe (RTTI) | Opcodes | O que cobre |
|---|---|---|---|
sub_9C20A0 | handler comum | 275 | Fallback de todo mundo. Sozinho cobre mais da metade do protocolo. |
sub_69EEF0 | CGolfTask | 90 | Tudo que acontece dentro da partida: tacada, vento, turno, resultado. |
sub_AC03A0 | CLobbyTask | 70 | Lobby, salas, canais — e também o socket do Login Server. |
sub_7B5A20 | ntAvatarChatTask | 12 | Lounge / chat com avatar. |
sub_5A8360 | CMyRoomWidget | 27 | MyRoom: mobília, mascote, armário. |
sub_5E0FF0 | CShopWidget | 7 | Loja. |
sub_6B2EE0 | CTutorialWidget | 2 | Tutorial. |
sub_82C830 | CGhostTask | 1 | Matchmaking (0x13A). |
sub_B1C5D0 | CFamilyTask | 1 | Guild/família (0xAF). |
Formato do pacote
O opcode é um u16 little-endian no começo do payload em texto claro. O corpo vai empacotado/cifrado por cima disso. Tudo aqui descreve o payload já decifrado.
Envio no cliente: sub_A4E800 é o
WSendPacket::WSendPacket(u16 opcode). Todo ponto do binário que
chama isso com uma constante entrega um opcode client→server — foi assim que os
348 saíram.
De onde vem a coluna Payload
É a sequência de leituras que o handler faz no pacote, extraída direto do fluxo de controle no IDA:
| Função | Lê | |
|---|---|---|
sub_A4DEB0 | u8 | byte / bool |
sub_A4DEC0 | u16 | é a que lê o opcode |
sub_A4DED0 | u32 | inteiro / ID |
sub_A4DEF0 | str | string |
sub_A4DF00 | buf | bloco binário (buf, len) |
sub_A4DF70 | — | rebobina o cursor pro início |
Do lado do envio: o cliente escolhe o socket na mão
Depois de montar o pacote com WSendPacket, o cliente chama
sub_A4DAB0(connId) — o Send. Ele não
manda pra "a conexão": indexa um array de 4 conexões guardado na classe
WNetworkSystem (global dword_E73E5C,
construtor sub_A4D760). E cada slot recebe uma classe
diferente — os nomes saíram do RTTI, não de dedução:
| connId | slot | Classe (RTTI) | Servidor | Envios provados |
|---|---|---|---|---|
0 | 0x30 | GameUnit | Game Server | 533 sites · 252 opcodes |
1 | 0x34 | MSNUnit | Message Server | 3 sites · 0x29, 0x3C |
3 | 0x38 | LoginUnit | Login Server | nenhum com constante |
4 | 0x3C | RankUnit | Rank Server | nenhum com constante |
2 e outros | — | — | não existe | cai no default: não envia nada |
Ou seja: o socket de destino é uma constante no próprio código. E existem
duas cópias do construtor — sub_A4E800 e
sub_A4E8A0, idênticas (mesmo vtable, mesmo impl; o compilador
duplicou). Somando as duas dá 614 pontos de envio, dos quais 595 (97%) estão
atribuídos. 252 dos 256 opcodes client→server têm pelo menos um envio provado
no GameUnit; na tabela eles aparecem com a etiqueta
socket 0.
A segunda cópia é a que explica tudo: Login, MSN e Rank usam exclusivamente ela. Como
eu só tinha enumerado a primeira, nenhum envio de Login aparecia — parecia que o
LoginUnit não era usado. Com as duas, os 66 call sites da segunda
fecham 100%: 7 opcodes pro Login, 23 pro MSN e 2 pro Rank.
0x3C é
CLIENT_MSN_REQUEST no enum da comunidade — e é justamente um dos dois opcodes
que o binário prova irem pro MSNUnit. O nome que alguém deduziu há
anos bate com o socket que o código escolhe.
sub_A4DAB0 capturando o endereço de retorno e o argumento pegou os
disparos reais, e eles bateram com a análise estática — inclusive o
0xF4 (heartbeat) saindo a cada 10 s numa thread própria, pelo
GameUnit. Foi essa captura que revelou a segunda cópia do construtor.
O RankUnit nunca disparou: o servidor de ranking não existe no
SuperSS, então esse socket não chega a conectar.
Ver Login & MSN →
Tabela de pacotes
Busque por opcode (0x064), por nome (SHOT)
ou por qualquer palavra da descrição.
| Opcode | Dir. | Nome | Cliente / Payload | Status | Descrição |
|---|
Divergências com o enum da comunidade
O enum público foi tratado como hipótese, não como verdade. Cruzando com o binário, deu nisso:
Server→client que não estão no enum
Server→client que o cliente trata e ninguém nomeou:
0x1AC, 0x1DD–0x1E0,
0x21F–0x224,
0x277–0x27D e outros. Filtre por
Fora do enum pra ver todos com endereço de handler.
Estão no enum, o cliente ignora
Opcode de outra season ou de outra região (KR/JP) que este cliente não tem. Mandar um
desses não faz nada: cai no default e é descartado. Filtre por
Não implementado.
Client→server que o cliente nunca monta
Estão listados no enum mas não existe nenhum WSendPacket com esse
opcode no binário inteiro. Ou é legado, ou é de um cliente diferente.
Opcodes abaixo de 0x3F
Os mesmos dispatchers atendem também os sockets do Login Server e do Message Server (MSN/amigos), que têm espaço de opcode próprio e começam do zero.
0x12–0x2D, por exemplo, significam
coisas completamente diferentes no Game Server e no Message Server.
- Login Server → cliente:
0x01 0x02 0x03 0x06 0x09 0x0E 0x0F 0x10 0x11— batem com o bloco baixo doCLobbyTask, que é quem desenha a tela de seleção de servidor. - Message Server → cliente:
0x2F 0x30 0x3B 0x3C 0x40. O0x30lêu16 u8 u32 str u32 u32 buf…, que é a entrada da lista de amigos. - Alguns cases não leem nada do corpo — só trocam de formulário na UI. São notificações sem payload.
Ver o protocolo completo do Login & Message Server →
| Valor | Tratado por | Payload | Provável origem |
|---|---|---|---|
0x01 | Lobby | — | Login Server |
0x02 | Lobby | — | Login Server |
0x03 | Lobby | u32 | Login Server |
0x04 | Lobby | u8 | Login/Message |
0x05 | Lobby | u8 u32 u32 | Login/Message |
0x06 | Lobby | buf | Login Server |
0x07 | Lobby | u32 | Login/Message |
0x08 | Lobby | u32 | Login/Message |
0x09 | Lobby | u8 | Login Server |
0x0B | Lobby | str | Login/Message |
0x0D | Lobby | u32 u32 str | Login/Message |
0x0E | Lobby | u32 u32 str | Login Server |
0x0F | Lobby | u8 str | Login Server |
0x10 | Lobby | — | Login Server |
0x11 | Lobby | u8 u32 | Login Server |
0x2F | Comum | u8 u32 | Message Server |
0x30 | Comum | u16 u8 u32 str u32 u32 buf u32 u8 u32 u32 u32 u32 u32 | Message Server |
0x32 | Comum | — | Login/Message |
0x33 | Comum | — | Login/Message |
0x34 | Comum | u32 | Login/Message |
0x37 | Lobby | u16 u32 u8 u16 u32 u32 u16 u32 | Login/Message |
0x38 | Comum | — | Login/Message |
0x39 | Comum | — | Login/Message |
O que ficou em aberto
- Os 25 opcodes fora do enum estão confirmados, mas o handler só repassa o valor pra UI sem nenhuma string identificável. Pra nomear cada um dá pra subir o servidor e disparar a feature com o sniffer ligado.
- A coluna Payload mostra só o prefixo lido no primeiro bloco de código — laços de leitura (listas) aparecem truncados.