Moved classes from init files
This commit is contained in:
parent
f9d8470ee6
commit
59b30ed591
|
@ -1,52 +1,6 @@
|
||||||
import struct
|
|
||||||
import socket
|
|
||||||
import asyncio
|
|
||||||
from time import time
|
|
||||||
|
|
||||||
from . import base
|
from . import base
|
||||||
from . import query
|
from . import query
|
||||||
from . import player
|
from . import player
|
||||||
|
from . import client
|
||||||
|
|
||||||
STATE_UNKNOWN = (0, base.BaseClient)
|
from .client import Client
|
||||||
STATE_QUERY = (1, query.QueryClient)
|
|
||||||
STATE_PLAYER = (2, player.PlayerClient)
|
|
||||||
|
|
||||||
TIMEOUT = 10 # assume connection is closed after 10 seconds of inactivity (change this to a higher value so you dont timeout while debugging might be a good idea)
|
|
||||||
|
|
||||||
class Client:
|
|
||||||
def __init__(self, server: "__ServerInstance__", ip: str, port: int):
|
|
||||||
self.server = server
|
|
||||||
self.ip = ip
|
|
||||||
self.port = port
|
|
||||||
|
|
||||||
self.set_state(STATE_UNKNOWN)
|
|
||||||
|
|
||||||
self.last_active = time()
|
|
||||||
self.keep_alive_task = asyncio.create_task( self.keep_alive() )
|
|
||||||
self.connected = True # keep_alive will set this to False if connection has not been interacted with for a while (allowing server loop to remove their reference)
|
|
||||||
|
|
||||||
def set_state(self, state: tuple):
|
|
||||||
#self.keep_alive_task.cancel()
|
|
||||||
self.state = state
|
|
||||||
self.client = self.state[1](self.server, self.ip, self.port)
|
|
||||||
|
|
||||||
async def on_packet(self, packet: bytes):
|
|
||||||
self.last_active = time()
|
|
||||||
|
|
||||||
if self.state == STATE_UNKNOWN:
|
|
||||||
# We are currently unaware if this is a player client or query client, but we got a packet that will be our check to know
|
|
||||||
if packet.startswith(b"SAMP"):
|
|
||||||
self.set_state(STATE_QUERY)
|
|
||||||
else:
|
|
||||||
self.set_state(STATE_PLAYER)
|
|
||||||
|
|
||||||
await self.client.on_packet(packet)
|
|
||||||
|
|
||||||
async def keep_alive(self): # Maybe bad name for this method as it rather checks if connection is dropped
|
|
||||||
while True:
|
|
||||||
timestamp = time()
|
|
||||||
if self.last_active + TIMEOUT - timestamp < 0:
|
|
||||||
self.connected = False
|
|
||||||
return
|
|
||||||
|
|
||||||
await asyncio.sleep(self.last_active + TIMEOUT - timestamp)
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ import struct
|
||||||
import logging
|
import logging
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class BaseClient:
|
class BaseClient:
|
||||||
def __init__(self, server: "__ServerInstance__", ip: str, port: int):
|
def __init__(self, server: "__ServerInstance__", ip: str, port: int):
|
||||||
self.server = server
|
self.server = server
|
||||||
|
|
53
sampy/client/client.py
Normal file
53
sampy/client/client.py
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
import struct
|
||||||
|
import socket
|
||||||
|
import asyncio
|
||||||
|
from time import time
|
||||||
|
|
||||||
|
from . import base
|
||||||
|
from . import query
|
||||||
|
from . import player
|
||||||
|
|
||||||
|
STATE_UNKNOWN = (0, base.BaseClient)
|
||||||
|
STATE_QUERY = (1, query.QueryClient)
|
||||||
|
STATE_PLAYER = (2, player.PlayerClient)
|
||||||
|
|
||||||
|
TIMEOUT = 10 # assume connection is closed after 10 seconds of inactivity (change this to a higher value so you dont timeout while debugging might be a good idea)
|
||||||
|
|
||||||
|
|
||||||
|
class Client:
|
||||||
|
def __init__(self, server: "__ServerInstance__", ip: str, port: int):
|
||||||
|
self.server = server
|
||||||
|
self.ip = ip
|
||||||
|
self.port = port
|
||||||
|
|
||||||
|
self.set_state(STATE_UNKNOWN)
|
||||||
|
|
||||||
|
self.last_active = time()
|
||||||
|
self.keep_alive_task = asyncio.create_task(self.keep_alive())
|
||||||
|
self.connected = True # keep_alive will set this to False if connection has not been interacted with for a while (allowing server loop to remove their reference)
|
||||||
|
|
||||||
|
def set_state(self, state: tuple):
|
||||||
|
# self.keep_alive_task.cancel()
|
||||||
|
self.state = state
|
||||||
|
self.client = self.state[1](self.server, self.ip, self.port)
|
||||||
|
|
||||||
|
async def on_packet(self, packet: bytes):
|
||||||
|
self.last_active = time()
|
||||||
|
|
||||||
|
if self.state == STATE_UNKNOWN:
|
||||||
|
# We are currently unaware if this is a player client or query client, but we got a packet that will be our check to know
|
||||||
|
if packet.startswith(b"SAMP"):
|
||||||
|
self.set_state(STATE_QUERY)
|
||||||
|
else:
|
||||||
|
self.set_state(STATE_PLAYER)
|
||||||
|
|
||||||
|
await self.client.on_packet(packet)
|
||||||
|
|
||||||
|
async def keep_alive(self): # Maybe bad name for this method as it rather checks if connection is dropped
|
||||||
|
while True:
|
||||||
|
timestamp = time()
|
||||||
|
if self.last_active + TIMEOUT - timestamp < 0:
|
||||||
|
self.connected = False
|
||||||
|
return
|
||||||
|
|
||||||
|
await asyncio.sleep(self.last_active + TIMEOUT - timestamp)
|
|
@ -10,6 +10,7 @@ logger = logging.getLogger(__name__)
|
||||||
STATE_CONNECTING = 0
|
STATE_CONNECTING = 0
|
||||||
STATE_CONNECTED = 1
|
STATE_CONNECTED = 1
|
||||||
|
|
||||||
|
|
||||||
class PlayerClient(BaseClient):
|
class PlayerClient(BaseClient):
|
||||||
def __init__(self, server: "__ServerInstance__", ip: str, port: int):
|
def __init__(self, server: "__ServerInstance__", ip: str, port: int):
|
||||||
super().__init__(server, ip, port)
|
super().__init__(server, ip, port)
|
||||||
|
|
|
@ -7,6 +7,7 @@ from ..shared import glob
|
||||||
import logging
|
import logging
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class QueryClient(BaseClient):
|
class QueryClient(BaseClient):
|
||||||
def __init__(self, server: "__ServerInstance__", ip: str, port: int):
|
def __init__(self, server: "__ServerInstance__", ip: str, port: int):
|
||||||
super().__init__(server, ip, port)
|
super().__init__(server, ip, port)
|
||||||
|
|
|
@ -4,6 +4,7 @@ from threading import Thread
|
||||||
from .struct.server import ServerConfig
|
from .struct.server import ServerConfig
|
||||||
from .server import Server
|
from .server import Server
|
||||||
|
|
||||||
|
|
||||||
class Environment(Thread):
|
class Environment(Thread):
|
||||||
def __init__(self, config: ServerConfig):
|
def __init__(self, config: ServerConfig):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
1
sampy/raknet/__init__.py
Normal file
1
sampy/raknet/__init__.py
Normal file
|
@ -0,0 +1 @@
|
||||||
|
from . import bitstream
|
|
@ -7,6 +7,7 @@ from typing import *
|
||||||
import logging
|
import logging
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Bitstream:
|
class Bitstream:
|
||||||
def __init__(self, data: bytes = b""):
|
def __init__(self, data: bytes = b""):
|
||||||
self._offset = 0
|
self._offset = 0
|
||||||
|
|
|
@ -1,64 +1,4 @@
|
||||||
import socket
|
from . import compression
|
||||||
import asyncio
|
from . import server
|
||||||
from select import select # This is straight up magic
|
|
||||||
|
|
||||||
from ..struct.server import ServerConfig
|
from .server import Server
|
||||||
from ..client import Client
|
|
||||||
|
|
||||||
from .compression import Compressor
|
|
||||||
|
|
||||||
import logging
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
class Server:
|
|
||||||
def __init__(self, config: ServerConfig):
|
|
||||||
self.config = config
|
|
||||||
self.clients = {}
|
|
||||||
self.rcon_clients = {}
|
|
||||||
|
|
||||||
self.compressor = Compressor(self.config)
|
|
||||||
|
|
||||||
async def create_socket(self):
|
|
||||||
self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
|
|
||||||
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
||||||
self.socket.bind((self.config.host, self.config.port))
|
|
||||||
self.socket.setblocking(0)
|
|
||||||
logger.debug("Socket created")
|
|
||||||
|
|
||||||
async def on_command(self, cmd: str):
|
|
||||||
logger.debug("on_command(%s)" % cmd)
|
|
||||||
# TODO: When commands return a reponse we also want to forward this to potential rcon clients
|
|
||||||
|
|
||||||
async def get_online_players(self): # TODO: Get data from server's client objects
|
|
||||||
return [
|
|
||||||
{"nick": b"Sunpy", "score": 64, "ping": 8, "id": 1} # replace id with function to get player's id
|
|
||||||
]
|
|
||||||
|
|
||||||
async def get_rules(self): # TODO
|
|
||||||
return {b"Rule name sample": b"Rule value", b"weburl": b"https://git.osufx.com/Sunpy/sampy"}
|
|
||||||
|
|
||||||
async def get_players_scores(self): # TODO
|
|
||||||
return {b"Sunpy": 64, b"username": 123}
|
|
||||||
|
|
||||||
async def main(self):
|
|
||||||
await self.create_socket()
|
|
||||||
|
|
||||||
while True:
|
|
||||||
(incomming, _, _) = select([self.socket], [], [], 0) # How this works is beyond me, but this sets `incomming` to be true~y if socket has awaiting data
|
|
||||||
|
|
||||||
if incomming:
|
|
||||||
data, addr = self.socket.recvfrom(0xFFFF)
|
|
||||||
|
|
||||||
if addr not in self.clients:
|
|
||||||
ip, port = addr
|
|
||||||
self.clients[addr] = Client(self, ip, port)
|
|
||||||
await self.clients[addr].on_packet(data)
|
|
||||||
|
|
||||||
disconnected = [c for c in self.clients.values() if c.connected == False]
|
|
||||||
for c in disconnected: # Remove dead connections
|
|
||||||
addr = (c.ip, c.port)
|
|
||||||
if addr in self.clients:
|
|
||||||
del self.clients[addr]
|
|
||||||
logger.debug("free(%s)" % c)
|
|
||||||
|
|
||||||
await asyncio.sleep(0)
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ logger = logging.getLogger(__name__)
|
||||||
# Found @ addr 0x004C88E0
|
# Found @ addr 0x004C88E0
|
||||||
LOOKUP_TABLE = b"\xb4b\x07\xe5\x9d\xafc\xdd\xe3\xd0\xcc\xfe\xdc\xdbk.j@\xabG\xc9\xd1S\xd5 \x91\xa5\x0eJ\xdf\x18\x89\xfdo%\x12\xb7\x13w\x00e6mI\xecW*\xa9\x11_\xfax\x95\xa4\xbd\x1e\xd9yD\xcd\xde\x81\xeb\t>\xf6\xee\xda\x7f\xa3\x1a\xa7-\xa6\xad\xc1F\x93\xd2\x1b\x9c\xaa\xd7NKML\xf3\xb84\xc0\xca\x88\xf4\x94\xcb\x0490\x82\xd6s\xb0\xbf\"\x01AnH,\xa8u\xb1\n\xae\x9f\'\x80\x10\xce\xf0)(\x85\r\x05\xf75\xbb\xbc\x15\x06\xf5`q\x03\x1f\xeaZ3\x92\x8d\xe7\x90[\xe9\xcf\x9e\xd3]\xed1\x1c\x0bR\x16Q\x0f\x86\xc5h\x9b!\x0c\x8bB\x87\xffO\xbe\xc8\xe8\xc7\xd4z\xe0U/\x8a\x8e\xba\x987\xe4\xb28\xa1\xb62\x83:{\x84<a\xfb\x8c\x14=C;\x1d\xc3\xa2\x96\xb3\xf8\xc4\xf2&+\xd8|\xfc#$f\xefidPTY\xf1\xa0t\xac\xc6}\xb5\xe6\xe2\xc2~g\x17^\xe1\xb9?lp\x08\x99EVv\xf9\x9a\x97\x19r\\\x02\x8fX"
|
LOOKUP_TABLE = b"\xb4b\x07\xe5\x9d\xafc\xdd\xe3\xd0\xcc\xfe\xdc\xdbk.j@\xabG\xc9\xd1S\xd5 \x91\xa5\x0eJ\xdf\x18\x89\xfdo%\x12\xb7\x13w\x00e6mI\xecW*\xa9\x11_\xfax\x95\xa4\xbd\x1e\xd9yD\xcd\xde\x81\xeb\t>\xf6\xee\xda\x7f\xa3\x1a\xa7-\xa6\xad\xc1F\x93\xd2\x1b\x9c\xaa\xd7NKML\xf3\xb84\xc0\xca\x88\xf4\x94\xcb\x0490\x82\xd6s\xb0\xbf\"\x01AnH,\xa8u\xb1\n\xae\x9f\'\x80\x10\xce\xf0)(\x85\r\x05\xf75\xbb\xbc\x15\x06\xf5`q\x03\x1f\xeaZ3\x92\x8d\xe7\x90[\xe9\xcf\x9e\xd3]\xed1\x1c\x0bR\x16Q\x0f\x86\xc5h\x9b!\x0c\x8bB\x87\xffO\xbe\xc8\xe8\xc7\xd4z\xe0U/\x8a\x8e\xba\x987\xe4\xb28\xa1\xb62\x83:{\x84<a\xfb\x8c\x14=C;\x1d\xc3\xa2\x96\xb3\xf8\xc4\xf2&+\xd8|\xfc#$f\xefidPTY\xf1\xa0t\xac\xc6}\xb5\xe6\xe2\xc2~g\x17^\xe1\xb9?lp\x08\x99EVv\xf9\x9a\x97\x19r\\\x02\x8fX"
|
||||||
|
|
||||||
|
|
||||||
class Compressor:
|
class Compressor:
|
||||||
def __init__(self, config: ServerConfig):
|
def __init__(self, config: ServerConfig):
|
||||||
self.config = config
|
self.config = config
|
||||||
|
@ -49,6 +50,7 @@ class Compressor:
|
||||||
def get_port_xor_key(self) -> int:
|
def get_port_xor_key(self) -> int:
|
||||||
return (self.config.port ^ 0xCCCC) & 0xFF
|
return (self.config.port ^ 0xCCCC) & 0xFF
|
||||||
|
|
||||||
|
|
||||||
class StringCompressor:
|
class StringCompressor:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
pass
|
pass
|
||||||
|
|
65
sampy/server/server.py
Normal file
65
sampy/server/server.py
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
import socket
|
||||||
|
import asyncio
|
||||||
|
from select import select # This is straight up magic
|
||||||
|
|
||||||
|
from ..struct.server import ServerConfig
|
||||||
|
from ..client import Client
|
||||||
|
|
||||||
|
from .compression import Compressor
|
||||||
|
|
||||||
|
import logging
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class Server:
|
||||||
|
def __init__(self, config: ServerConfig):
|
||||||
|
self.config = config
|
||||||
|
self.clients = {}
|
||||||
|
self.rcon_clients = {}
|
||||||
|
|
||||||
|
self.compressor = Compressor(self.config)
|
||||||
|
|
||||||
|
async def create_socket(self):
|
||||||
|
self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
|
||||||
|
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||||
|
self.socket.bind((self.config.host, self.config.port))
|
||||||
|
self.socket.setblocking(0)
|
||||||
|
logger.debug("Socket created")
|
||||||
|
|
||||||
|
async def on_command(self, cmd: str):
|
||||||
|
logger.debug("on_command(%s)" % cmd)
|
||||||
|
# TODO: When commands return a reponse we also want to forward this to potential rcon clients
|
||||||
|
|
||||||
|
async def get_online_players(self): # TODO: Get data from server's client objects
|
||||||
|
return [
|
||||||
|
{"nick": b"Sunpy", "score": 64, "ping": 8, "id": 1} # replace id with function to get player's id
|
||||||
|
]
|
||||||
|
|
||||||
|
async def get_rules(self): # TODO
|
||||||
|
return {b"Rule name sample": b"Rule value", b"weburl": b"https://git.osufx.com/Sunpy/sampy"}
|
||||||
|
|
||||||
|
async def get_players_scores(self): # TODO
|
||||||
|
return {b"Sunpy": 64, b"username": 123}
|
||||||
|
|
||||||
|
async def main(self):
|
||||||
|
await self.create_socket()
|
||||||
|
|
||||||
|
while True:
|
||||||
|
(incomming, _, _) = select([self.socket], [], [], 0) # How this works is beyond me, but this sets `incomming` to be true~y if socket has awaiting data
|
||||||
|
|
||||||
|
if incomming:
|
||||||
|
data, addr = self.socket.recvfrom(0xFFFF)
|
||||||
|
|
||||||
|
if addr not in self.clients:
|
||||||
|
ip, port = addr
|
||||||
|
self.clients[addr] = Client(self, ip, port)
|
||||||
|
await self.clients[addr].on_packet(data)
|
||||||
|
|
||||||
|
disconnected = [c for c in self.clients.values() if c.connected == False]
|
||||||
|
for c in disconnected: # Remove dead connections
|
||||||
|
addr = (c.ip, c.port)
|
||||||
|
if addr in self.clients:
|
||||||
|
del self.clients[addr]
|
||||||
|
logger.debug("free(%s)" % c)
|
||||||
|
|
||||||
|
await asyncio.sleep(0)
|
Loading…
Reference in New Issue
Block a user