super basic server impl

This commit is contained in:
əlemi 2021-11-21 23:57:22 +01:00
parent 3cee338ca5
commit f513801132

View file

@ -2,7 +2,8 @@ import asyncio
import logging import logging
import uuid import uuid
from asyncio import Task, StreamReader, StreamWriter, Server from asyncio import Task, StreamReader, StreamWriter
from asyncio.base_events import Server # just for typing
from enum import Enum from enum import Enum
from typing import Dict, List, Callable, Type, Optional, Tuple, AsyncIterator from typing import Dict, List, Callable, Type, Optional, Tuple, AsyncIterator
@ -13,7 +14,7 @@ from .mc.token import Token, AuthException
from .mc.definitions import Dimension, Difficulty, Gamemode, ConnectionState from .mc.definitions import Dimension, Difficulty, Gamemode, ConnectionState
from .mc.proto.handshaking.serverbound import PacketSetProtocol from .mc.proto.handshaking.serverbound import PacketSetProtocol
from .mc.proto.play.serverbound import PacketKeepAlive as PacketKeepAliveResponse from .mc.proto.play.serverbound import PacketKeepAlive as PacketKeepAliveResponse
from .mc.proto.play.clientbound import PacketKeepAlive, PacketSetCompression, PacketKickDisconnect from .mc.proto.play.clientbound import PacketKeepAlive, PacketSetCompression, PacketKickDisconnect, PacketPosition, PacketLogin
from .mc.proto.login.serverbound import PacketLoginStart, PacketEncryptionBegin as PacketEncryptionResponse from .mc.proto.login.serverbound import PacketLoginStart, PacketEncryptionBegin as PacketEncryptionResponse
from .mc.proto.login.clientbound import ( from .mc.proto.login.clientbound import (
PacketCompress, PacketDisconnect, PacketEncryptionBegin, PacketLoginPluginRequest, PacketSuccess PacketCompress, PacketDisconnect, PacketEncryptionBegin, PacketLoginPluginRequest, PacketSuccess
@ -22,7 +23,7 @@ from .util import encryption
LOGGER = logging.getLogger(__name__) LOGGER = logging.getLogger(__name__)
class Server: class MinecraftServer:
host:str host:str
port:int port:int
options:dict options:dict
@ -45,6 +46,7 @@ class Server:
self.port = port self.port = port
self.options = options or { self.options = options or {
"poll-timeout" : 1,
"online-mode" : False, "online-mode" : False,
} }
@ -63,11 +65,11 @@ class Server:
loop.run_until_complete(self.start()) loop.run_until_complete(self.start())
async def idle(): async def idle():
while self._processing: # TODO don't busywait even if it doesn't matter much while True: # TODO don't busywait even if it doesn't matter much
await asyncio.sleep(self.options["poll-timeout"]) await asyncio.sleep(self.options["poll-timeout"])
try: try:
loop.run_forever(idle()) loop.run_until_complete(idle())
except KeyboardInterrupt: except KeyboardInterrupt:
self._logger.info("Received SIGINT, stopping...") self._logger.info("Received SIGINT, stopping...")
try: try:
@ -76,26 +78,19 @@ class Server:
self._logger.info("Received SIGINT, stopping for real") self._logger.info("Received SIGINT, stopping for real")
loop.run_until_complete(self.stop(wait_tasks=False)) loop.run_until_complete(self.stop(wait_tasks=False))
async def start(self, block=False): async def start(self):
self._server = await asyncio.start_server( self._server = await asyncio.start_server(
self._server_worker, self.host, self.port self._server_worker, self.host, self.port
) )
addrs = ', '.join(str(sock.getsockname()) for sock in server.sockets)
print(f'Serving on {addrs}')
self._processing = True self._processing = True
async with self._server:
self._logger.info("Minecraft server started")
if block:
await self._server.serve_forever()
else:
await self._server.start_serving() await self._server.start_serving()
self._logger.info("Minecraft server started")
async def stop(self, block=True, wait_tasks=True): async def stop(self, block=True, wait_tasks=True):
self._processing = False self._processing = False
if self.dispatcher.connected: # if self.dispatcher.connected:
await self.dispatcher.disconnect(block=block) # await self.dispatcher.disconnect(block=block)
self._server.close() self._server.close()
if block: if block:
await self._server.wait_closed() await self._server.wait_closed()
@ -109,7 +104,16 @@ class Server:
await dispatcher.write(PacketKickDisconnect(dispatcher.proto, reason="Connection terminated")) await dispatcher.write(PacketKickDisconnect(dispatcher.proto, reason="Connection terminated"))
async def _server_worker(self, reader:StreamReader, writer:StreamWriter): async def _server_worker(self, reader:StreamReader, writer:StreamWriter):
dispatcher = Dispatcher() if not self._processing:
if not writer.is_closing() and writer.can_write_eof():
try:
writer.write_eof()
except OSError as e:
self._logger.error("Failed to write EOF to connecting client : %s", str(e))
writer.close()
return await writer.wait_closed()
dispatcher = Dispatcher(server=True)
self._logger.debug("Starting dispatcher for client") self._logger.debug("Starting dispatcher for client")
await dispatcher.connect( await dispatcher.connect(
@ -131,23 +135,29 @@ class Server:
await dispatcher.disconnect() await dispatcher.disconnect()
async def _handshake(self, dispatcher:Dispatcher) -> bool: # TODO make this fancier! poll for version and status first async def _handshake(self, dispatcher:Dispatcher) -> bool: # TODO make this fancier! poll for version and status first
self._logger.info("Awaiting handshake")
async for packet in dispatcher.packets(): async for packet in dispatcher.packets():
if isinstance(packet, PacketSetProtocol): if isinstance(packet, PacketSetProtocol):
self._logger.info("Received set protocol packet")
dispatcher.proto = packet.protocolVersion dispatcher.proto = packet.protocolVersion
if packet.nextState == ConnectionState.STATUS: if packet.nextState == 1:
self._logger.debug("Changing state to STATUS")
dispatcher.state = ConnectionState.STATUS dispatcher.state = ConnectionState.STATUS
return True return True
elif packet.nextState == ConnectionState.LOGIN: elif packet.nextState == 2:
self._logger.debug("Changing state to LOGIN")
dispatcher.state = ConnectionState.LOGIN dispatcher.state = ConnectionState.LOGIN
return True return True
return False return False
async def _status(self, dispatcher:Dispatcher) -> bool: async def _status(self, dispatcher:Dispatcher) -> bool:
self._logger.info("Answering ping")
async for packet in dispatcher.packets(): async for packet in dispatcher.packets():
pass # TODO handle status! pass # TODO handle status!
return False return False
async def _login(self, dispatcher:Dispatcher) -> bool: async def _login(self, dispatcher:Dispatcher) -> bool:
self._logger.info("Logging in player")
async for packet in dispatcher.packets(): async for packet in dispatcher.packets():
if isinstance(packet, PacketLoginStart): if isinstance(packet, PacketLoginStart):
if self.options["online-mode"]: if self.options["online-mode"]:
@ -164,7 +174,7 @@ class Server:
await dispatcher.write( await dispatcher.write(
PacketSuccess( PacketSuccess(
dispatcher.proto, dispatcher.proto,
uuid=packet.username, uuid=str(uuid.uuid4()),
username=packet.username, username=packet.username,
) )
) )
@ -177,7 +187,47 @@ class Server:
return False return False
async def _play(self, dispatcher:Dispatcher) -> bool: async def _play(self, dispatcher:Dispatcher) -> bool:
self._logger.info("Player connected")
await dispatcher.write(
PacketLogin(
dispatcher.proto,
gameMode=3,
isFlat=False,
worldNames=b'',
worldName='aiocraft',
previousGameMode=3,
entityId=1,
isHardcore=False,
difficulty=0,
isDebug=True,
enableRespawnScreen=False,
maxPlayers=1,
dimension=1,
levelType='aiocraft',
reducedDebugInfo=False,
hashedSeed=1234,
viewDistance=4
)
)
await dispatcher.write(
PacketPosition(
dispatcher.proto,
dismountVehicle=True,
x=0,
y=120,
flags=0,
yaw=0.0,
onGround=False,
teleportId=1,
pitch=0.0,
z=0,
)
)
async for packet in dispatcher.packets(): async for packet in dispatcher.packets():
pass # TODO handle play pass # TODO handle play
return False return False