aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRitabrata Das <[email protected]>2025-01-29 21:17:27 +0530
committerRitabrata Das <[email protected]>2025-01-29 21:17:27 +0530
commit69a2bb0c6dd3d91b323f3d334d42f28c670334d7 (patch)
tree43d0ca106d6a4bf50382424544b7d9a009fe8c38
parent12ce4ff2d26a721ac3465397f8b1c6f9a0b45457 (diff)
Improve code quality and add via version
-rw-r--r--README.md14
-rw-r--r--config.py25
-rw-r--r--lib/YSundead.py4
-rw-r--r--lib/YSviaversion.py11
-rw-r--r--proxy.py169
5 files changed, 137 insertions, 86 deletions
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 <player name>
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("<I I", data[:8])
debug("S2C" + str(packet_type))
debug(data)
if packet_type == 36:
- for player in CONNECTED_PLAYERS:
- if player.ip == ipAddr:
- targetWriter = player.streamWriterObject
- id = player.playerId
- break
+ # print("S2C ", str(data))
+ pass
+ """
+ targetWriter = player.streamWriterObject
+ id = player.playerId
data = YSundead.undeadPatch(id, data)
targetWriter.write(data)
await targetWriter.drain()
+ """
# Forward the packet to the other endpoint
writer.write(data)
await writer.drain()
except (asyncio.CancelledError, ConnectionResetError, BrokenPipeError) as e:
- warn(f"Connection error during packet forwarding: {e}")
+ if e == BrokenPipeError:
+ info(f"Connection closed by {player.username} : {player.ip}")
+ else:
+ warning(f"Connection error during packet forwarding: {e}")
break
# Start forwarding data between client and server