Skip to main content
Sakuya AC is designed as a modular, event-driven proxy server. This document explains how the major components work together to provide a flexible anti-cheat and enhancement layer for YSFlight.

System overview

┌─────────────────────────────────────────────────────────────┐
│                        Sakuya AC Proxy                       │
│                                                               │
│  ┌─────────────┐      ┌──────────────┐      ┌────────────┐  │
│  │   Client    │◄────►│    Proxy     │◄────►│  YSFlight  │  │
│  │  Connection │      │   Handler    │      │   Server   │  │
│  └─────────────┘      └──────┬───────┘      └────────────┘  │
│                              │                                │
│                              ▼                                │
│                    ┌─────────────────┐                        │
│                    │ PacketManager   │                        │
│                    └────────┬────────┘                        │
│                             │                                 │
│          ┌──────────────────┼──────────────────┐              │
│          ▼                  ▼                  ▼              │
│     ┌─────────┐      ┌───────────┐     ┌──────────┐          │
│     │ Player  │      │ Aircraft  │     │  Plugin  │          │
│     │  State  │      │   State   │     │  System  │          │
│     └─────────┘      └───────────┘     └──────────┘          │
└─────────────────────────────────────────────────────────────┘

Core components

Proxy server (proxy.py)

The main entry point that orchestrates all other components. Key responsibilities:
  • Accept incoming client connections
  • Establish connections to the YSFlight server
  • Manage bidirectional packet forwarding
  • Coordinate plugin execution
  • Handle graceful shutdown and cleanup
async def start_proxy():
    server = await asyncio.start_server(
        handle_client, 
        "0.0.0.0", 
        PROXY_PORT, 
        backlog=100
    )
    info(f"Proxy server listening on port {PROXY_PORT}")
    async with server:
        await server.serve_forever()
From proxy.py:369-376

Connection handler

Each client connection runs in its own async task with two forwarding coroutines:
  1. Client → Server: Intercepts outgoing packets from the game client
  2. Server → Client: Intercepts incoming packets from the game server
client_to_server_task = asyncio.create_task(
    forward(client_reader, server_writer, "client_to_server")
)
server_to_client_task = asyncio.create_task(
    forward(server_reader, client_writer, "server_to_client")
)

# Wait for either task to complete
done, pending = await asyncio.wait(
    [client_to_server_task, server_to_client_task],
    return_when=asyncio.FIRST_COMPLETED
)
From proxy.py:301-312
Using asyncio.FIRST_COMPLETED ensures that when one side disconnects, the entire connection is cleaned up promptly.

PacketManager

Centralized packet identification and parsing system. Features:
  • Maps packet IDs to type names
  • Provides packet-specific decoder classes
  • Supports 50+ YSFlight packet types
class PacketManager:
    def get_packet_type(self, data: bytes):
        if len(data) < 4:
            return None
        return MESSAGE_TYPES[unpack("<I", data[:4])[0]]
From lib/PacketManager/PacketManager.py:5-14 Each packet type has a dedicated class (e.g., FSNETCMD_LOGON, FSNETCMD_AIRPLANESTATE) that handles encoding and decoding.

Player class (lib/Player.py)

Tracks per-connection state and player information. Attributes:
  • username - Player’s display name
  • alias - Extended username (for names > 16 chars)
  • version - YSFlight version number
  • ip - Client IP address
  • aircraft - Current aircraft state (see Aircraft class)
  • iff - Identify Friend or Foe team
  • is_a_bot - Whether connection is a real player
  • streamWriterObject - Socket writer for sending messages
class Player:
    def __init__(self, server_messages, client_messages, streamWriterObject):
        self.username = ""
        self.alias = ""
        self.aircraft = Aircraft()
        self.version = 0
        self.ip = ""
        self.streamWriterObject = streamWriterObject
        self.is_a_bot = True
        self.iff = 1
        self.connection_closed = False
From lib/Player.py:3-16

Aircraft class (lib/Aircraft.py)

Tracks the state of a player’s aircraft in real-time. Tracked data:
  • Position (x, y, z coordinates)
  • Attitude (heading, pitch, bank)
  • Life/health percentage
  • Fuel and payload
  • Configuration (afterburner, landing gear, flaps, etc.)
  • Last received state packet
class Aircraft:
    def __init__(self, parent=None):
        self.name = ""
        self.position = [0,0,0]
        self.attitude = [0,0,0]
        self.initial_config = {}
        self.custom_config = {}
        self.life = -1
        self.prev_life = -1
        self.id = -1
        self.last_packet = None
From lib/Aircraft.py:5-18
The prev_life field enables anti-cheat detection by comparing health between consecutive state updates.

Plugin system

Provides extensibility through event hooks. Hook types:
  • on_login - Player connects
  • on_join - Player spawns aircraft
  • on_chat - Player sends message
  • on_flight_data - Aircraft state update
  • on_weapon_config - Weapon loadout change
  • Per-packet hooks for any packet type
keep_message = triggerRespectiveHook(
    packet_type,      # e.g., "FSNETCMD_AIRPLANESTATE"
    packet,           # Raw packet data
    player,           # Player object
    message_to_client,# Queue for injecting client messages
    message_to_server,# Queue for injecting server messages
    plugin_manager    # Plugin system
)
From proxy.py:234 Plugins can inspect packets, modify game state, and return False to block packet forwarding.

Data flow

Client to server packets

┌──────────┐
│  Client  │
└────┬─────┘
     │ 1. Send packet (e.g., FSNETCMD_TEXTMESSAGE)

┌──────────────────┐
│ Proxy reads 4B   │
│ header + payload │
└────┬─────────────┘
     │ 2. Identify packet type

┌──────────────────┐
│  PacketManager   │
│  get_packet_type │
└────┬─────────────┘
     │ 3. Parse packet

┌──────────────────┐
│ FSNETCMD_*       │
│ decode()         │
└────┬─────────────┘
     │ 4. Update state & trigger hooks

┌──────────────────┐
│ Player/Aircraft  │
│ Plugin hooks     │
└────┬─────────────┘
     │ 5. Forward (or block)

┌──────────┐
│  Server  │
└──────────┘

Server to client packets

The flow is identical but reversed:
  1. Server sends packet
  2. Proxy intercepts and identifies type
  3. Packet decoded and processed
  4. State updated (e.g., FSNETCMD_ADDOBJECT creates aircraft)
  5. Hooks triggered
  6. Forwarded to client (or blocked/modified)

Message queues

Sakuya AC uses two lists to inject custom packets:
message_to_client = []  # Packets to send to client
message_to_server = []  # Packets to send to server
These are checked at the beginning of each forwarding loop iteration:
if direction == "client_to_server" and message_to_server:
    writer.write(message_to_server.pop(0))
    await writer.drain()
elif direction == "server_to_client" and message_to_client:
    client_writer.write(message_to_client.pop(0))
    await client_writer.drain()
From proxy.py:116-121
You can inject packets from anywhere in your code by appending to these queues. They’ll be sent on the next loop iteration.

Global state

CONNECTED_PLAYERS = []  # List of all active Player objects
This global list enables:
  • Broadcasting messages to all players
  • Checking for duplicate usernames
  • Tracking total player count
  • Cross-player interactions

Configuration

Key settings from config.py:
  • SERVER_HOST - YSFlight server address
  • SERVER_PORT - YSFlight server port
  • PROXY_PORT - Port for clients to connect to
  • YSF_VERSION - Expected YSFlight version
  • VIA_VERSION - Enable version translation
  • PREFIX - Command prefix (e.g., /)
  • DISCORD_ENABLED - Discord integration
  • LOGGING_LEVEL - Debug verbosity

Concurrency model

Sakuya AC uses Python’s asyncio event loop:
  • One task per connection - Isolated, non-blocking
  • Two coroutines per task - Bidirectional forwarding
  • Non-blocking I/O - await on all socket operations
  • Task cancellation - Cleanup when connections close
try:
    import uvloop
    uvloop.install()
    info("uvloop found! Using uvloop for fast I/O")
except ImportError:
    info("uvloop not found but can be installed!")
From proxy.py:57-62
uvloop is a drop-in replacement for asyncio’s event loop, providing 2-4x better performance on Linux and macOS.

Build docs developers (and LLMs) love