From 69a2bb0c6dd3d91b323f3d334d42f28c670334d7 Mon Sep 17 00:00:00 2001 From: Ritabrata Das Date: Wed, 29 Jan 2025 21:17:27 +0530 Subject: Improve code quality and add via version --- README.md | 14 ++--- config.py | 25 +++++++- lib/YSundead.py | 4 ++ lib/YSviaversion.py | 11 ++++ proxy.py | 169 ++++++++++++++++++++++++++++------------------------ 5 files changed, 137 insertions(+), 86 deletions(-) create mode 100644 lib/YSviaversion.py diff --git a/README.md b/README.md index f56b57f..55c76ff 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ - Intercepts and parses network packets to monitor gameplay. - Adds features like G Limiter and Smoke Emission on Death. - All features are server side, vanilla client can join +- Supports clients post 20150425 version to join the server (Experimental) - Easy configuration via `config.py`. --- @@ -27,12 +28,13 @@ ``` 2. Edit `config.py` to match your server and client setup. +> **Warning** +> Please ensure that you have put the correct YSFlight version in YSF_VERSION variable in `config.py`. This is important for the proxy to work correctly. 3. Run the proxy server: ```bash python proxy.py ``` - --- ## **Configuration** @@ -54,12 +56,10 @@ PROXY_PORT = 9000 # Port where the proxy listens --- ## **TODO :memo:** 1. Add more detection rules. -2. Fix on ground re supply which triggers the cheat detection. -3. Negative g-values are wrong interpeted -4. Add more documentation. -~~5. Add black smoke emission on death~~ Implemented in latest commit -6. Add radar features -7. Add Plugin API +2. Negative g-values are wrong interpeted +3. Add more documentation. +4. Add radar features +5. Add Plugin API --- ## **Contact** diff --git a/config.py b/config.py index e95900d..17f2291 100644 --- a/config.py +++ b/config.py @@ -3,7 +3,7 @@ from logging import DEBUG, WARN, INFO, CRITICAL # Configuration # Logging Level : Most of the times having INFO level is enough # But while submitting issues, please consider sending the logs with DEBUG level -LOGGING_LEVEL = INFO +LOGGING_LEVEL = DEBUG # Server Configuration # Replace with the YSFlight server address @@ -12,9 +12,32 @@ SERVER_PORT = 7915 # Please put where the normal YSFlight server is running # Port for the proxy server PROXY_PORT = 9000 +# Native YSFlight Server +# Please select the YSFlight server version for the +# local ysflight server + +YSF_VERSION = 20150425 + +# Enable ViaVersion? This allows you clients post-20150425 versions +# to join your YSFlight server, this may however raise some issues +# Currently experimental + +VIA_VERSION = True + # G Limit (abs(g) >= limit) and the player gets killed G_LIM = 4 # Will appear as message + player name # eg. Detected health hack by HEALTH_HACK_MESSAGE = "Detected for health hack" + +# Enable planes smoking on low life +# If true, planes on life < SMOKE_LIFE will emit black smoke +# They will also have an engine breakdown, not being able to turn +# on afterburner. Also they won't be able to fire missiles + +SMOKE_PLANE = True + +# Planes smoking minimum life + +SMOKE_LIFE = 5 diff --git a/lib/YSundead.py b/lib/YSundead.py index 2b68ed6..57e6648 100644 --- a/lib/YSundead.py +++ b/lib/YSundead.py @@ -7,3 +7,7 @@ from struct import pack def undeadPatch(id:int, data:bytes): buffer = data[0:8] + pack("I", id) + data[12:] return buffer + +def smokedPlane(id:int): + data = b'"\x00\x00\x00$\x00\x00\x00*\x03\x01\x00\x0c\x00\xc8\x00\x14\x00 \x00\x08!!\x00\x08!"\x00\x08!\x00\x00\xb8\x0b\xc8\x00\x14\x00' + return undeadPatch(id, data) diff --git a/lib/YSviaversion.py b/lib/YSviaversion.py new file mode 100644 index 0000000..70b4554 --- /dev/null +++ b/lib/YSviaversion.py @@ -0,0 +1,11 @@ +from struct import pack + +def genViaVersion(username: str, finalVersion: int): + """ + Generates Packets for porting the version + """ + padding = 16-len(username) + byteUserame = [] + for char in username: + byteUserame.append(char.encode('ascii')) + return pack("II16cI", 24, 1, *byteUserame+(padding*[b"\x00"]), finalVersion) diff --git a/proxy.py b/proxy.py index 75433ed..5852d67 100644 --- a/proxy.py +++ b/proxy.py @@ -4,12 +4,11 @@ Lisenced under GPLv3 """ import asyncio -from os import write from struct import unpack, pack from lib.parseFlightData import parseFlightData -from lib import YSchat, YSplayer, YSendFlight, YSundead +from lib import YSchat, YSplayer, YSendFlight, YSundead, YSviaversion import logging -from logging import critical, warn, info, debug +from logging import critical, warning, info, debug from config import * # Configuration @@ -25,14 +24,17 @@ info("Lisenced under GPLv3") # Handle client connections async def handle_client(client_reader, client_writer): + player = YSplayer.Player("username", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "0.0.0.0", + -1, -1, -1, -1, 0, client_writer) try: # Connect to the actual server server_reader, server_writer = await asyncio.open_connection(SERVER_HOST, SERVER_PORT) peername = client_writer.get_extra_info('peername') if peername: ipAddr, clientPort = peername + debug("Player object initiated") - async def forward(reader, writer, direction): + async def forward(reader, writer, direction, player=player): while True: try: data = await reader.read(4096) @@ -44,59 +46,54 @@ async def handle_client(client_reader, client_writer): debug("C2S" + str(packet_type)) debug(data) if packet_type == 11: # Flight data packet - for player in CONNECTED_PLAYERS: - if player.ip == ipAddr: - playerData = parseFlightData(data) - player.playerId = playerData[1] - player.x = playerData[2] - player.y = playerData[3] - player.z = playerData[4] - player.throttle = playerData[22] - player.aam = playerData[18] - player.agm = playerData[19] - player.gunAmmo = playerData[16] - player.rktAmmo = playerData[17] - player.fuel = playerData[12] - player.gValue = playerData[28] - debug(player) - - # Check if health increased - if player.life == -1: - player.life = playerData[21] - - elif playerData[21] > player.life: - cheatingMsg = YSchat.message(f"{HEALTH_HACK_MESSAGE} by {player.username}") - writer.write(cheatingMsg) - await writer.drain() - + playerData = parseFlightData(data) + player.playerId = playerData[1] + player.x = playerData[2] + player.y = playerData[3] + player.z = playerData[4] + player.throttle = playerData[22] + player.aam = playerData[18] + player.agm = playerData[19] + player.gunAmmo = playerData[16] + player.rktAmmo = playerData[17] + player.fuel = playerData[12] + player.gValue = playerData[28] + debug(player) + + # Check if health increased + if player.life == -1: player.life = playerData[21] - if player.life < 5: - targetWriter = player.streamWriterObject - if not player.warningSent: - warningMsg = YSchat.message(f"Your engine has been damaged! You can't turn on afterburner") - debug(f"Sending warning to {player.username}") - targetWriter.write(warningMsg) - await targetWriter.drain() - player.warningSent = True - # add smoke - # assuming packet version 5 - # TODO : Make it dynamic for every pack version - # Since broken aircrafts will fly at < 400kts - # version 5 packets will do fine - data = data[0:60] + pack("h", -254) + data[62:] - writer.write(b'"\x00\x00\x00$\x00\x00\x00*\x03\x01\x00\x0c\x00\xc8\x00\x14\x00 \x00\x08!!\x00\x08!"\x00\x08!\x00\x00\xb8\x0b\xc8\x00\x14\x00') - await writer.drain() - targetWriter.write(YSundead.undeadState) - await targetWriter.drain() + elif playerData[21] > player.life: + cheatingMsg = YSchat.message(f"{HEALTH_HACK_MESSAGE} by {player.username}") + writer.write(cheatingMsg) + await writer.drain() - if abs(player.gValue) > G_LIM and player.gValue < 23: - deathMsg = YSchat.message(f"{player.username}'s G-Force exceeded the limit, gValue = {player.gValue}!") - endPacket = YSendFlight.endFlight(player.playerId) - writer.write(deathMsg) - writer.write(endPacket) - await writer.drain() - break + player.life = playerData[21] + + if player.life < SMOKE_LIFE and SMOKE_PLANE : + targetWriter = player.streamWriterObject + if not player.warningSent: + warningMsg = YSchat.message(f"Your engine has been damaged! You can't turn on afterburner") + debug(f"Sending warning to {player.username}") + targetWriter.write(warningMsg) + await targetWriter.drain() + player.warningSent = True + # add smoke + # assuming packet version 5 + # TODO : Make it dynamic for every pack version + # Since broken aircrafts will fly at < 400kts + # version 5 packets will do fine + data = data[0:60] + pack("h", -254) + data[62:] + writer.write(YSundead.smokedPlane(player.playerId)) + await writer.drain() + + if abs(player.gValue) > G_LIM and player.gValue < 23: + deathMsg = YSchat.message(f"{player.username}'s G-Force exceeded the limit, gValue = {player.gValue}!") + endPacket = YSendFlight.endFlight(player.playerId) + writer.write(deathMsg) + writer.write(endPacket) + await writer.drain() # parsed_data = parseFlightData(data) @@ -110,29 +107,41 @@ async def handle_client(client_reader, client_writer): # f"Throttle: {throttle}, AAM: {aam}, AGM: {agm}, " # f"Gun Ammo: {gunAmmo}, Rocket Ammo: {rktAmmo}, Fuel: {fuel}") elif packet_type == 1: # Connection Request - info("Connection Request") - username = (b''.join(unpack("II16cI", data)[2:16])).decode('ascii').strip('\x00') - playerObject = YSplayer.Player(username, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ipAddr, - -1, -1, -1, -1, 0, client_writer) - CONNECTED_PLAYERS.append(playerObject) + extracted = unpack("II16cI", data) + # username = (b''.join(unpack("II16cI", data)[2:16])).decode('ascii').strip('\x00') + username = b''.join(extracted[2:16]).decode('ascii').strip('\x00') + version = extracted[-1] + info(f"Connection request by {player.username} : {ipAddr}; YSFVERSION = {version}") + player.username = username + player.ip = ipAddr + debug("Player object fixed!") + debug(player) + if version != YSF_VERSION and VIA_VERSION: + info(f"ViaVersion enabled : Porting {username} from {YSF_VERSION} to {version}") + targetWriter = player.streamWriterObject + targetWriter.write(YSchat.message(f"Porting you to YSFlight {YSF_VERSION}, This is currently Experimental")) + targetWriter.write(YSchat.message(f"Please report any bugs to the server admin or join with the correct version")) + await targetWriter.drain() + data = YSviaversion.genViaVersion(username, YSF_VERSION) + elif packet_type == 12: # End Flight - for player in CONNECTED_PLAYERS: - if player.ip == ipAddr: - player.playerId = 0 - player.life = -1 - player.warningSent = False - debug("Health resseted to -1") - break + player.playerId = 0 + player.life = -1 + player.warningSent = False + debug("Health resseted to -1") + elif packet_type == 36: # Weapon config - # here we patch the packet to have smoke forcefully - for player in CONNECTED_PLAYERS: - if player.ip == ipAddr: - targetPlayer = player - break + # here we patch the packet to have smoke forcefully + # This part also is for regen, so we disable cheat detection for health + player.life = -1 + """ + if player.life < 5: + targetPlayer = player data = YSundead.undeadPatch(targetPlayer.playerId, data) writer.write(data) await writer.drain() """ + """ elif packet_type == 32: # Char message # will be used for commands msg = YSchat.message("Pong!") @@ -141,26 +150,30 @@ async def handle_client(client_reader, client_writer): """ except Exception as e: - warn(f"Error parsing flight data: {e}") + warning(f"Error parsing flight data: {e}") else : length, packet_type = unpack("