super basic server impl
This commit is contained in:
parent
3cee338ca5
commit
f513801132
1 changed files with 71 additions and 21 deletions
|
@ -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:
|
await self._server.start_serving()
|
||||||
self._logger.info("Minecraft server started")
|
self._logger.info("Minecraft server started")
|
||||||
if block:
|
|
||||||
await self._server.serve_forever()
|
|
||||||
else:
|
|
||||||
await self._server.start_serving()
|
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue