# Adding New Packet Parsers This guide explains how to implement parsers for new GCNet packet types. ## Architecture Overview The packet parsing system uses several design patterns: ``` ┌─────────────────────────────────────────────────────────────────┐ │ GCNetPacketProcessor │ │ ┌─────────────────────────────────────────────────────────────┐│ │ │ PacketParserFactory (Factory) ││ │ │ ┌────────────┐ ┌────────────┐ ┌──────────────────────────┐ ││ │ │ │ PingParser │ │ PongParser │ │ VerifyAccountRequestParser│ ││ │ │ └────────────┘ └────────────┘ └──────────────────────────┘ ││ │ │ Strategy Pattern ││ │ └─────────────────────────────────────────────────────────────┘│ └─────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ PacketContext │ │ • decrypted payload bytes │ │ • TCP segment (src/dst IP:port) │ │ • server port for direction detection │ └─────────────────────────────────────────────────────────────────┘ ``` ### Design Patterns Used | Pattern | Class | Purpose | |---------|-------|---------| | **Strategy** | `PacketParser` interface | Each opcode has its own parsing strategy | | **Factory** | `PacketParserFactory` | Creates the correct parser for each opcode | | **Context** | `PacketContext` | Immutable context with payload + connection metadata | | **Registry** | `GCNetOpcode` enum | Maps opcode numbers to metadata and direction hints | | **Null Object** | `GenericPayloadParser` | Fallback for unimplemented opcodes | ## Step-by-Step: Adding a New Parser ### 1. Define the Opcode Add the opcode to `GCNetOpcode.java`: ```java // In packets.com.gcpp.GCNetOpcode /** * Example: Player join request. * Structure: Player name (string), Character class (int), Level (short) */ ENU_PLAYER_JOIN_REQ(50, "ENU_PLAYER_JOIN_REQ", "Player join request", Direction.CLIENT_TO_SERVER), ``` The `Direction` enum indicates expected traffic flow: - `CLIENT_TO_SERVER` — Sent from client **to** server port (dstPort == serverPort) - `SERVER_TO_CLIENT` — Sent from server **from** server port (srcPort == serverPort) - `BIDIRECTIONAL` — Valid both ways (e.g., PING/PONG) ### 2. Create the Parser Create a new class in `packets/parsers/`: ```java package com.gcnet.decryptor.packets.parsers; import packets.com.gcemu.gcpp.GCNetOpcode; import packets.com.gcemu.gcpp.PacketContext; import packets.com.gcemu.gcpp.PacketParser; import java.nio.ByteBuffer; import java.util.LinkedHashMap; import java.util.Map; /** * Parser for ENU_PLAYER_JOIN_REQ packets (opcode 50). * *

Packet Structure:

* * * * * * * * * *
OffsetSizeTypeDescription
02ushort (BE)Opcode (50)
24int (BE)Content size
61boolCompression flag
74int (LE)Player name byte length
11varstring (UTF-16LE)Player name
var4int (LE)Character class ID
var2short (LE)Character level
* *

Direction: {@link GCNetOpcode.Direction#CLIENT_TO_SERVER}

*/ @PacketParser.PacketParserFor(opcode = 50) public class PlayerJoinRequestParser implements PacketParser { private static final int CONTENT_OFFSET = 7; @Override public ParseResult parse(PacketContext context) { byte[] payload = context.decryptedPayload(); if (payload.length < CONTENT_OFFSET + 10) { return ParseResult.empty(GCNetOpcode.ENU_PLAYER_JOIN_REQ, "Client → Server"); } int offset = CONTENT_OFFSET; Map fields = new LinkedHashMap<>(); // Parse player name ByteBuffer bb = ByteBuffer.wrap(payload, offset, 4) .order(java.nio.ByteOrder.LITTLE_ENDIAN); int nameLen = bb.getInt(); offset += 4; if (offset + nameLen <= payload.length) { String playerName = new String(payload, offset, nameLen, java.nio.charset.StandardCharsets.UTF_16LE); fields.put("Player Name", playerName); offset += nameLen; } // Parse character class bb = ByteBuffer.wrap(payload, offset, 4) .order(java.nio.ByteOrder.LITTLE_ENDIAN); int charClass = bb.getInt(); fields.put("Character Class", String.valueOf(charClass)); offset += 4; // Parse level bb = ByteBuffer.wrap(payload, offset, 2) .order(java.nio.ByteOrder.LITTLE_ENDIAN); short level = bb.getShort(); fields.put("Level", String.valueOf(level)); return ParseResult.parsed( GCNetOpcode.ENU_PLAYER_JOIN_REQ, "Client → Server", "ENU_PLAYER_JOIN_REQ { name: string_utf16_le, class_id: int32, level: int16 }", fields, formatContentHex(payload), "player=\"" + fields.getOrDefault("Player Name", "?") + "\"" ); } private String formatContentHex(byte[] data) { var sb = new StringBuilder(); for (int i = 0; i < data.length; i++) { if (i % 16 == 0 && i > 0) sb.append("\n"); sb.append(String.format("%02X ", data[i])); } return sb.toString().trim(); } } ``` ### 3. Register the Parser Add registration in `PacketParserFactory.registerBuiltInParsers()`: ```java private void registerBuiltInParsers() { // ... existing registrations ... // Player Management registerParser(50, new PlayerJoinRequestParser()); registerParser(51, new PlayerJoinAckParser()); } ``` ### 4. Direction Detection The `PacketContext` automatically determines direction: ```java public boolean isClientToServer() { return segment.dstPort() == serverPort; // Destination is the server } public boolean isServerToClient() { return segment.srcPort() == serverPort; // Source is the server } ``` For a server on port 9501: - `10.0.1.10:51094 → 10.0.1.10:9501` → **Client → Server** (dstPort == 9501) - `10.0.1.10:9501 → 10.0.1.10:51094` → **Server → Client** (srcPort == 9501) ## File Structure ``` src/main/java/com/gcnet/decryptor/packets/ ├── GCNetOpcode.java ← Opcode registry (add new opcodes here) ├── PacketContext.java ← Immutable context object ├── PacketParser.java ← Strategy interface + annotation ├── PacketParserFactory.java ← Factory (register new parsers here) └── parsers/ ├── GenericPayloadParser.java ← Fallback for unknown opcodes ├── KeyExchangeParser.java ← Opcode 1 ├── PingParser.java ← Opcode 39 ├── PongParser.java ← Opcode 40 ├── VerifyAccountRequestParser.java ← Opcode 34 └── VerifyAccountAckParser.java ← Opcode 35 ``` ## Testing Run the decryptor with your capture file: ```bash java -jar target/gcnet-decryptor-1.0.0-jar-with-dependencies.jar capture.pcapng 9501 ``` New parsers will automatically be invoked for their registered opcodes, producing structured field output instead of raw hex dumps.