Moved classes from init files

This commit is contained in:
Emily 2021-03-25 00:00:39 +01:00
parent f9d8470ee6
commit 59b30ed591
11 changed files with 131 additions and 111 deletions

View File

@ -1,52 +1,6 @@
import struct
import socket
import asyncio
from time import time
from . import base
from . import query
from . import player
from . import client
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)
from .client import Client

View File

@ -4,6 +4,7 @@ import struct
import logging
logger = logging.getLogger(__name__)
class BaseClient:
def __init__(self, server: "__ServerInstance__", ip: str, port: int):
self.server = server

53
sampy/client/client.py Normal file
View 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)

View File

@ -10,6 +10,7 @@ logger = logging.getLogger(__name__)
STATE_CONNECTING = 0
STATE_CONNECTED = 1
class PlayerClient(BaseClient):
def __init__(self, server: "__ServerInstance__", ip: str, port: int):
super().__init__(server, ip, port)

View File

@ -7,6 +7,7 @@ from ..shared import glob
import logging
logger = logging.getLogger(__name__)
class QueryClient(BaseClient):
def __init__(self, server: "__ServerInstance__", ip: str, port: int):
super().__init__(server, ip, port)

View File

@ -4,6 +4,7 @@ from threading import Thread
from .struct.server import ServerConfig
from .server import Server
class Environment(Thread):
def __init__(self, config: ServerConfig):
super().__init__()

1
sampy/raknet/__init__.py Normal file
View File

@ -0,0 +1 @@
from . import bitstream

View File

@ -7,6 +7,7 @@ from typing import *
import logging
logger = logging.getLogger(__name__)
class Bitstream:
def __init__(self, data: bytes = b""):
self._offset = 0

View File

@ -1,64 +1,4 @@
import socket
import asyncio
from select import select # This is straight up magic
from . import compression
from . import server
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)
from .server import Server

View File

@ -7,6 +7,7 @@ logger = logging.getLogger(__name__)
# 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"
class Compressor:
def __init__(self, config: ServerConfig):
self.config = config
@ -49,6 +50,7 @@ class Compressor:
def get_port_xor_key(self) -> int:
return (self.config.port ^ 0xCCCC) & 0xFF
class StringCompressor:
def __init__(self):
pass

65
sampy/server/server.py Normal file
View 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)