← back
sourcesniffer
closed source
Kotlin · Networking · Reverse Engineering · Protocol Research · Pcap4J · Compose Desktop
Overview
sourcesniffer is a low-level networking research framework implementing the Source Engine netchannel protocol from scratch in Kotlin. It enables real-time decoding, inspection, and manipulation of game traffic at the bit level. The project grew into a full engine-faithful reimplementation of the entire network stack — from raw UDP frames to structured entity state — paired with a live desktop UI built in Compose.
This entire project was developed and tested in local, controlled, and isolated environments to research video game networking protocols and security. The project contains no code that provides an unfair advantage in online play and cannot be used to monitor network communications without explicit permission.

Almost all of the (outdated) Source Engine network message protocol is documented in Valve's csgo-demoinfo.
Updated versions of the Source Engine protocol use asymmetric encryption, Perfect Forward Secrecy (PFS), IP protection, and several real-time authentication checks that make this class of research impossible on live infrastructure.
The video shows a Windows machine running Counter-Strike: Source (CS:S) streamed via Moonlight (client) and Sunshine (host).
My MacBook Air (M1) is running the proxy server in IntelliJ, forwarding all network packets to a local dedicated CS:S server running on the same Windows computer.
The game connects to the proxy; the proxy decodes all packets in real time and injects SVC_FixAngle messages into every server-bound datagram, overwriting the client's viewangles. This causes the client's camera to spin continuously without any code running on the Windows machine.
It is also possible to inject packets into an ongoing session on the same LAN without a proxy, using the passive sniffer path to infer sequence numbers. Using this technique I was able to inject chat messages from my MacBook directly into my isolated test server. This produced observable side effects — graphical glitches and sequence-ack desyncs — when spammed, providing useful data about how the engine handles out-of-order reliable state.
Motivation
The goal was to understand the Source Engine netchannel at the deepest practical level — not by reading documentation (little exists) but by observing live traffic, studying the open-source SDK, and reconstructing every field through experimentation. The protocol involves multiple overlapping encoding schemes (bit-aligned I/O, delta compression, varint encoding, Snappy frames) and a reliable/unreliable multiplexing layer that must be maintained with precise sequencing. Building a proxy that can intercept, decode, modify, and re-forward traffic without breaking the session is a strong correctness test.
System Design
Netchannel Layer
The netchannel sits immediately above UDP. Each datagram has a fixed header followed by an optional reliable subchannel block and a stream of back-to-back variable-length messages.
Header fields (in order):
Before the netchannel header, the engine wraps datagrams in one of two network-layer envelopes: split packets (magic -2, reassembled from up to N fragments of 1400 bytes each) and compressed packets (magic -3, Snappy payload). Connectionless queries carry magic 0xFFFFFFFF and are handled separately.
The flags byte has an undocumented dual use: the upper 3 bits encode a pad-bit count used to byte-align the message stream tail, overlapping with the CHALLENGE flag region. Correct reconstruction requires preserving this exactly.
Reliable Channel & Subchannels
Source's reliable layer sits inside the netchannel rather than in a separate TCP stream. It operates over 8 subchannels, each carrying fragments of 256 bytes. Two independent fragment streams exist: a general stream (index 0) and a file-transfer stream (index 1). Payloads exceeding 1024 bytes are optionally Snappy-compressed before fragmentation.
Message Types
Messages are prefixed with a 6-bit type ID. Bidirectional (NET_*) messages are shared; directional messages use separate namespaces.
Every message implements a readFromBuffer / writeToBuffer / process lifecycle. A captureBits mechanism snapshots the raw payload bits on read so a message can be re-emitted verbatim without needing a bit-perfect field encoder — critical for messages like SVC_PacketEntities where re-encoding every delta field exactly is impractical.
Entity Decoding
SVC_PacketEntities is the densest part of the protocol. The server sends compressed delta updates for every entity in the player's PVS each tick. Decoding requires a fully initialised send-table schema, matching the engine's internal flat property list exactly.
Send-table pipeline:
Delta decode (SVC_PacketEntities):
Bit I/O Layer
The engine operates on a bit stream, not a byte stream. All reads and writes are bit-aligned. The bitbuf/ package reimplements Source's bf_read and bf_write with word-aligned I/O and masking:
Packet Reconstruction
The proxy can forward a datagram via two distinct paths:
Choosing between paths requires knowing which messages are bit-sensitive. SVC_SendTable, SVC_ClassInfo, string table messages, and SVC_PacketEntities are all treated as verbatim-only. The captureBits / writeSavedBits pattern on each Message object makes this transparent to the caller.
Execution Modes
Live Desktop UI
A Compose Desktop application provides real-time visibility into the decoded protocol state:
Reactive state flows (EntityPropSession, EntityOriginSession, LocalPlayerSession) bridge the JVM network thread to the Compose UI thread without polling.
Protocol Injection Research
A core research goal was understanding which server-sent messages can be injected mid-session without desynchronising the client, and which cannot. Several message types were studied:
The framework was also used to test how the engine handles structurally invalid messages from a protocol-fuzzing perspective — malformed prop indices, oversized array counts, and out-of-bounds reads in the message parsing path.
Cryptographic RNG Research
The Source Engine uses an MD5-based pseudo-random number generator (MD5_PseudoRandom) seeded per client command for certain game mechanics. The crypto/ module reimplements this RNG exactly — matching the engine's byte-level output — to study its properties and period, and to verify that the command number field in CLC_Move is the sole entropy source for those mechanics.
Technical Challenges
What I Learned
Current Status
Future Work