Files
CreateE_SpaceCCL/MilRcon.py
2026-01-25 13:51:15 +08:00

108 lines
4.4 KiB
Python

import getpass
import argparse
import os
import socket
import ssl
import select
import struct
import time
import platform
#if platform.system() != "Windows":
# import signal
class MCRconException(Exception):
pass
def timeout_handler(signum, frame):
raise MCRconException("Connection timeout error")
class _MinecraftRcon(object):
socket = None
def __init__(self, host, password, port=25575, tlsmode=0, timeout=5):
self.host = host
self.password = password
self.port = port
self.tlsmode = tlsmode
self.timeout = timeout
#if platform.system() != "Windows":
# signal.signal(signal.SIGALRM, timeout_handler)
def __enter__(self):
self.connect()
return self
def __exit__(self, type, value, tb):
self.disconnect()
def connect(self):
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
if self.tlsmode > 0:
ctx = ssl.create_default_context()
if self.tlsmode > 1:
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
self.socket = ctx.wrap_socket(self.socket, server_hostname=self.host)
self.socket.connect((self.host, self.port))
self._send(3, self.password)
def disconnect(self):
if self.socket is not None:
self.socket.close()
self.socket = None
def _read(self, length):
#if platform.system() != "Windows": signal.alarm(self.timeout)
data = b""
while len(data) < length: data += self.socket.recv(length - len(data))
#if platform.system() != "Windows": signal.alarm(0)
return data
def _send(self, out_type, out_data):
if self.socket is None:
raise MCRconException("Must connect before sending data")
out_payload = (
struct.pack("<ii", 0, out_type) + out_data.encode("utf8") + b"\x00\x00"
)
out_length = struct.pack("<i", len(out_payload))
self.socket.send(out_length + out_payload)
in_data = ""
while True:
(in_length,) = struct.unpack("<i", self._read(4))
in_payload = self._read(in_length)
in_id, in_type = struct.unpack("<ii", in_payload[:8])
in_data_partial, in_padding = in_payload[8:-2], in_payload[-2:]
if in_padding != b"\x00\x00":
raise MCRconException("Incorrect padding")
if in_id == -1:
raise MCRconException("Login failed")
in_data += in_data_partial.decode("utf8")
if len(select.select([self.socket], [], [], 0)[0]) == 0:
return in_data
def command(self, command):
result = self._send(2, command)
#time.sleep(0.003) # MC-72390 workaround
return result
def MinecraftRconCli():
try:
parser = argparse.ArgumentParser(description="connect to and use Minecraft Server remote console protocol")
parser.add_argument("host", metavar="HOST", help="the host to connect to")
parser.add_argument("--password", metavar="PASSWORD", help="the password to connect with, default is a prompt or envvar RCON_PASSWORD.")
parser.add_argument("-p", "--port", metavar="PORT", dest="port", type=int, default=25575,help="the port to connect to")
parser.add_argument("-t", "--tls", dest="tlsmode", action="store_true", help="connect to the server with tls encryption",)
args = parser.parse_args()
if not args.password and not os.environ.get("RCON_PASSWORD"): password = getpass.getpass("Password: ")
elif os.environ.get("RCON_PASSWORD"): password = os.environ.get("RCON_PASSWORD")
else: password = args.password
try:
with _MinecraftRcon(args.host, password, args.port, args.tlsmode) as mcr:
while True:
cmd = input("> ")
if cmd.strip() == "exit": break
else:
try: print(mcr.command(cmd))
except (ConnectionResetError, ConnectionAbortedError):
print("The connection was terminated, the server may have been stopped.")
break
if cmd == "exit": break
except ConnectionRefusedError: print("The connection could not be made as the server actively refused it.")
except ConnectionError as e: print(e)
except KeyboardInterrupt: pass