aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRitabrata Das <[email protected]>2025-03-09 12:02:56 +0530
committerGitHub <[email protected]>2025-03-09 12:02:56 +0530
commite49bd6e0dac89c48a7280630da88d313d8fca464 (patch)
tree7d298f9f50c210e16be4c6a632fa66ae2041043c
parent9978030d5be36c2fdadb8dd0f76f9f576d06d7a3 (diff)
parent42d2d3acfc44455b70bb46c7bcd9cb1487a79ca4 (diff)
Merge pull request #10 from the-indian-dev/dev20250309
merge into prod
-rw-r--r--lib/Aircraft.py3
-rw-r--r--lib/PacketManager/packets/FSNETCMD_LOGON.py2
-rw-r--r--lib/PacketManager/packets/FSNETCMD_READBACK.py8
-rw-r--r--lib/Player.py4
-rw-r--r--lib/discordSync.py2
-rw-r--r--lib/plugin_manager.py22
-rw-r--r--lib/triggerRespectiveHook.py4
-rw-r--r--plugins/chat_weather_setter.py8
-rw-r--r--plugins/crash_on_ground.py30
-rw-r--r--plugins/radar.py4
-rw-r--r--plugins/refuel.py5
-rw-r--r--proxy.py86
12 files changed, 130 insertions, 48 deletions
diff --git a/lib/Aircraft.py b/lib/Aircraft.py
index 5e6e24d..9f162ef 100644
--- a/lib/Aircraft.py
+++ b/lib/Aircraft.py
@@ -78,9 +78,8 @@ class Aircraft:
return None
if self.life == -1:
self.life=packet.life
-
+
self.prev_life = self.life
-
self.life = packet.life
self.set_position(packet.position)
self.set_attitude(packet.atti)
diff --git a/lib/PacketManager/packets/FSNETCMD_LOGON.py b/lib/PacketManager/packets/FSNETCMD_LOGON.py
index 9848590..620b2cd 100644
--- a/lib/PacketManager/packets/FSNETCMD_LOGON.py
+++ b/lib/PacketManager/packets/FSNETCMD_LOGON.py
@@ -55,4 +55,4 @@ class FSNETCMD_LOGON: #1
@staticmethod
def alter_version(buffer:bytes, new_version:int):
- return buffer[:20]+pack("I",new_version)+buffer[24:] \ No newline at end of file
+ return buffer[:20]+pack("I",new_version)+buffer[24:]
diff --git a/lib/PacketManager/packets/FSNETCMD_READBACK.py b/lib/PacketManager/packets/FSNETCMD_READBACK.py
index 0fd4ead..b86199f 100644
--- a/lib/PacketManager/packets/FSNETCMD_READBACK.py
+++ b/lib/PacketManager/packets/FSNETCMD_READBACK.py
@@ -6,7 +6,7 @@ class FSNETCMD_READBACK: #6
* Client sends FSNETREADBACK_ADDAIRPLAN or FSNETREADBACK_ADDGROUND to
acknowledge FSNETCMD_ADDOBJECT
- * Client sends FSNETREADBACK_REMOVEAIRPLANE or FSNETREADBACK_REMOVEGROUND
+ * Client sends FSNETREADBACK_REMOVEAIRPLANE or FSNETREADBACK_REMOVEGROUND
to acknowledge FSNETCMD_REMOVEAIRPLANE or FSNETCMD_REMOVEGROUND
* Client sends FSNETREADBACK_ENVIRONMENT to acknowledge FSNETCMD_ENVIRONMENT
* Client sends FSNETREADBACK_JOINREQUEST to acknowledge FSNETCMD_JOINREQUEST
@@ -14,7 +14,7 @@ class FSNETCMD_READBACK: #6
* Client sends FSNETREADBACK_USEMISSILE to acknowledge FSNETCMD_USEMISSILE
* Client sends FSNETREADBACK_USEUNGUIDEDWEAPON to acknowledge FSNETCMD_USEUNGUIDEDWEAPON
* Client sends FSNETREADBACK_CTRLSHOWUSERNAME to acknowledge FSNETCMD_CTRLSHOWUSERNAME
-
+
* Server sends FSNETREADBACK_JOINREQUEST to acknowledge FSNETCMD_JOINREQUEST -
Will punt the user if the server receives this from them
* There are probably more, but I've not gone into much detail here yet.
@@ -33,7 +33,7 @@ class FSNETCMD_READBACK: #6
@staticmethod
def encode(read_back_type, read_back_param, with_size:bool=False):
- buffer = pack("IhhI", 6, read_back_type, 0, read_back_param)
+ buffer = pack("IhhI", 6, 0, read_back_type, read_back_param)
if with_size:
return pack("I",len(buffer))+buffer
- return buffer \ No newline at end of file
+ return buffer
diff --git a/lib/Player.py b/lib/Player.py
index 8e2250a..f8da70e 100644
--- a/lib/Player.py
+++ b/lib/Player.py
@@ -13,6 +13,7 @@ class Player:
self.streamWriterObject = streamWriterObject
self.is_a_bot = True # We check if they are still present after LOGIN packet, then they're not a bot
self.iff = 1
+ self.connection_closed = False
def set_aircraft(self, aircraft:Aircraft):
self.aircraft = aircraft
@@ -26,7 +27,8 @@ class Player:
self.ip = ip
def check_add_object(self, packet:FSNETCMD_ADDOBJECT):
- if packet.pilot == self.username:
+ # we check the full name here
+ if packet.pilot == self.alias:
self.aircraft = Aircraft()
self.aircraft.name = packet.identifier
self.aircraft.id = packet.object_id
diff --git a/lib/discordSync.py b/lib/discordSync.py
index 0a8e8be..770543e 100644
--- a/lib/discordSync.py
+++ b/lib/discordSync.py
@@ -84,8 +84,6 @@ async def monitor_channel(channel_id, playerList:list):
encoded_msg = txtMsgr.encode(f"[Discord] {message['author']['username']}: {message['content']}", True)
for player in playerList:
if player.streamWriterObject.is_closing():
- if not player.is_a_bot:
- asyncio.create_task(discord_send_message(channel_id, f"{player.username} has left the server!"))
playerList.remove(player) # Remove disconnected players
continue
player.streamWriterObject.write(encoded_msg)
diff --git a/lib/plugin_manager.py b/lib/plugin_manager.py
index 52fb1f7..b96045b 100644
--- a/lib/plugin_manager.py
+++ b/lib/plugin_manager.py
@@ -7,12 +7,13 @@ PLUGIN_DIR = os.path.join(os.path.dirname(__file__), '../plugins')
sys.path.append(PLUGIN_DIR)
class PluginManager:
- def __init__(self):
+ def __init__(self, connected_players):
self.plugins = {}
self.hooks= {}
- self.commands = {}
+ self.commands = {'help': None}
self.help_message = "List of Available Commands:\n"
self.load_plugins()
+ self.connected_players = connected_players
def load_plugins(self):
for plugin in os.listdir(PLUGIN_DIR):
@@ -45,13 +46,24 @@ class PluginManager:
self.hooks[hook_name] = []
self.hooks[hook_name].append(callback)
- def register_command(self, command_name, callback):
- """Registers the command with the plugin manager"""
+ def register_command(self, command_name, callback, help_text="No Description", alias:str=None):
+ """Registers the command with the plugin manager
+ Set help text to help users understand what your command does.
+ """
if command_name in self.commands:
warning(f"Command {command_name} already registered, Ignoring this registration")
else:
self.commands[command_name] = callback
- self.help_message = self.help_message + f"/{command_name}\n"
+ if alias == None:
+ self.help_message = self.help_message + f"/{command_name} : {help_text}\n"
+ if alias != None:
+ if alias in self.commands:
+ warning(f"Command {alias} already registered, Ignoring this registration")
+ else:
+ self.commands[alias] = callback
+ self.help_message = self.help_message + f"/{command_name} [{alias}] : {help_text}\n"
+
+
def triggar_hook(self, hook_name, data, *args, **kwargs):
"""Triggars the callbacks for the hook.
diff --git a/lib/triggerRespectiveHook.py b/lib/triggerRespectiveHook.py
index 25801a0..f7dc443 100644
--- a/lib/triggerRespectiveHook.py
+++ b/lib/triggerRespectiveHook.py
@@ -59,7 +59,7 @@ def triggerRespectiveHook(packet_type, packet, player, message_to_client, messag
keep_message = plugin_manager.triggar_hook('on_list', packet, player, message_to_client, message_to_server)
else:
keep_message = True
- debug(f"Unknown packet type {packet_type}, C2S")
+ debug(f"Unimplemented packet type {packet_type}, C2S")
return keep_message
@@ -122,6 +122,6 @@ def triggerRespectiveHookServer(packet_type, packet, player, message_to_client,
keep_message = plugin_manager.triggar_hook('on_list_server', packet, player, message_to_client, message_to_server)
else:
keep_message = True
- debug(f"Unknown packet type {packet_type}, S2C")
+ debug(f"Unimplemented packet type {packet_type}, S2C")
return keep_message
diff --git a/plugins/chat_weather_setter.py b/plugins/chat_weather_setter.py
index 5b31a38..675bac5 100644
--- a/plugins/chat_weather_setter.py
+++ b/plugins/chat_weather_setter.py
@@ -13,10 +13,10 @@ class Plugin:
self.plugin_manager = plugin_manager
#self.plugin_manager.register_hook('on_chat', self.on_chat)
self.plugin_manager.register_hook('on_environment_server', self.on_environment)
- self.plugin_manager.register_command('fog', self.fog)
- self.plugin_manager.register_command('sky', self.sky)
- self.plugin_manager.register_command('time', self.time)
- self.plugin_manager.register_command('vis', self.visibility)
+ self.plugin_manager.register_command('fog', self.fog, "Sets fog color. Usage : /fog r,g,b")
+ self.plugin_manager.register_command('sky', self.sky, "Sets sky color. Usage : /sky r,g,b")
+ self.plugin_manager.register_command('time', self.time, "Sets time. Usage : /time <day|night>")
+ self.plugin_manager.register_command('visibility', self.visibility, "Sets Visibilty : /vis <visibilty in meters>", "vis")
def fog(self, full_message, player, message_to_client, message_to_server):
try:
diff --git a/plugins/crash_on_ground.py b/plugins/crash_on_ground.py
new file mode 100644
index 0000000..15b1fbf
--- /dev/null
+++ b/plugins/crash_on_ground.py
@@ -0,0 +1,30 @@
+# This plugin renders the dead plane until crashes in ground
+# Thus disabling mid air dispawning when dead
+from lib.PacketManager.packets import FSNETCMD_REMOVEAIRPLANE, FSNETCMD_READBACK, FSNETCMD_GETDAMAGE
+
+ENABLED = True
+
+class Plugin:
+ def __init__(self):
+ self.plugin_manager = None
+
+ def register(self, plugin_manager):
+ self.plugin_manager = plugin_manager
+ self.plugin_manager.register_hook('on_remove_airplane_server', self.on_death)
+
+ def on_death(self, data, player, message_to_client, message_to_server):
+ if player.aircraft.id != -1:
+ if player.aircraft.id == FSNETCMD_REMOVEAIRPLANE(data).object_id:
+ return True
+ else:
+ decode = FSNETCMD_REMOVEAIRPLANE(data)
+ b = FSNETCMD_GETDAMAGE.encode(decode.object_id, 1, 1,
+ player.aircraft.id, 10000,
+ 11, 0, True)
+ message_to_client.append(b)
+ message_to_server.append(b)
+ a = FSNETCMD_READBACK.encode(2, player.aircraft.id, True)
+ message_to_server.append(a)
+ return False
+ else:
+ return True
diff --git a/plugins/radar.py b/plugins/radar.py
index 7636dc4..ac47b5b 100644
--- a/plugins/radar.py
+++ b/plugins/radar.py
@@ -9,7 +9,7 @@ import struct
import traceback
ENABLED = True
-RADIUS = 5000 # in meters, within this range planes with different IFF can see each other.
+RADIUS = 9000 # in meters, within this range planes with different IFF can see each other.
flying_players = {}
@@ -60,7 +60,7 @@ class Plugin:
try:
# This is a really arbitary position thats hard to reach, better would
# be to randomise it for every time the server starts.
- position_data = struct.pack("3f", 10e7, 10e7, 10e7)
+ position_data = struct.pack("3f", 10e8, 200, 10e8)
if decode.packet_version == 4 or 5:
updated_data = data[:14] + position_data + data[26:]
else:
diff --git a/plugins/refuel.py b/plugins/refuel.py
index ff96699..cde2e03 100644
--- a/plugins/refuel.py
+++ b/plugins/refuel.py
@@ -1,6 +1,7 @@
"""
This plugin enables you to perform air to air refueling.
"""
+from ast import alias
import math
from lib.PacketManager.packets import FSNETCMD_AIRCMD, FSNETCMD_AIRPLANESTATE
from lib import YSchat
@@ -21,8 +22,8 @@ class Plugin:
def register(self, plugin_manager):
self.plugin_manager = plugin_manager
- self.plugin_manager.register_command('refuel', self.refuel)
- self.plugin_manager.register_command('refueler', self.refueler)
+ self.plugin_manager.register_command('refuel', self.refuel, "Allows you to get refuled", alias = "r")
+ self.plugin_manager.register_command('refueler', self.refueler, "Allows other players to refuel from you", alias = "rf")
self.plugin_manager.register_hook('on_flight_data', self.on_flight_data)
self.plugin_manager.register_hook('on_unjoin', self.on_unjoin)
diff --git a/proxy.py b/proxy.py
index d043b32..f68e6d7 100644
--- a/proxy.py
+++ b/proxy.py
@@ -40,7 +40,8 @@ class ColoredFormatter(logging.Formatter):
def format(self, record):
log_color = COLORS.get(record.levelname, COLORS["RESET"])
reset = COLORS["RESET"]
- log_message = f"{log_color}{record.levelname}: {record.getMessage()}{reset}"
+ file_name = record.pathname.split('/')[-1] # Extract just the filename
+ log_message = f"{log_color}{record.levelname} [{file_name}:{record.lineno}]: {record.getMessage()}{reset}"
return log_message
logging.basicConfig(level=LOGGING_LEVEL)
@@ -55,7 +56,15 @@ info("Lisenced under GPLv3")
info("Press CTRL+C to stop the proxy")
#Load the plugins
-plugin_manager = PluginManager()
+plugin_manager = PluginManager(CONNECTED_PLAYERS)
+
+# Close Connection
+async def close_connection(client_writer, server_writer):
+ # if DISCORD_ENABLED: await discord_send_message(CHANNEL_ID, "has left the server!")
+ client_writer.close()
+ server_writer.close()
+ await client_writer.wait_closed()
+ await server_writer.wait_closed()
# Handle client connections
async def handle_client(client_reader, client_writer):
@@ -92,10 +101,13 @@ async def handle_client(client_reader, client_writer):
try:
header = await reader.readexactly(4) # Ensures we always get 4 bytes
except asyncio.IncompleteReadError:
+ await close_connection(client_writer, server_writer)
break
except ConnectionResetError:
+ await close_connection(client_writer, server_writer)
break
except Exception as e:
+ await close_connection(client_writer, server_writer)
critical(f"Error reading header: {e}")
break
@@ -107,21 +119,29 @@ async def handle_client(client_reader, client_writer):
packet = await reader.read(length)
if not packet:
+ await close_connection(client_writer, server_writer)
break
data = header + packet
packet_type = PacketManager().get_packet_type(packet)
if direction == "client_to_server":
- debug("C2S" + str(packet_type))
+ debug("C2S" + str(packet_type) + str(player.username))
debug(data)
try:
if packet_type == "FSNETCMD_LOGON":
- # keep_message = plugin_manager.triggar_hook('on_login', packet, player, message_to_client, message_to_server)
- # if not keep_message:
- # data = None
- player.login(FSNETCMD_LOGON(packet))
+ decode = FSNETCMD_LOGON(packet)
+ for p in CONNECTED_PLAYERS:
+ if p.username == decode.username:
+ client_writer.write(YSchat.message(f"Same username {decode.username} is aldready connected to server! Kicked {ipAddr}"))
+ data = None
+ info(f"Same username {decode.username} is aldready connected to server! Kicked {ipAddr}")
+ await close_connection(client_writer, server_writer)
+
+ player.login(decode)
+ info(f"Player {player.username} connected from {player.ip}")
+
if player.version != YSF_VERSION and VIA_VERSION:
info(f"ViaVersion enabled : Porting {player.username} from {player.version} to {YSF_VERSION}")
message_to_client.append(YSchat.message(f"Porting you to YSFlight {YSF_VERSION}, This is currently Experimental"))
@@ -131,12 +151,16 @@ async def handle_client(client_reader, client_writer):
continue
elif packet_type == "FSNETCMD_JOINREQUEST":
- player.iff = FSNETCMD_JOINREQUEST(packet).iff + 1
+ decode = FSNETCMD_JOINREQUEST(packet)
+ player.iff = decode.iff
if DISCORD_ENABLED:
- asyncio.create_task(discord_send_message(CHANNEL_ID, f"{player.username} has taken off! 🛫"))
+ asyncio.create_task(discord_send_message(CHANNEL_ID, f"{player.username} has took off in a {decode.aircraft}! 🛫"))
elif packet_type == "FSNETCMD_AIRPLANESTATE":
- player.aircraft.add_state(FSNETCMD_AIRPLANESTATE(packet)) #TODO: Do we want to convert all this to plugins? Probably not, but there is duplicated functionality
+ decode = FSNETCMD_AIRPLANESTATE(packet)
+ player.aircraft.add_state(decode)
+
+ #TODO: Do we want to convert all this to plugins? Probably not, but there is duplicated functionality
# keep_message = plugin_manager.triggar_hook('on_flight_data', packet, player, message_to_client, message_to_server)
# if not keep_message:
# data = None
@@ -170,10 +194,11 @@ async def handle_client(client_reader, client_writer):
data = None
command = msg.message.split(" ")[0][1:]
asyncio.create_task(triggerCommand.triggerCommand(command, msg.message, player, message_to_client, message_to_server, plugin_manager))
-
- if DISCORD_ENABLED:
- # Make it non blocking!
- asyncio.create_task(discord_send_message(CHANNEL_ID, finalMsg))
+ info(f"Command {command} triggered by {player.username}")
+ else:
+ if DISCORD_ENABLED:
+ # Make it non blocking!
+ asyncio.create_task(discord_send_message(CHANNEL_ID, finalMsg))
elif packet_type == "FSNETCMD_LIST":
# keep_message = plugin_manager.triggar_hook('on_list', packet, player, message_to_client, message_to_server)
@@ -189,7 +214,7 @@ async def handle_client(client_reader, client_writer):
traceback.print_exc() # This will display the full traceback
else :
- debug("S2C" + str(packet_type))
+ debug("S2C" + str(packet_type) + str(player.username))
debug(data)
#if packet_type == "FSNETCMD_AIRCMD":
@@ -223,11 +248,13 @@ async def handle_client(client_reader, client_writer):
writer.write(data)
await writer.drain()
except (asyncio.CancelledError, ConnectionResetError, BrokenPipeError) as e:
- if e == BrokenPipeError or ConnectionResetError or asyncio.CancelledError:
- info(f"Connection closed by {player.username} : {player.ip}")
- else:
- warning(f"Connection error during packet forwarding: {e}")
- break
+ if not player.connection_closed:
+ await close_connection(client_writer, server_writer)
+ if e == BrokenPipeError or ConnectionResetError or asyncio.CancelledError:
+ 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
await asyncio.gather(
@@ -238,12 +265,24 @@ async def handle_client(client_reader, client_writer):
if not isinstance(e, BrokenPipeError):
critical(f"Connection error: {e}")
finally:
+ """
try:
- client_writer.close()
- await client_writer.wait_closed()
+ #client_writer.close()
+ #await client_writer.wait_closed()
+ pass
except Exception as e:
if not isinstance(e, BrokenPipeError):
critical(f"Error closing client connection: {e}")
+ """
+ if not player.connection_closed:
+ player.connection_closed = True
+ CONNECTED_PLAYERS.remove(player)
+ if not player.is_a_bot:
+ for player in CONNECTED_PLAYERS:
+ player.streamWriterObject.write(YSchat.message(f"{player.username} has left the server!"))
+ if DISCORD_ENABLED:
+ await discord_send_message(CHANNEL_ID, f"{player.username} has left the server!")
+ await close_connection(client_writer, server_writer)
# Start the proxy server
@@ -263,5 +302,6 @@ if __name__ == "__main__":
asyncio.run(discord_send_message(CHANNEL_ID, "✅ Server has started."))
asyncio.run(start_proxy())
except KeyboardInterrupt:
- asyncio.run(discord_send_message(CHANNEL_ID, "❌ Server has stopped."))
+ if DISCORD_ENABLED:
+ asyncio.run(discord_send_message(CHANNEL_ID, "❌ Server has stopped."))
info("Goodbye!")