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:
- 4-byte header - Contains packet length (unsigned integer)
- 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.
- 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