Architecture
This document provides an overview of PadRelay’s internal architecture and design.
System Overview
PadRelay is a client-server application that transmits gamepad input across a network. The architecture is designed for:
Low latency: Minimal processing between input capture and virtual gamepad output
Reliability: Connection management, rate limiting, and error recovery
Security: Authentication, optional TLS encryption, and input validation
Flexibility: Support for TCP/UDP, multiple gamepad types, and custom mappings
Architecture Diagram
┌─────────────────┐ ┌──────────────────┐
│ Physical │ │ Windows PC │
│ Gamepad │ │ │
└────────┬────────┘ └────────┬─────────┘
│ │
│ USB / Bluetooth │ ViGEmBus
│ │
v v
┌─────────────────┐ ┌──────────────────┐
│ │ │ │
│ PadRelay │ Network │ PadRelay │
│ Client │◄─────────────────────────►│ Server │
│ (Any OS) │ TCP/UDP + TLS │ (Windows) │
│ │ │ │
└─────────────────┘ └──────────────────┘
│ │
│ │
v v
┌─────────────────┐ ┌──────────────────┐
│ pygame │ │ vgamepad │
│ Input │ │ Virtual │
│ Capture │ │ Gamepad │
└─────────────────┘ └──────────────────┘
Component Architecture
Client Components
Input Layer (
padrelay.client.input)Captures gamepad input using pygame
Polls button states, axis positions, and D-pad directions
Normalizes input values to standard ranges
Client Application (
padrelay.client.client_app)Manages connection lifecycle
Handles authentication flow
Sends input messages at configured rate
Implements reconnection logic
Manages heartbeat
Protocol Layer (
padrelay.protocol)Message serialization/deserialization
Protocol version negotiation
Message validation
Server Components
Server Application (
padrelay.server.server_app)Accepts client connections
Manages authentication
Receives and processes input messages
Enforces rate limits
Manages virtual gamepad lifecycle
Input Processor (
padrelay.server.input_processor)Translates network messages to gamepad commands
Maps physical buttons to virtual buttons
Applies axis transformations (dead zones, inversion)
Handles trigger mappings
Virtual Gamepad (
padrelay.server.virtual_gamepad)Interfaces with vgamepad library
Creates Xbox 360 or DualShock 4 virtual controllers
Updates button and axis states
Manages gamepad lifecycle
Communication Flow
TCP Connection Flow
Connection Establishment
Client Server │ │ │───── TCP Connect ─────────────►│ │ │ │◄──── TLS Handshake ───────────►│ (if enabled) │ │ │◄──── Auth Challenge ───────────│ │ │ │───── Auth Response ───────────►│ │ │ │◄──── Auth Success ─────────────│ │ │
Input Transmission
Client Server │ │ │───── Input Message ───────────►│ (at update_rate Hz) │ │ │───── Heartbeat ───────────────►│ (periodic) │◄──── Heartbeat Ack ────────────│ │ │
UDP Connection Flow
Authentication
Client Server │ │ │───── Auth Params Request ─────►│ (if needed) │◄──── Auth Params ──────────────│ │ │ │───── Input + Token ───────────►│ (HMAC-based token) │ │
Input Transmission
Client Server │ │ │───── Input + Token ───────────►│ (at update_rate Hz) │ │
Message Format
All messages are JSON-encoded with the following base structure:
{
"type": "message_type",
"protocol_version": "1.0",
"timestamp": "2025-01-06T10:30:00.123456"
}
Input Message
{
"type": "input",
"protocol_version": "1.0",
"timestamp": "2025-01-06T10:30:00.123456",
"buttons": [0, 1, 4],
"axes": [0.0, -0.5, 0.8, 0.0],
"hats": [[0, 1]],
"triggers": {
"left": 0.0,
"right": 0.7
},
"token": "hmac_value" // UDP only
}
Authentication Messages
Challenge:
{
"type": "auth_challenge",
"protocol_version": "1.0",
"timestamp": "2025-01-06T10:30:00.123456",
"challenge": "random_bytes_hex",
"salt": "salt_hex",
"iterations": 100000
}
Response:
{
"type": "auth_response",
"protocol_version": "1.0",
"timestamp": "2025-01-06T10:30:00.123456",
"response": "hmac_hex"
}
Security Architecture
Authentication
TCP: Challenge-response authentication
Server generates random challenge
Server sends challenge + hash parameters (salt, iterations)
Client computes HMAC using password-derived key
Server verifies HMAC
Connection established or rejected
UDP: Time-based token authentication
Each message includes HMAC token
Token computed from: message data + timestamp + password-derived key
Server verifies token and timestamp freshness
Prevents replay attacks (60-second window)
Password Hashing
Algorithm: PBKDF2-HMAC-SHA256
Iterations: 100,000 (configurable)
Salt: 16-byte random value
Storage format:
pbkdf2_sha256$iterations$salt$hash
TLS/SSL
Minimum version: TLS 1.2
Cipher suites: Modern, secure ciphers (ECDHE, AES-GCM, ChaCha20)
Certificates: Auto-generated self-signed or custom
TCP only: UDP does not support TLS
Rate Limiting
Window-based: Track requests per time window
Configurable: Different limits for TCP (100/min) and UDP (6000/min)
Blocking: Temporarily block abusive clients
Per-client: Rate limits applied per client address
Data Flow
Input Capture to Virtual Output
Capture (Client)
pygame polls physical gamepad
Read button states (pressed/released)
Read axis positions (-1.0 to 1.0)
Read D-pad directions
Serialize (Client)
Create InputMessage with current state
Add authentication token (UDP)
Encode to JSON
Send over network
Receive (Server)
Accept TCP connection or UDP datagram
TLS decrypt (TCP only)
Parse JSON message
Validate protocol version and structure
Authenticate (Server)
Verify HMAC token (UDP) or challenge-response (TCP)
Check rate limits
Reject if invalid
Process (Server)
Map physical button IDs to virtual button constants
Apply axis transformations (dead zone, inversion)
Normalize trigger values
Output (Server)
Update vgamepad virtual controller
Press/release virtual buttons
Set virtual stick positions
Set virtual trigger values
Windows sees virtual gamepad input
Threading Model
Client
Main thread: Event loop for connection management
Async tasks: Input polling, message sending, heartbeat
Server
TCP: One async task per client connection
UDP: Single async task for all clients
Main thread: Server lifecycle management
Configuration System
The configuration system supports multiple sources with clear priority:
Environment variables: Highest priority (passwords, log directory, debug mode)
Command-line arguments: Override config file settings
Configuration files: INI format with sections
Defaults: Built-in fallback values
Example flow:
# 1. Load config file
config = load_config("server_config.ini")
# 2. Parse command-line args
args = parse_args()
# 3. Apply precedence
password = os.getenv("PASSWORD") or args.password or config["password"]
# 4. Validate
validate_config(final_config)
Extension Points
Custom Axis Transformations
Axis options support various transformations:
Dead zones (ignore small movements)
Inversion (reverse axis direction)
Thresholds (trigger sensitivity)
Future extensibility could add:
Custom scaling functions
Axis remapping
Macro support
Performance Considerations
Latency Sources
Input polling: ~1ms (60-120 Hz)
Serialization: <1ms (JSON encoding)
Network transmission: 1-50ms (depends on network)
Deserialization: <1ms (JSON decoding)
vgamepad update: ~1ms
Total typical latency: 5-55ms
Optimization Strategies
Use UDP: Eliminates TCP handshake and retransmission delays
Increase update rate: Higher Hz = lower perceived latency
Reduce network hops: Same LAN = lower latency
Minimize processing: Efficient serialization, minimal transformations
Async I/O: Non-blocking operations prevent backlog
Memory Usage
Client: ~10-20 MB (pygame + Python runtime)
Server: ~15-30 MB (vgamepad + Python runtime)
Per connection: ~1-2 MB (buffers, state)
Design Principles
Simplicity: Easy to install, configure, and use
Security: Authentication, encryption, input validation
Reliability: Error handling, reconnection, rate limiting
Performance: Low latency, efficient serialization
Flexibility: Multiple protocols, gamepad types, custom mappings
Portability: Cross-platform client, standard Python
Maintainability: Clear separation of concerns, documented code
See Also
Protocol Specification - Detailed protocol specification
Security - Security design and best practices
API Reference - API reference documentation