Initial commit
This commit is contained in:
7
sampy/__init__.py
Normal file
7
sampy/__init__.py
Normal 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
32
sampy/client/__init__.py
Normal 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
16
sampy/client/base.py
Normal 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
13
sampy/client/player.py
Normal 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
13
sampy/client/query.py
Normal 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
13
sampy/default_config.json
Normal 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
21
sampy/env.py
Normal 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
40
sampy/server.py
Normal 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
1
sampy/shared/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import glob
|
||||
37
sampy/shared/glob.py
Normal file
37
sampy/shared/glob.py
Normal 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
1
sampy/struct/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import server
|
||||
21
sampy/struct/server.py
Normal file
21
sampy/struct/server.py
Normal 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)
|
||||
Reference in New Issue
Block a user