Engenharia reversa · ProjectG.exe

PangYa Packet Map

Confirmado no IDA 500 server→client 348 client→server 3 fontes cruzadas

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.

500Server → Client
348Client → Server
9Dispatchers
40Fora do enum (2 direções)

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çãoClasse (RTTI)OpcodesO que cobre
sub_9C20A0handler comum275Fallback de todo mundo. Sozinho cobre mais da metade do protocolo.
sub_69EEF0CGolfTask90Tudo que acontece dentro da partida: tacada, vento, turno, resultado.
sub_AC03A0CLobbyTask70Lobby, salas, canais — e também o socket do Login Server.
sub_7B5A20ntAvatarChatTask12Lounge / chat com avatar.
sub_5A8360CMyRoomWidget27MyRoom: mobília, mascote, armário.
sub_5E0FF0CShopWidget7Loja.
sub_6B2EE0CTutorialWidget2Tutorial.
sub_82C830CGhostTask1Matchmaking (0x13A).
sub_B1C5D0CFamilyTask1Guild/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çãoLê
sub_A4DEB0u8byte / bool
sub_A4DEC0u16é a que lê o opcode
sub_A4DED0u32inteiro / ID
sub_A4DEF0strstring
sub_A4DF00bufbloco 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:

connIdslotClasse (RTTI)ServidorEnvios provados
00x30GameUnitGame Server533 sites · 252 opcodes
10x34MSNUnitMessage Server3 sites · 0x29, 0x3C
30x38LoginUnitLogin Servernenhum com constante
40x3CRankUnitRank Servernenhum com constante
2 e outros——não existecai 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.

Uma confirmação que caiu do céu: o 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.
Confirmado com o jogo rodando. Um breakpoint de hardware em 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.

0 de 0 pacotes
Opcode Dir. Nome Cliente / Payload Status Descrição
carregando mais pacotes…
Role dentro do painel para carregar o resto — 15 por vez.

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:

25

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.

73

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.

91

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.

Cuidado: opcode sozinho não identifica um pacote — precisa saber de qual socket ele veio. 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 do CLobbyTask, que é quem desenha a tela de seleção de servidor.
  • Message Server → cliente: 0x2F 0x30 0x3B 0x3C 0x40. O 0x30 lê 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 →

ValorTratado porPayloadProvável origem
0x01Lobby—Login Server
0x02Lobby—Login Server
0x03Lobbyu32Login Server
0x04Lobbyu8Login/Message
0x05Lobbyu8 u32 u32Login/Message
0x06LobbybufLogin Server
0x07Lobbyu32Login/Message
0x08Lobbyu32Login/Message
0x09Lobbyu8Login Server
0x0BLobbystrLogin/Message
0x0DLobbyu32 u32 strLogin/Message
0x0ELobbyu32 u32 strLogin Server
0x0FLobbyu8 strLogin Server
0x10Lobby—Login Server
0x11Lobbyu8 u32Login Server
0x2FComumu8 u32Message Server
0x30Comumu16 u8 u32 str u32 u32 buf u32 u8 u32 u32 u32 u32 u32Message Server
0x32Comum—Login/Message
0x33Comum—Login/Message
0x34Comumu32Login/Message
0x37Lobbyu16 u32 u8 u16 u32 u32 u16 u32Login/Message
0x38Comum—Login/Message
0x39Comum—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.