Initial commit

This commit is contained in:
2020-03-29 18:49:22 +02:00
commit fee2005eef
17 changed files with 484 additions and 0 deletions

7
sampy/__init__.py Normal file
View File

@@ -0,0 +1,7 @@
from . import env
from . import server
from . import client
from . import shared
from . import struct
from . import client

32
sampy/client/__init__.py Normal file
View File

@@ -0,0 +1,32 @@
import struct
import socket
from . import base
from . import query
from . import player
STATE_UNKNOWN = (0, base.BaseClient)
STATE_QUERY = (1, query.QueryClient)
STATE_PLAYER = (2, player.PlayerClient)
class Client:
def __init__(self, socket: socket.socket, ip: str, port: int):
self.socket = socket
self.ip = ip
self.port = port
self.set_state(STATE_UNKNOWN)
def set_state(self, state: tuple):
self.state = state
self.client = self.state[1](self.socket, self.ip, self.port)
async def on_packet(self, packet: bytes):
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)

16
sampy/client/base.py Normal file
View File

@@ -0,0 +1,16 @@
import socket
import struct
import logging
logger = logging.getLogger(__name__)
class BaseClient:
def __init__(self, socket: socket.socket, ip: str, port: int):
self.socket = socket
self.ip = ip
self.port = port
self.ip_uint, = struct.unpack(b"<I", bytes(int(x) for x in self.ip.split(".")))
async def on_packet(self, packet: bytes):
logger.debug("on_packet(%s)" % packet)

13
sampy/client/player.py Normal file
View File

@@ -0,0 +1,13 @@
import socket
from . import base
import logging
logger = logging.getLogger(__name__)
class PlayerClient(base.BaseClient):
def __init__(self, socket: socket.socket, ip: str, port: int):
super().__init__(socket, ip, port)
async def on_packet(self, packet: bytes):
logger.debug("on_packet(%s)" % packet)

13
sampy/client/query.py Normal file
View File

@@ -0,0 +1,13 @@
import socket
from . import base
import logging
logger = logging.getLogger(__name__)
class QueryClient(base.BaseClient):
def __init__(self, socket: socket.socket, ip: str, port: int):
super().__init__(socket, ip, port)
async def on_packet(self, packet: bytes):
logger.debug("on_packet(%s)" % packet)

13
sampy/default_config.json Normal file
View File

@@ -0,0 +1,13 @@
{
"sampy": {
"logging": {
"filename": "",
"level": "DEBUG",
"format": "[%(name)s:L%(lineno)02d %(levelname)s] %(message)s",
"datefmt": "%d-%b-%y %H:%M:%S"
}
},
"custom": {
"_comment": "Anything that is not in the sampy namespace is free for the 3rd party to use and implement"
}
}

21
sampy/env.py Normal file
View File

@@ -0,0 +1,21 @@
import asyncio
from threading import Thread
from .struct.server import Server as struct
from . import server
class Environment(Thread):
def __init__(self, config: struct):
super().__init__()
self.daemon = True
self.event_loop = asyncio.get_event_loop()
self.config = config
self.server = server.Server(self.config)
def command(self, cmd: str):
self.event_loop.create_task(self.server.on_command(cmd))
def run(self):
self.event_loop.run_until_complete(self.server.main())
print("Ended?")

40
sampy/server.py Normal file
View File

@@ -0,0 +1,40 @@
import socket
import asyncio
from select import select # This is straight up magic
from .struct.server import Server as struct
from .client import Client
import logging
logger = logging.getLogger(__name__)
class Server:
def __init__(self, config: struct):
self.config = config
self.clients = {}
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) # TODO: Check if needed? I dont fully understand this "feature"
logger.debug("Socket created")
async def on_command(self, cmd: str):
logger.debug("on_command(%s)" % cmd)
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.socket, ip, port)
await self.clients[addr].on_packet(data)
await asyncio.sleep(0)

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

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

37
sampy/shared/glob.py Normal file
View File

@@ -0,0 +1,37 @@
import os
import sys
import json
import shutil
import logging
if not os.path.isfile("config.json"):
shutil.copyfile(os.path.join(*__package__.split(".")[:-1], "default_config.json"), "config.json")
with open("config.json", "r") as f:
config = json.load(f)
# Setup logger
conf_log = config["sampy"]["logging"] # alias
## fix for logging level
default_logging_fallback = False
if type(conf_log["level"]) is not int:
try:
conf_log["level"] = getattr(logging, conf_log["level"])
except:
conf_log["level"] = logging.INFO
default_logging_fallback = True
## logging settings
logging_handlers = [logging.StreamHandler(sys.stdout)]
if len(conf_log["filename"]):
logging_handlers.append(logging.FileHandler(conf_log["filename"]))
del conf_log["filename"]
logging.basicConfig(**conf_log, handlers = logging_handlers)
logger = logging.getLogger(__name__)
logger.debug("Configured logger")
if default_logging_fallback:
logger.warning("Invalid logging value in config! Defaulting to logging level INFO.")
logger.info("Logging level: %d" % conf_log["level"])

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

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

21
sampy/struct/server.py Normal file
View File

@@ -0,0 +1,21 @@
from random import randint
class Server:
def __init__(self,
host: str, port: int,
hostname: str, password: str,
max_players: int,
mode: str, language: str):
self.host = host
self.port = port
self.hostname = hostname
self.password = password
self.max_players = max_players
self.mode = mode
self.language = language
# Things the user should not be able to control (automated)
self.challenge_short = randint(0, 0xFFFF)