|
@@ -0,0 +1,327 @@
|
|
|
+
|
|
|
+
|
|
|
+import sys
|
|
|
+import re
|
|
|
+
|
|
|
+from twisted.internet import defer
|
|
|
+from twisted.internet import protocol
|
|
|
+from twisted.internet import reactor
|
|
|
+from twisted.python import log
|
|
|
+from twisted.python.logfile import DailyLogFile
|
|
|
+
|
|
|
+
|
|
|
+import pendulum
|
|
|
+from subprocess import check_output
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+try:
|
|
|
+ from config_dev import *
|
|
|
+except ModuleNotFoundError:
|
|
|
+ from config import *
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+version = check_output(
|
|
|
+ [
|
|
|
+ "git",
|
|
|
+ "describe",
|
|
|
+ "--long",
|
|
|
+ "--tags",
|
|
|
+
|
|
|
+ "--always",
|
|
|
+ "--match",
|
|
|
+ "v[0-9]\.[0-9]\.[0-9]",
|
|
|
+ ],
|
|
|
+ universal_newlines=True,
|
|
|
+).strip()
|
|
|
+
|
|
|
+
|
|
|
+cleaner = re.compile(r"\x1b\[[0-9;]*[A-Zmh]")
|
|
|
+
|
|
|
+makeNL = re.compile(r"\x1b\[[0-9;]*[J]")
|
|
|
+
|
|
|
+
|
|
|
+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)
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+class Game(protocol.Protocol):
|
|
|
+ def __init__(self):
|
|
|
+
|
|
|
+ self.user = None
|
|
|
+
|
|
|
+ self.buffer = ""
|
|
|
+
|
|
|
+ def connectionMade(self):
|
|
|
+ log.msg("Client: connected to peer")
|
|
|
+ self.queue_player = self.factory.queue_player
|
|
|
+ self.queue_game = self.factory.queue_game
|
|
|
+ self.setPlayerReceived()
|
|
|
+
|
|
|
+
|
|
|
+ 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_twgs = None
|
|
|
+ log.msg("Client: disconnecting from peer")
|
|
|
+ self.factory.continueTrying = False
|
|
|
+ self.transport.loseConnection()
|
|
|
+ else:
|
|
|
+ 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.factory.getUser(self.user)
|
|
|
+
|
|
|
+
|
|
|
+ self.transport.write(chunk)
|
|
|
+ self.setPlayerReceived()
|
|
|
+
|
|
|
+
|
|
|
+ def dataReceived(self, chunk):
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ self.queue_player.put(chunk)
|
|
|
+
|
|
|
+ def connectionLost(self, why):
|
|
|
+ log.msg("Game connectionLost because: %s" % why)
|
|
|
+ self.queue_game.put(False)
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ self.transport.loseConnection()
|
|
|
+
|
|
|
+
|
|
|
+class GlueFactory(protocol.ClientFactory):
|
|
|
+
|
|
|
+ maxDelay = 10
|
|
|
+ protocol = Game
|
|
|
+
|
|
|
+ def __init__(self, twgs):
|
|
|
+ self.twgs = twgs
|
|
|
+ self.queue_player = twgs.queue_player
|
|
|
+ self.queue_game = twgs.queue_game
|
|
|
+
|
|
|
+ def closeIt(self):
|
|
|
+ log.msg("closeIt")
|
|
|
+ self.queue_player.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_player.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.fpRaw = None
|
|
|
+ self.fpLines = None
|
|
|
+ self.action = None
|
|
|
+ self.username = ""
|
|
|
+ self.game = ""
|
|
|
+ self.passon = True
|
|
|
+
|
|
|
+ def connectionMade(self):
|
|
|
+ """ connected, setup queues. """
|
|
|
+ self.queue_player = defer.DeferredQueue()
|
|
|
+ self.queue_game = defer.DeferredQueue()
|
|
|
+ self.setGameReceived()
|
|
|
+
|
|
|
+
|
|
|
+ factory = GlueFactory(self)
|
|
|
+
|
|
|
+ reactor.connectTCP(HOST, PORT, factory, 5)
|
|
|
+
|
|
|
+ def setGameReceived(self):
|
|
|
+ """ Get deferred from client queue, callback clientDataReceived. """
|
|
|
+ self.queue_game.get().addCallback(self.gameDataReceived)
|
|
|
+
|
|
|
+ def logUser(self, user):
|
|
|
+ """ We have the username. """
|
|
|
+ now = pendulum.now()
|
|
|
+ self.username = user
|
|
|
+ filename = now.format("YYYY-MM-DD_HHmm") + "-" + user.lower()
|
|
|
+
|
|
|
+
|
|
|
+ if RAW:
|
|
|
+ self.fpRaw = open(filename + ".raw", "ab")
|
|
|
+ self.fpLines = open(filename + ".lines", "a")
|
|
|
+ print("Log created:", now.to_rss_string(), "\n", file=self.fpLines)
|
|
|
+
|
|
|
+ def setGame(self, game):
|
|
|
+ """ We have the game (A-P) they are playing. """
|
|
|
+ if self.game != game:
|
|
|
+ log.msg("USER {0} ENTERED {1}".format(self.username, self.game))
|
|
|
+ self.data = {}
|
|
|
+ self.game = game
|
|
|
+
|
|
|
+ def gotLine(self, line):
|
|
|
+ """ We got a line from the server.
|
|
|
+
|
|
|
+ This is ANSI filtered.
|
|
|
+ Backspaces have removed the character from the line.
|
|
|
+ The line is unicode. We don't need to decode it. ;)
|
|
|
+
|
|
|
+ """
|
|
|
+ log.msg(">>> [{0}]".format(line))
|
|
|
+ if self.fpLines is not None:
|
|
|
+ print(line, file=self.fpLines)
|
|
|
+
|
|
|
+ if "TWGS v2.20b" in line:
|
|
|
+ if "www.eisonline.com" in line:
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ self.queue_client.put(
|
|
|
+ (
|
|
|
+ b"TWGS Proxy build "
|
|
|
+ + version.encode()
|
|
|
+ + b" 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:
|
|
|
+
|
|
|
+ self.game = ""
|
|
|
+ if "Selection (? for menu): " in line:
|
|
|
+ game = line[-1]
|
|
|
+ if game >= "A" and game < "Q":
|
|
|
+ self.setGame(game)
|
|
|
+
|
|
|
+ if self.action and not self.passon:
|
|
|
+ self.action.server(line)
|
|
|
+
|
|
|
+ def gameDataReceived(self, chunk):
|
|
|
+ """ Data received from the client/player. """
|
|
|
+ if chunk is False:
|
|
|
+ self.transport.loseConnection()
|
|
|
+ else:
|
|
|
+ if type(chunk) is tuple:
|
|
|
+
|
|
|
+
|
|
|
+ self.transport.write(chunk[0])
|
|
|
+ self.setGameReceived()
|
|
|
+ else:
|
|
|
+ if self.fpRaw is not None:
|
|
|
+ self.fpRaw.write(chunk)
|
|
|
+ self.buffer += chunk.decode("utf-8", "ignore")
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ while "\x08" in self.buffer:
|
|
|
+ part = self.buffer.partition("\x08")
|
|
|
+ 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.gotLine(line)
|
|
|
+ self.buffer = part[2]
|
|
|
+
|
|
|
+
|
|
|
+ if self.passon:
|
|
|
+ self.transport.write(chunk)
|
|
|
+
|
|
|
+ self.setGameReceived()
|
|
|
+
|
|
|
+ def dataReceived(self, chunk):
|
|
|
+ if self.action and self.action.isActive():
|
|
|
+
|
|
|
+ self.action.received(chunk)
|
|
|
+ else:
|
|
|
+
|
|
|
+ if chunk == b"~":
|
|
|
+ self.action.activate(self.buffer)
|
|
|
+ else:
|
|
|
+ self.queue_game.put(chunk)
|
|
|
+
|
|
|
+ def connectionLost(self, why):
|
|
|
+ log.msg("lost connection %s" % why)
|
|
|
+ self.queue_game.put(False)
|
|
|
+
|
|
|
+
|
|
|
+ if self.fpRaw is not None:
|
|
|
+ self.fpRaw.close()
|
|
|
+ self.fpRaw = None
|
|
|
+ if self.fpLines is not None:
|
|
|
+ if self.buffer != "":
|
|
|
+ print(self.buffer, file=self.fpLines)
|
|
|
+ self.fpLines.close()
|
|
|
+ self.fpLines = None
|
|
|
+
|
|
|
+ def connectionFailed(self, why):
|
|
|
+ log.msg("connectionFailed: %s" % why)
|
|
|
+
|
|
|
+
|
|
|
+if __name__ == "__main__":
|
|
|
+ log.startLogging(DailyLogFile("proxy.log", "."))
|
|
|
+ log.msg("This is version: %s" % version)
|
|
|
+ factory = protocol.Factory()
|
|
|
+ factory.protocol = Player
|
|
|
+ reactor.listenTCP(LISTEN_PORT, factory, interface=LISTEN_ON)
|
|
|
+ reactor.run()
|