123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534 |
- import sys
- import re
- from twisted.internet import defer
- from twisted.internet import protocol
- from twisted.internet import reactor
- from twisted.internet import task
- from twisted.python import log
- from twisted.python.logfile import DailyLogFile
- import pendulum
- from subprocess import check_output
- from colorama import Fore, Back, Style
- from itertools import cycle
- from deprecated import deprecated
- from pprint import pformat
- try:
- from config_dev import *
- except ModuleNotFoundError:
- from config import *
- version = check_output(
- [
- "git",
- "describe",
- "--abbrev=8",
- "--long",
- "--tags",
- "--dirty",
- "--always",
- "--match",
- "v[0-9]*",
- ],
- universal_newlines=True,
- ).strip()
- def merge(color_string):
- """ Given a string of colorama ANSI, merge them if you can. """
- return color_string.replace("m\x1b[", ";")
- cleaner = re.compile(r"\x1b\[[0-9;]*[A-Zmh]")
- makeNL = re.compile(r"\x1b\[[0-9;]*[JK]")
- def treatAsNL(line):
- """ Replace any ANSI codes that would be better understood as newlines. """
- global makeNL
- return makeNL.sub("\n", line)
- def cleanANSI(line):
- """ Remove all ANSI codes. """
- global cleaner
- return cleaner.sub("", line)
-
- PORT_CLASSES = { 1: 'BBS', 2: 'BSB', 3: 'SBB', 4:'SSB', 5:'SBS', 6:'BSS', 7:'SSS', 8:'BBB'}
- CLASSES_PORT = { v: k for k,v in PORT_CLASSES.items() }
- from observer import Observer
- class PlayerInput(object):
- def __init__(self, game):
-
- self.game = game
- self.observer = self.game.observer
- self.save = None
- self.defer = None
- self.queue_game = game.queue_game
-
- self.c = merge(Style.BRIGHT + Fore.WHITE + Back.BLUE)
- self.r = Style.RESET_ALL
- self.nl = "\n\r"
- self.bsb = "\b \b"
- self.keepalive = None
- def color(self, c):
- self.c = c
- def awake(self):
- log.msg("PlayerInput.awake()")
- self.game.queue_player.put(" ")
- def prompt(self, prompt, limit, default=''):
- log.msg("PlayerInput({0}, {1}, {2}".format(prompt, limit, default))
- self.prompt = prompt
- self.limit = limit
- self.default = default
- self.input = ''
- assert(self.save is None)
- assert(self.keepalive is None)
-
- self.save = self.observer.save()
- self.observer.connect('player', self.input)
- self.keepalive = task.LoopingCall(self.awake)
- self.keepalive.start(30)
-
-
- self.to_player = self.game.to_player
- self.game.to_player = False
-
- self.queue_game.put(self.r + self.nl + self.c + prompt)
-
- self.queue_game.put( " " * limit + "\b" * limit)
- assert(self.defer is None)
- d = defer.Deferred()
- self.defer = d
- return d
- def input(self, chunk):
- """ Data from player (in bytes) """
- chunk = chunk.decode('utf-8', 'ignore')
- for ch in chunk:
- if ch == "\b":
- if len(self.input) > 0:
- self.queue_game.put(self.bsb)
- self.input = self.input[0:-1]
- else:
- self.queue_game.put("\a")
- if ch == "'\r":
- self.queue_game.put(self.r + self.nl)
- assert(not self.save is None)
- self.observer.load(self.save)
- self.save = None
- self.keepalive.stop()
- self.keepalive = None
- line = self.input
- self.input = ''
- assert(not self.defer is None)
- reactor.callLater(0, self.defer.callback, line)
- self.defer = None
- if ch.isprintable():
- if len(self.input) + 1 <= self.limit:
- self.input += c
- self.queue_game.put(ch)
- else:
- self.queue_game.put("\a")
- def output(self, line):
- """ A default display of what they just input. """
- log.msg("PlayerInput.output({0})".format(line))
- self.game.queue_game.put(self.r + self.nl + "[{0}]".format(line) + self.nl)
- return line
- class ProxyMenu(object):
- def __init__(self, game):
- self.nl = "\n\r"
- self.c = merge(Style.BRIGHT + Fore.YELLOW + Back.BLUE)
- self.r = Style.RESET_ALL
- self.c1 = merge(Style.BRIGHT + Fore.BLUE)
- self.c2 = merge(Style.NORMAL + Fore.BLUE)
- self.game = game
- self.queue_game = game.queue_game
- self.observer = game.observer
-
- self.prompt = game.buffer
- self.save = self.observer.save()
- self.observer.connect("player", self.player)
-
- self.defer = None
- self.keepalive = task.LoopingCall(self.awake)
- self.keepalive.start(30)
- self.menu()
- def __del__(self):
- log.msg("ProxyMenu {0} RIP".format(self))
- def whenDone(self):
- self.defer = defer.Deferred()
-
- return self.defer
- def menu(self):
- self.queue_game.put(self.nl + self.c + "TradeWars Proxy active." + self.r + self.nl)
- def menu_item(ch, desc):
- self.queue_game.put(" " + self.c1 + ch + self.c2 + " - " + self.c1 + desc + self.nl)
- self.queue_game.put(" " + self.c1 + "D" + self.c2 + " - " + self.c1 + "Diagnostics" + self.nl)
- menu_item("Q", "Quest")
- menu_item("T", "Display current Time")
- self.queue_game.put(" " + self.c1 + "P" + self.c2 + " - " + self.c1 + "Port CIM Report" + self.nl)
-
- self.queue_game.put(" " + self.c1 + "S" + self.c2 + " - " + self.c1 + "Scripts" + self.nl)
- self.queue_game.put(" " + self.c1 + "X" + self.c2 + " - " + self.c1 + "eXit" + self.nl)
- self.queue_game.put(" " + self.c + "-=>" + self.r + " ")
- def awake(self):
- log.msg("ProxyMenu.awake()")
- self.game.queue_player.put(" ")
- def player(self, chunk):
- """ Data from player (in bytes). """
- chunk = chunk.decode("utf-8", 'ignore')
- key = chunk.upper()
- log.msg("ProxyMenu.player({0})".format(key))
-
- self.keepalive.stop()
- if key == "T":
- self.queue_game.put(self.c + key + self.r + self.nl)
-
- now = pendulum.now()
- self.queue_game.put(self.nl + self.c1 + "Current time " + now.to_datetime_string() + self.nl)
- elif key == 'Q':
- self.queue_game.put(self.c + key + self.r + self.nl)
- ask = PlayerInput(self.game)
- d = ask.prompt("What is your quest?", 20)
- d.callback(ask.prompt, "What is your favorite color?", 10)
- elif key == 'X':
- self.queue_game.put(self.c + key + self.r + self.nl)
- self.observer.load(self.save)
- self.save = None
-
-
- self.keepalive = None
- self.queue_game.put(self.prompt)
- self.prompt = None
-
- if self.defer:
- reactor.CallLater(0, self.defer.callback, None)
-
- self.defer = None
- return
- self.keepalive.start(30, True)
- self.menu()
- from mcp import MCP
- class Game(protocol.Protocol):
- def __init__(self):
- self.buffer = ""
- self.game = None
- self.usergame = (None, None)
- self.to_player = True
- self.mcp = MCP(self)
- def connectionMade(self):
- log.msg("Connected to Game Server")
- self.queue_player = self.factory.queue_player
- self.queue_game = self.factory.queue_game
- self.observer = self.factory.observer
- self.factory.game = self
- self.setPlayerReceived()
- self.observer.connect("user-game", self.show_game)
- self.mcp.finishSetup()
- def show_game(self, game):
- self.usergame = game
- log.msg("## User-Game:", game)
- def setPlayerReceived(self):
- """ Get deferred from client queue, callback clientDataReceived. """
- self.queue_player.get().addCallback(self.playerDataReceived)
- def playerDataReceived(self, chunk):
- if chunk is False:
- self.queue_player = None
- log.msg("Player: disconnected, close connection to game")
-
- self.factory.continueTrying = False
- self.transport.loseConnection()
- else:
-
- if type(chunk) == str:
- self.transport.write(chunk.encode())
- else:
- self.transport.write(chunk)
- self.setPlayerReceived()
- def lineReceived(self, line):
- """ line received from the game. """
- if LOG_LINES:
- log.msg(">> [{0}]".format(line))
- if "TWGS v2.20b" in line and "www.eisonline.com" in line:
- self.queue_game.put(
- "TWGS Proxy build "
- + version
- + " is active. \x1b[1;34m~\x1b[0m to activate.\n\r\n\r",
- )
- if "TradeWars Game Server" in line and "Copyright (C) EIS" in line:
-
- if not self.game is None:
-
- self.game = None
- self.observer.emit("user-game", (self.factory.player.user, self.game))
- if "Selection (? for menu): " in line:
- game = line[-1]
- if game >= "A" and game < "Q":
- self.game = game
- log.msg("Game: {0}".format(self.game))
- self.observer.emit("user-game", (self.factory.player.user, self.game))
- self.observer.emit("game-line", line)
- def getPrompt(self):
- """ Return the current prompt, stripped of ANSI. """
- return cleanANSI(self.buffer)
- def dataReceived(self, chunk):
- """ Data received from the Game.
-
- Remove backspaces.
- Treat some ANSI codes as NewLine.
- Remove ANSI.
- Break into lines.
- Trim out carriage returns.
- Call lineReceived().
-
- "Optionally" pass data to player.
- FUTURE: trigger on prompt. [cleanANSI(buffer)]
- """
-
-
-
-
-
- if self.to_player:
- self.queue_game.put(chunk)
- self.buffer += chunk.decode("utf-8", "ignore")
-
- while "\b" in self.buffer:
- part = self.buffer.partition("\b")
- self.buffer = part[0][:-1] + part[2]
-
- self.buffer = treatAsNL(self.buffer)
-
- while "\n" in self.buffer:
- part = self.buffer.partition("\n")
- line = part[0].replace("\r", "")
-
- line = cleanANSI(line)
- self.lineReceived(line)
- self.buffer = part[2]
- self.observer.emit("prompt", self.getPrompt())
- def connectionLost(self, why):
- log.msg("Game connectionLost because: %s" % why)
- self.observer.emit('close', why)
- self.queue_game.put(False)
- self.transport.loseConnection()
- class GlueFactory(protocol.ClientFactory):
-
- maxDelay = 10
- protocol = Game
- def __init__(self, player):
- self.player = player
- self.queue_player = player.queue_player
- self.queue_game = player.queue_game
- self.observer = player.observer
- self.game = None
- def closeIt(self):
- log.msg("closeIt")
- self.queue_game.put(False)
- def getUser(self, user):
- log.msg("getUser( %s )" % user)
- self.twgs.logUser(user)
-
-
-
-
- def clientConnectionFailed(self, connector, why):
- log.msg("connection to game failed: %s" % why)
- self.queue_game.put(b"Sorry! I'm Unable to connect to the game server.\r\n")
-
-
- reactor.callLater(2, self.closeIt)
- class Player(protocol.Protocol):
- def __init__(self):
- self.buffer = ""
- self.user = None
- self.observer = Observer()
- self.game = None
- self.glue = None
- def connectionMade(self):
- """ connected, setup queues.
-
- queue_player is data from player.
- queue_game is data to player. (possibly from game)
- """
- self.queue_player = defer.DeferredQueue()
- self.queue_game = defer.DeferredQueue()
- self.setGameReceived()
-
- factory = GlueFactory(self)
- self.glue = factory
-
- reactor.connectTCP(HOST, PORT, factory, 5)
- def setGameReceived(self):
- """ Get deferred from client queue, callback clientDataReceived. """
- self.queue_game.get().addCallback(self.gameDataReceived)
- def gameDataReceived(self, chunk):
- """ Data received from the game. """
-
- if self.game is None:
- self.game = self.glue.game
- if chunk is False:
- self.transport.loseConnection()
- else:
- if type(chunk) == bytes:
- self.transport.write(chunk)
- elif type(chunk) == str:
- self.transport.write(chunk.encode())
- else:
- log.err("gameDataReceived: type (%s) given!", type(chunk))
- self.transport.write(chunk)
- self.setGameReceived()
- def dataReceived(self, chunk):
- if self.user is None:
- self.buffer += chunk.decode("utf-8", "ignore")
- parts = self.buffer.split("\x00")
- if len(parts) >= 5:
-
- self.user = parts[1]
- log.msg("User: {0}".format(self.user))
- zpos = self.buffer.rindex("\x00")
- self.buffer = self.buffer[zpos + 1 :]
-
- self.buffer = ""
-
- self.observer.emit("user", self.user)
-
- if not self.observer.emit("player", chunk):
-
- self.queue_player.put(chunk)
-
- else:
-
- return
- if chunk == b"~":
- if self.game:
- prompt = self.game.getPrompt()
- if re.match(r"Command \[TL=.* \(\?=Help\)\? :", prompt):
- self.observer.emit("hotkey", prompt)
- else:
- self.observer.emit("notyet", prompt)
-
-
-
-
- if chunk == b"|":
-
-
-
-
-
- menu = ProxyMenu(self.game)
- def connectionLost(self, why):
- log.msg("lost connection %s" % why)
- self.observer.emit('close', why)
- self.queue_player.put(False)
- def connectionFailed(self, why):
- log.msg("connectionFailed: %s" % why)
- if __name__ == "__main__":
- if LOGFILE:
- log.startLogging(DailyLogFile("proxy.log", "."))
- else:
- log.startLogging(sys.stdout)
- log.msg("This is version: %s" % version)
- factory = protocol.Factory()
- factory.protocol = Player
- reactor.listenTCP(LISTEN_PORT, factory, interface=LISTEN_ON)
- reactor.run()
|