Query protocol
This commit is contained in:
parent
0c0a8d327c
commit
c1ad95d233
|
@ -5,16 +5,13 @@ from . import base
|
|||
from . import query
|
||||
from . import player
|
||||
|
||||
from ..struct.server import ServerConfig
|
||||
|
||||
STATE_UNKNOWN = (0, base.BaseClient)
|
||||
STATE_QUERY = (1, query.QueryClient)
|
||||
STATE_PLAYER = (2, player.PlayerClient)
|
||||
|
||||
class Client:
|
||||
def __init__(self, config: ServerConfig, socket: socket.socket, ip: str, port: int):
|
||||
self.config = config
|
||||
self.socket = socket
|
||||
def __init__(self, server: "__ServerInstance__", ip: str, port: int):
|
||||
self.server = server
|
||||
self.ip = ip
|
||||
self.port = port
|
||||
|
||||
|
@ -22,7 +19,7 @@ class Client:
|
|||
|
||||
def set_state(self, state: tuple):
|
||||
self.state = state
|
||||
self.client = self.state[1](self.config, self.socket, self.ip, self.port)
|
||||
self.client = self.state[1](self.server, self.ip, self.port)
|
||||
|
||||
async def on_packet(self, packet: bytes):
|
||||
if self.state == STATE_UNKNOWN:
|
||||
|
|
|
@ -1,15 +1,12 @@
|
|||
import socket
|
||||
import struct
|
||||
|
||||
from ..struct.server import ServerConfig
|
||||
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class BaseClient:
|
||||
def __init__(self, config: ServerConfig, socket: socket.socket, ip: str, port: int):
|
||||
self.config = config
|
||||
self.socket = socket
|
||||
def __init__(self, server: "__ServerInstance__", ip: str, port: int):
|
||||
self.server = server
|
||||
self.ip = ip
|
||||
self.port = port
|
||||
|
||||
|
@ -17,3 +14,7 @@ class BaseClient:
|
|||
|
||||
async def on_packet(self, packet: bytes):
|
||||
logger.debug("on_packet(%s)" % packet)
|
||||
|
||||
async def send(self, packet: bytes):
|
||||
sock: socket.socket = self.server.socket
|
||||
sock.sendto(packet, (self.ip, self.port))
|
|
@ -1,14 +1,13 @@
|
|||
import socket
|
||||
|
||||
from .base import BaseClient
|
||||
from ..struct.server import ServerConfig
|
||||
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class PlayerClient(BaseClient):
|
||||
def __init__(self, config: ServerConfig, socket: socket.socket, ip: str, port: int):
|
||||
super().__init__(config, socket, ip, port)
|
||||
def __init__(self, server: "__ServerInstance__", ip: str, port: int):
|
||||
super().__init__(server, ip, port)
|
||||
logger.debug("Client resolved to PlayerClient")
|
||||
|
||||
async def on_packet(self, packet: bytes):
|
||||
|
|
|
@ -2,22 +2,111 @@ import socket
|
|||
import struct
|
||||
|
||||
from .base import BaseClient
|
||||
from ..struct.server import ServerConfig
|
||||
from ..shared import glob
|
||||
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class QueryClient(BaseClient):
|
||||
def __init__(self, config: ServerConfig, socket: socket.socket, ip: str, port: int):
|
||||
super().__init__(config, socket, ip, port)
|
||||
def __init__(self, server: "__ServerInstance__", ip: str, port: int):
|
||||
super().__init__(server, ip, port)
|
||||
logger.debug("Client resolved to QueryClient")
|
||||
|
||||
self.handlers = {
|
||||
b"i": self.query_i,
|
||||
b"r": self.query_r,
|
||||
b"c": self.query_c,
|
||||
b"d": self.query_d
|
||||
}
|
||||
|
||||
async def on_packet(self, packet: bytes):
|
||||
logger.debug("on_packet(%s)" % packet)
|
||||
|
||||
async def query_i(self):
|
||||
len_hostname = len(self.config.hostname)
|
||||
len_mode = len(self.config.mode)
|
||||
len_language = len(self.config.language)
|
||||
"""
|
||||
if len(packet) != 11: # Just a ping with some random numbers at the end (14 bytes in total)
|
||||
await self.send(packet)
|
||||
return
|
||||
"""
|
||||
# ip ~~and port~~ the client connected to; (would be server ip ~~and port~~ if not though proxy)
|
||||
# This could be used to check if client connected remotely in some way though a proxy
|
||||
# SA:MP default server drops the connection if port does not match (when its client (not query protocol))
|
||||
#server_ip = ".".join((str(x) for x in struct.unpack(b"<4B", packet[4:8])))
|
||||
#server_port, = struct.unpack(b"<H", packet[8:10]) # This is not port, the wiki is wrong: https://wiki.sa-mp.com/wiki/Query
|
||||
#logger.debug("[%s:%d] via %s:%d" % (self.ip, self.port, server_ip, server_port))
|
||||
|
||||
if len(packet) <= 10: # Invalid
|
||||
return
|
||||
|
||||
if packet[10:11] in self.handlers:
|
||||
packet = await self.handlers[packet[10:11]](packet)
|
||||
|
||||
if len(packet): # Send packet back if not 0
|
||||
await self.send(packet)
|
||||
|
||||
async def query_i(self, packet: bytes) -> bytes:
|
||||
len_hostname = len(self.server.config.hostname)
|
||||
len_mode = len(self.server.config.mode)
|
||||
len_language = len(self.server.config.language)
|
||||
|
||||
return packet + struct.pack(b"<?HHI%dsI%dsI%ds" % (len_hostname, len_mode, len_language),
|
||||
len(self.server.config.password) != 0,
|
||||
|
||||
len(await self.server.get_online_players()),
|
||||
self.server.config.max_players,
|
||||
|
||||
len_hostname,
|
||||
self.server.config.hostname.encode(),
|
||||
|
||||
len_mode,
|
||||
self.server.config.mode.encode(),
|
||||
|
||||
len_language,
|
||||
self.server.config.language.encode()
|
||||
)
|
||||
|
||||
async def query_r(self, packet: bytes) -> bytes:
|
||||
data = []
|
||||
|
||||
rules = await self.server.get_rules()
|
||||
data.append(len(rules))
|
||||
|
||||
for k, v in rules.items():
|
||||
data += [
|
||||
len(k), k,
|
||||
len(v), v
|
||||
]
|
||||
|
||||
return packet + struct.pack(b"<H" + (b"B%dsB%ds" * data[0]) % tuple(len(y) for x in rules.items() for y in x), *data)
|
||||
|
||||
async def query_c(self, packet: bytes) -> bytes:
|
||||
data = []
|
||||
|
||||
scores = await self.server.get_players_scores()
|
||||
data.append(len(scores))
|
||||
|
||||
for k, v in scores.items():
|
||||
data += [
|
||||
len(k), k,
|
||||
v
|
||||
]
|
||||
|
||||
return packet + struct.pack(b"<H" + (b"B%dsI" * data[0]) % tuple(len(x) for x in scores.keys()), *data)
|
||||
|
||||
async def query_d(self, packet: bytes) -> bytes:
|
||||
data = []
|
||||
|
||||
players = await self.server.get_online_players()
|
||||
data.append(len(players))
|
||||
|
||||
for p in players:
|
||||
data += [
|
||||
p["id"],
|
||||
len(p["nick"]), p["nick"],
|
||||
p["score"],
|
||||
p["ping"]
|
||||
]
|
||||
|
||||
return packet + struct.pack(b"<H" + (b"cc%dsII" * data[0]) % tuple(len(p["nick"]) for p in players), *data)
|
||||
|
||||
async def query_p(self, packet: bytes) -> bytes:
|
||||
return packet
|
|
@ -23,6 +23,17 @@ class Server:
|
|||
async def on_command(self, cmd: str):
|
||||
logger.debug("on_command(%s)" % cmd)
|
||||
|
||||
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()
|
||||
|
||||
|
@ -34,7 +45,7 @@ class Server:
|
|||
|
||||
if addr not in self.clients:
|
||||
ip, port = addr
|
||||
self.clients[addr] = Client(self.config, self.socket, ip, port)
|
||||
self.clients[addr] = Client(self, ip, port)
|
||||
await self.clients[addr].on_packet(data)
|
||||
|
||||
await asyncio.sleep(0)
|
Loading…
Reference in New Issue
Block a user