diff options
| -rw-r--r-- | lib/Aircraft.py | 3 | ||||
| -rw-r--r-- | lib/PacketManager/packets/FSNETCMD_LOGON.py | 2 | ||||
| -rw-r--r-- | lib/PacketManager/packets/FSNETCMD_READBACK.py | 8 | ||||
| -rw-r--r-- | lib/Player.py | 4 | ||||
| -rw-r--r-- | lib/discordSync.py | 2 | ||||
| -rw-r--r-- | lib/plugin_manager.py | 22 | ||||
| -rw-r--r-- | lib/triggerRespectiveHook.py | 4 | ||||
| -rw-r--r-- | plugins/chat_weather_setter.py | 8 | ||||
| -rw-r--r-- | plugins/crash_on_ground.py | 30 | ||||
| -rw-r--r-- | plugins/radar.py | 4 | ||||
| -rw-r--r-- | plugins/refuel.py | 5 | ||||
| -rw-r--r-- | proxy.py | 86 |
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) @@ -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!") |
