Query protocol
This commit is contained in:
parent
0c0a8d327c
commit
c1ad95d233
|
@ -5,16 +5,13 @@ from . import base
|
||||||
from . import query
|
from . import query
|
||||||
from . import player
|
from . import player
|
||||||
|
|
||||||
from ..struct.server import ServerConfig
|
|
||||||
|
|
||||||
STATE_UNKNOWN = (0, base.BaseClient)
|
STATE_UNKNOWN = (0, base.BaseClient)
|
||||||
STATE_QUERY = (1, query.QueryClient)
|
STATE_QUERY = (1, query.QueryClient)
|
||||||
STATE_PLAYER = (2, player.PlayerClient)
|
STATE_PLAYER = (2, player.PlayerClient)
|
||||||
|
|
||||||
class Client:
|
class Client:
|
||||||
def __init__(self, config: ServerConfig, socket: socket.socket, ip: str, port: int):
|
def __init__(self, server: "__ServerInstance__", ip: str, port: int):
|
||||||
self.config = config
|
self.server = server
|
||||||
self.socket = socket
|
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.port = port
|
self.port = port
|
||||||
|
|
||||||
|
@ -22,7 +19,7 @@ class Client:
|
||||||
|
|
||||||
def set_state(self, state: tuple):
|
def set_state(self, state: tuple):
|
||||||
self.state = state
|
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):
|
async def on_packet(self, packet: bytes):
|
||||||
if self.state == STATE_UNKNOWN:
|
if self.state == STATE_UNKNOWN:
|
||||||
|
|
|
@ -1,15 +1,12 @@
|
||||||
import socket
|
import socket
|
||||||
import struct
|
import struct
|
||||||
|
|
||||||
from ..struct.server import ServerConfig
|
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
class BaseClient:
|
class BaseClient:
|
||||||
def __init__(self, config: ServerConfig, socket: socket.socket, ip: str, port: int):
|
def __init__(self, server: "__ServerInstance__", ip: str, port: int):
|
||||||
self.config = config
|
self.server = server
|
||||||
self.socket = socket
|
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.port = port
|
self.port = port
|
||||||
|
|
||||||
|
@ -17,3 +14,7 @@ class BaseClient:
|
||||||
|
|
||||||
async def on_packet(self, packet: bytes):
|
async def on_packet(self, packet: bytes):
|
||||||
logger.debug("on_packet(%s)" % packet)
|
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
|
import socket
|
||||||
|
|
||||||
from .base import BaseClient
|
from .base import BaseClient
|
||||||
from ..struct.server import ServerConfig
|
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
class PlayerClient(BaseClient):
|
class PlayerClient(BaseClient):
|
||||||
def __init__(self, config: ServerConfig, socket: socket.socket, ip: str, port: int):
|
def __init__(self, server: "__ServerInstance__", ip: str, port: int):
|
||||||
super().__init__(config, socket, ip, port)
|
super().__init__(server, ip, port)
|
||||||
logger.debug("Client resolved to PlayerClient")
|
logger.debug("Client resolved to PlayerClient")
|
||||||
|
|
||||||
async def on_packet(self, packet: bytes):
|
async def on_packet(self, packet: bytes):
|
||||||
|
|
|
@ -2,22 +2,111 @@ import socket
|
||||||
import struct
|
import struct
|
||||||
|
|
||||||
from .base import BaseClient
|
from .base import BaseClient
|
||||||
from ..struct.server import ServerConfig
|
|
||||||
from ..shared import glob
|
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, config: ServerConfig, socket: socket.socket, ip: str, port: int):
|
def __init__(self, server: "__ServerInstance__", ip: str, port: int):
|
||||||
super().__init__(config, socket, ip, port)
|
super().__init__(server, ip, port)
|
||||||
logger.debug("Client resolved to QueryClient")
|
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):
|
async def on_packet(self, packet: bytes):
|
||||||
logger.debug("on_packet(%s)" % packet)
|
logger.debug("on_packet(%s)" % packet)
|
||||||
|
|
||||||
async def query_i(self):
|
"""
|
||||||
len_hostname = len(self.config.hostname)
|
if len(packet) != 11: # Just a ping with some random numbers at the end (14 bytes in total)
|
||||||
len_mode = len(self.config.mode)
|
await self.send(packet)
|
||||||
len_language = len(self.config.language)
|
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):
|
async def on_command(self, cmd: str):
|
||||||
logger.debug("on_command(%s)" % cmd)
|
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):
|
async def main(self):
|
||||||
await self.create_socket()
|
await self.create_socket()
|
||||||
|
|
||||||
|
@ -34,7 +45,7 @@ class Server:
|
||||||
|
|
||||||
if addr not in self.clients:
|
if addr not in self.clients:
|
||||||
ip, port = addr
|
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 self.clients[addr].on_packet(data)
|
||||||
|
|
||||||
await asyncio.sleep(0)
|
await asyncio.sleep(0)
|
Loading…
Reference in New Issue
Block a user