Skip to main content
Sakuya AC functions as a transparent proxy server that sits between YSFlight clients and the YSFlight server. It intercepts all network traffic, allowing you to inspect, modify, and enhance gameplay without modifying the game client or server.

Basic flow

When a player connects to your server through Sakuya AC, the following happens:

Connection handling

Each client connection spawns an asynchronous handler that manages bidirectional communication:
async def handle_client(client_reader, client_writer):
    # Connect to actual YSFlight server
    server_reader, server_writer = await asyncio.open_connection(SERVER_HOST, SERVER_PORT)
    
    # Create player object to track state
    player = Player.Player(message_to_server, message_to_client, client_writer)
    CONNECTED_PLAYERS.append(player)
    
    # Start bidirectional forwarding
    await asyncio.gather(
        forward(client_reader, server_writer, "client_to_server"),
        forward(server_reader, client_writer, "server_to_client")
    )
From proxy.py:94-106
Sakuya AC uses Python’s asyncio for concurrent connections. On Linux systems, it can optionally use uvloop for enhanced I/O performance.

Packet interception

Every YSFlight packet follows a simple structure:
  1. 4-byte header - Contains packet length (unsigned integer)
  2. Payload - The actual packet data
The proxy reads packets in the forward() function:
# Read 4-byte header
header = await reader.readexactly(4)
length = unpack("I", header)[0]

# Read payload based on length
packet = await reader.read(length)

# Reconstruct full packet
data = header + packet
From proxy.py:125-148

Packet identification

Once intercepted, the PacketManager identifies the packet type by reading the first 4 bytes of the payload:
packet_type = PacketManager().get_packet_type(packet)

# PacketManager implementation:
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:10-14 The MESSAGE_TYPES list maps packet IDs to human-readable names like FSNETCMD_LOGON, FSNETCMD_AIRPLANESTATE, etc.

Packet processing

Depending on the packet type and direction (client-to-server or server-to-client), Sakuya AC can:

Inspect packets

Decode and log packet contents:
if packet_type == "FSNETCMD_LOGON":
    decode = FSNETCMD_LOGON(packet)
    player.login(decode)
    info(f"Player {player.username} connected from {player.ip}")
From proxy.py:156-169

Modify packets

Change packet data before forwarding (e.g., version translation):
if player.version != YSF_VERSION and VIA_VERSION:
    info(f"ViaVersion enabled : Porting {player.username} from {player.version} to {YSF_VERSION}")
    data = YSviaversion.genViaVersion(player.username, YSF_VERSION)
From proxy.py:171-176

Block packets

Prevent certain packets from being forwarded:
if msg.message.startswith(PREFIX):
    data = None  # Setting data to None blocks forwarding
    command = msg.message.split(" ")[0][1:]
    asyncio.create_task(triggerCommand.triggerCommand(...))
From proxy.py:218-221
Setting data = None prevents the packet from being forwarded to its destination. This is useful for implementing custom commands or anti-cheat features.

Inject new packets

Send custom packets to clients or server:
# Add to message queue
welcomeMsg = YSchat.message(WELCOME_MESSAGE.format(username=player.username))
message_to_server.append(welcomeMsg)
From proxy.py:265-266 The message queues (message_to_server and message_to_client) are checked at the start of each forwarding loop iteration.

State tracking

As packets flow through the proxy, Sakuya AC maintains state for each player:
# Track aircraft state updates
elif packet_type == "FSNETCMD_AIRPLANESTATE":
    decode = FSNETCMD_AIRPLANESTATE(packet)
    player.aircraft.add_state(decode)
    
    # Use tracked state for anti-cheat
    if player.aircraft.prev_life < player.aircraft.life:
        # Health increased - possible hack
        cheatingMsg = YSchat.message(f"{HEALTH_HACK_MESSAGE} by {player.username}")
        message_to_server.append(cheatingMsg)
From proxy.py:184-196

Plugin hooks

For every packet type, Sakuya AC triggers plugin hooks that allow custom logic:
keep_message = triggerRespectiveHook(
    packet_type, 
    packet, 
    player, 
    message_to_client, 
    message_to_server, 
    plugin_manager
)
if not keep_message: 
    data = None
From proxy.py:234-235
Plugins can return False to block a packet from being forwarded, enabling custom game logic and moderation features.

Performance considerations

  • Asynchronous I/O: All socket operations use asyncio to handle multiple connections efficiently
  • Message queues: Separate queues for client and server messages prevent blocking
  • Task cancellation: Proper cleanup when connections close prevents resource leaks
  • uvloop: Optional high-performance event loop for Unix systems
if platform.system() != "Windows":
    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:56-62

Build docs developers (and LLMs) love