示例#1
0
    def __init__(
        self,
        pollInterval=10,
        ircClient: IRC_Client = None,
        settings=None
                ):
        threading.Thread.__init__(self)
        try:
            logging.info("Memory Monitor Started!")
            self.running = True

            self.settings = settings
            if not settings:
                self.settings = Settings()

            self.pm = None
            self.baseAddress = None
            self.gameInProgress = False

            self.ircClient = ircClient
            self.pollInterval = int(pollInterval)
            self.event = threading.Event()
            self.gameData = None
            self.winLostTimer = None

        except Exception as e:
            logging.error("In FileMonitor __init__")
            logging.error(str(e))
            logging.exception("Exception : ")
    def __init__(self, ucsPath=None, settings=None) -> None:

        self.settings = settings
        if not settings:
            self.settings = Settings()

        self.ucsPath = ucsPath
        if not ucsPath:
            self.ucsPath = self.settings.data.get('cohUCSPath')
示例#3
0
    def __init__(self, settings=None):
        self.settings = settings
        if not settings:
            self.settings = Settings()

        # Declare instance variables for storing data
        # returned from server (nested List/Dictionary)
        self.userStatCache = None
        self.userMatchHistoryCache = None
        self.availableLeaderboards = None
示例#4
0
    def __init__(self, ircClient=None, settings=None):
        """Instanciates a new object of type GameData"""

        self.settings = settings
        if not settings:
            self.settings = Settings()

        # Local reference to the IRC client
        self.ircClient = ircClient

        # Pymem handle
        self.pm = None
        self.baseAddress = None
        self.cohProcessID = None

        # Replay Data Default Values
        self.playerList = []
        self.numberOfHumans = 0
        self.numberOfComputers = 0
        self.easyCPUCount = 0
        self.normalCPUCount = 0
        self.hardCPUCount = 0
        self.expertCPUCount = 0
        self.numberOfPlayers = 0
        self.slots = 0
        self.matchType = MatchType.CUSTOM
        self.cohRunning = False
        self.gameInProgress = False
        self.gameStartedDate = None

        self.randomStart = None
        self.highResources = None
        self.VPCount = None
        self.automatch = None
        self.mapName = None
        self.mapNameFull = ""
        self.modName = None
        self.mapDescription = None
        self.mapDescriptionFull = ""
        self.gameDescriptionString = ""

        # This holds a list of IRC string outputs.
        self.ircStringOutputList = []
示例#5
0
    def test_output(self):
        """Produces text output according to Preformat."""
        if not self.settings:
            self.settings = Settings()

        steamNumber = self.settings.data.get('steamNumber')
        statsRequest = StatsRequest(settings=self.settings)
        streamerStats = statsRequest.return_stats(str(steamNumber))
        streamerPlayer = Player(name=self.settings.data.get('channel'))
        streamerPlayer.stats = streamerStats
        if streamerPlayer.stats:
            output = (
                "Streamer Full Stat list formatted according"
                " to Custom Chat Output Preformat:"
            )
            self.ircClient.send_to_outputfield(output)
            preformat = self.settings.data.get('customStringPreFormat')
            self.ircClient.send_to_outputfield(preformat)

            for match in MatchType:
                for faction in Faction:
                    for value in streamerPlayer.stats.leaderboardData:
                        ld = streamerPlayer.stats.leaderboardData[value]
                        playerFaction = str(ld.faction)
                        if (playerFaction == str(faction)):
                            playerMatchtype = str(ld.matchType)
                            if (playerMatchtype == str(match)):
                                self.matchType = match
                                streamerPlayer.faction = faction
                                self.__produceOutput(streamerPlayer)

        else:
            output = (
                "I could not get stats from the stat server using steam# "
                f"{steamNumber} it might be down or the steam# might "
                "be invalid."
            )
            self.ircClient.send_to_outputfield(output)
    def __init__(
        self,
        ircClient,
        ircSocket: socket,
        queue,
        channel,
        settings=None
            ):
        Thread.__init__(self)
        self.ircClient = ircClient
        self.running = True
        self.ircSocket = ircSocket
        self.queue = queue
        self.channel = channel

        self.settings = settings
        if not settings:
            self.settings = Settings()

        self.gameData = GameData(self.ircClient, settings=self.settings)
示例#7
0
    def __init__(self, filePath=None, parameters=None) -> None:

        self.parameters = parameters
        if not parameters:
            self.parameters = Settings()

        self.filePath = filePath

        self.fileVersion = None
        self.chunkyVersion = None
        self.randomStart = None
        self.highResources = None
        self.VPCount = None
        self.matchType = None
        self.localDateString = None
        self.localDate = None
        self.unknownDate = None
        self.replayName = None
        self.gameVersion = None
        self.modName = None
        self.mapName = None
        self.mapNameFull = None
        self.mapDescription = None
        self.mapDescriptionFull = None
        self.mapFileName = None
        self.mapWidth = None
        self.mapHeight = None
        self.playerList = []

        self.success = None

        self.data = None
        self.dataIndex = 0

        if filePath:
            self.load(self.filePath)
    def __init__(self, output, consoleDisplayBool, settings=None):

        threading.Thread.__init__(self)

        self.output = output

        self.displayConsoleOut = consoleDisplayBool

        self.settings = settings
        if not settings:
            self.settings = Settings()

        self.adminUserName = self.settings.privatedata.get('adminUserName')
        # This username will be able to use admin commands
        # exit the program and bypass some limits.

        # use botusername or get default if not set
        if (self.settings.data.get('botUserName') == ""):
            self.nick = self.settings.privatedata.get('IRCnick')
            # This value is the username used to connect to IRC
            # eg: "xcomreborn".
        else:
            self.nick = self.settings.data.get('botUserName')

        self.channel = "#" + self.settings.data.get('channel').lower()
        # The channel name for your channel eg: "#xcomreborn".

        # use botoauthkey or get default if not set
        if (self.settings.data.get('botOAuthKey') == ""):
            self.password = self.settings.privatedata.get('IRCpassword')
        else:
            self.password = self.settings.data.get('botOAuthKey')

        self.server = self.settings.privatedata.get('IRCserver')
        self.port = self.settings.privatedata.get('IRCport')
        rsp = self.settings.privatedata.get('relicServerProxy')
        self.relicServerProxy = rsp

        # create IRC socket
        try:
            self.ircSocket = socket.socket()
        except Exception as e:
            logging.error("A problem occurred trying to connect")
            logging.error("In IRCClient")
            logging.error(str(e))
            logging.exception("Exception : ")
            self.ircSocket.close()
            sys.exit(0)

        # irc send message buffer
        self.ircMessageBuffer = collections.deque()
        self.messageBufferTimer = None

        self.running = True

        # Start checking send buffer every 3 seconds.

        self.check_IRC_send_buffer_every_three_seconds()
        # only call this once.

        try:
            self.ircSocket.connect((self.server, self.port))
        except Exception as e:
            logging.error("A problem occurred trying to connect")
            logging.error("In IRCClient")
            logging.error(str(e))
            logging.exception("Exception : ")
            self.ircSocket.close()
            sys.exit(0)

        # sends variables for connection to twitch chat
        self.ircSocket.send(('PASS ' + self.password + '\r\n').encode("utf8"))
        self.ircSocket.send(('USER ' + self.nick + '\r\n').encode("utf8"))
        self.ircSocket.send(('NICK ' + self.nick + '\r\n').encode("utf8"))
        self.ircSocket.send(
            ('CAP REQ :twitch.tv/membership' + '\r\n').encode("utf8"))
        # sends a twitch specific request
        # necessary to recieve mode messages
        self.ircSocket.send(
            ('CAP REQ :twitch.tv/tags' + '\r\n').encode("utf8"))
        # sends a twitch specific request for extra data
        # contained in the PRIVMSG changes the way it is parsed
        self.ircSocket.send(
            ('CAP REQ :twitch.tv/commands' + '\r\n').encode("utf8"))

        # start sub thread that uses shared Queue to communicate
        # pass it irc for messaging, channel to join and queue
        self.queue = Queue()
        self.channelThread = IRC_Channel(self,
                                         self.ircSocket,
                                         self.queue,
                                         self.channel,
                                         settings=self.settings)
        self.channelThread.start()
示例#9
0
class GameData():
    """Contains information about the current COH1 game and players. """

    def __init__(self, ircClient=None, settings=None):
        """Instanciates a new object of type GameData"""

        self.settings = settings
        if not settings:
            self.settings = Settings()

        # Local reference to the IRC client
        self.ircClient = ircClient

        # Pymem handle
        self.pm = None
        self.baseAddress = None
        self.cohProcessID = None

        # Replay Data Default Values
        self.playerList = []
        self.numberOfHumans = 0
        self.numberOfComputers = 0
        self.easyCPUCount = 0
        self.normalCPUCount = 0
        self.hardCPUCount = 0
        self.expertCPUCount = 0
        self.numberOfPlayers = 0
        self.slots = 0
        self.matchType = MatchType.CUSTOM
        self.cohRunning = False
        self.gameInProgress = False
        self.gameStartedDate = None

        self.randomStart = None
        self.highResources = None
        self.VPCount = None
        self.automatch = None
        self.mapName = None
        self.mapNameFull = ""
        self.modName = None
        self.mapDescription = None
        self.mapDescriptionFull = ""
        self.gameDescriptionString = ""

        # This holds a list of IRC string outputs.
        self.ircStringOutputList = []

    def clear_data(self):
        """Clears all the variable information in the GameData instance."""

        self.playerList = []
        self.numberOfHumans = 0
        self.numberOfComputers = 0
        self.easyCPUCount = 0
        self.normalCPUCount = 0
        self.hardCPUCount = 0
        self.expertCPUCount = 0
        self.numberOfPlayers = 0
        self.slots = 0
        self.matchType = MatchType.CUSTOM
        self.cohRunning = False
        self.gameInProgress = False
        self.gameStartedDate = None
        self.cohProcessID = None
        self.randomStart = None
        self.highResources = None
        self.VPCount = None
        self.automatch = None
        self.mapName = None
        self.mapNameFull = ""
        self.modName = None
        self.mapDescription = None
        self.mapDescriptionFull = ""
        self.gameDescriptionString = ""

        # This holds a list of IRC string outputs.
        self.ircStringOutputList = []

    def get_data_from_game(self):
        """Attempts to get all the COH information from memory."""

        # First Clear all data that can be aquired from the game
        self.clear_data()

        # Check if company of heroes is running if not return false
        if not self.get_COH_memory_address():
            return False

        # Check if a game is currently in progress if not return false.
        # replayParser = self.Get_replayParser_BySearch()
        replayParser = self.get_replayParser_by_search()
        if not replayParser:
            return False

        self.gameStartedDate = replayParser.localDate
        self.randomStart = replayParser.randomStart

        self.highResources = replayParser.highResources
        self.VPCount = replayParser.VPCount
        if replayParser.matchType.lower() == "automatch":
            self.automatch = True
        else:
            self.automatch = False

        self.mapName = replayParser.mapName
        self.mapDescription = replayParser.mapDescription
        self.modName = replayParser.modName

        for item in replayParser.playerList:
            username = item['name']
            factionString = item['faction']
            player = Player(name=username, factionString=factionString)
            self.playerList.append(player)

        statList = self.get_stats_from_game()

        for player in self.playerList:
            if statList:
                for stat in statList:
                    try:
                        alias = str(stat.alias).encode('utf-16le')
                        name = str(player.name).encode('utf-16le')
                        if alias == name:
                            player.stats = stat
                    except Exception as e:
                        logging.error(str(e))
                        logging.exception("Stack : ")

                    steamNumber = self.settings.data.get('steamNumber')
                    ps = player.stats
                    if ps:
                        if steamNumber == ps.steamNumber:
                            self.settings.data['steamAlias'] = ps.alias
                            self.settings.save()

        humans = sum(item.stats is not None for item in self.playerList)
        self.numberOfHumans = humans

        cpuCounter = 0
        easyCounter = 0
        normalCounter = 0
        hardCounter = 0
        expertCounter = 0

        for item in self.playerList:
            if (item.stats is None):
                if ("CPU" in item.name):
                    cpuCounter += 1
                if ("Easy" in item.name):
                    easyCounter += 1
                if ("Normal" in item.name):
                    normalCounter += 1
                if ("Hard" in item.name):
                    hardCounter += 1
                if ("Expert" in item.name):
                    expertCounter += 1

        self.numberOfComputers = cpuCounter
        self.numberOfPlayers = cpuCounter + self.numberOfHumans
        self.easyCPUCount = easyCounter
        self.normalCPUCount = normalCounter
        self.hardCPUCount = hardCounter
        self.expertCPUCount = expertCounter

        self.slots = len(replayParser.playerList)

        # Set the current MatchType

        self.matchType = MatchType.CUSTOM
        if (int(self.numberOfComputers) > 0):
            self.matchType = MatchType.CUSTOM
        if self.automatch:
            if (0 <= int(self.slots) <= 2):
                if (int(self.numberOfComputers) == 0):
                    self.matchType = MatchType.ONES
            if (3 <= int(self.slots) <= 4):
                if (int(self.numberOfComputers) == 0):
                    self.matchType = MatchType.TWOS
            if (5 <= int(self.slots) <= 6):
                if (int(self.numberOfComputers) == 0):
                    self.matchType = MatchType.THREES

        return True

    def get_game_description_string(self) -> str:
        """Produces a single-line game description string."""

        offset = time.timezone
        if (time.localtime().tm_isdst == 0):
            offset = time.timezone
        else:
            time.altzone

        offset = offset / 60 / 60 * -1
        hours = offset
        hours_added = datetime.timedelta(hours=hours)
        UTC_corrected_start_time = self.gameStartedDate + hours_added

        gameStarted = str(UTC_corrected_start_time)
        channelName = self.settings.data.get('channel')

        # Get the map full name from ucs file this takes time and so
        # should only be called when output is intended.

        self.get_mapNameFull_from_UCS_file()

        try:
            numberOfHumans = str(int(self.numberOfHumans))
            numberOfComputers = str(int(self.numberOfComputers))
            numberOfPlayers = str(int(self.numberOfPlayers))
            slots = str(int(self.slots))
            randomStart = str(int(self.randomStart))
            highResources = str(int(self.highResources))
            VPCount = str(int(self.VPCount))
            automatch = str(int(self.automatch))
            mapNameFull = str(self.mapNameFull)
            modName = str(self.modName)

        except Exception as e:
            logging.error("Problem Creating Game Description")
            logging.exception("Exception : ")
            logging.error(str(e))

        message = (
            f"!start,{channelName},{gameStarted},{numberOfHumans},"
            f"{numberOfComputers},{numberOfPlayers},{slots},{randomStart},"
            f"{highResources},{VPCount},{automatch},{mapNameFull},{modName}"
            )

        for count, item in enumerate(self.playerList):
            steamNumber = ""
            if item.stats:
                steamNumber = item.stats.steamNumber
            else:
                steamNumber = item.name

            faction = ""
            if item.faction:
                faction = item.faction.name
            team = "0"
            if count >= (len(self.playerList)/2):
                team = "1"

            message += f",{str(steamNumber)},{str(faction)},{str(team)}"

        self.gameDescriptionString = message
        return self.gameDescriptionString

    def get_mapDescriptionFull_from_UCS_file(self):
        """mapDescriptionFull will be None until resolved.

        This takes time because file reading. gets mapDescriptionFull
        from ucs file if they exist there.
        """

        try:
            ucs = UCS(settings=self.settings)
            self.mapDescriptionFull = ucs.compare_UCS(self.mapDescription)

        except Exception as e:
            logging.error("Problem in GetMapDescription")
            logging.exception("Exception : ")
            logging.error(str(e))

    def get_mapNameFull_from_UCS_file(self):
        """mapNameFull will be None until resolved.

        This takes time because file reading
        get mapNameFull from ucs file if they exist there.
        """

        try:
            ucs = UCS(settings=self.settings)
            self.mapNameFull = ucs.compare_UCS(self.mapName)
        except Exception as e:
            logging.error("Problem in GetMapNameFull")
            logging.exception("Exception : ")
            logging.error(str(e))

    def get_pointer_address(self, base: int, offsets: list) -> int | None:
        """Gets memory address from a pointer address (base) and applies offsets.

        Parameters
        ----------
        base: int
            The base memory address.

        offsets: list
            The list of offsets.

        Returns
        -------
        int
            The memory address.
        """

        try:
            if self.pm:
                addr = self.pm.read_int(base)
                for i in offsets:
                    if i != offsets[-1]:
                        addr = self.pm.read_int(addr + i)
                return addr + offsets[-1]
        except Exception as e:
            if e:
                return None

    def get_COH_memory_address(self) -> bool:
        """Gets the active process for RelicCOH.exe

        Returns
        -------
        bool
            True if COH is running.
        """

        try:
            self.pm = pymem.Pymem("RelicCOH.exe")
        except Exception as e:
            if e:
                pass
        if self.pm:
            self.cohProcessID = self.pm.process_id
            logging.info(f"self.pm : {str(self.pm)}")
            logging.info(f"cohProcessID : {str(self.cohProcessID)}")
            ph = self.pm.process_handle
            mi = pymem.process.module_from_name(ph, "RelicCOH.exe")
            if mi:
                self.baseAddress = mi.lpBaseOfDll
                info = (
                    "getCOHMemoryAddress self.baseAddress :"
                    f"{str(self.baseAddress)}"
                )
                logging.info(info)
                self.cohRunning = True
                logging.info(f"self.cohRunning {self.cohRunning}")
                return True
        else:
            self.cohRunning = False
            logging.info(f"self.cohRunning {self.cohRunning}")
            return False

    def get_replayParser_by_pointer(self) -> ReplayParser:
        """Gets an instance of the replayParser containing COH game info."""

        # There are four pointers to the replay that appear to be 'static'
        # We can try them in sequence and return the data from the first
        # one that works (Probably the first one)
        myListOfCOHRECPointers = []
        # 1
        cohrecReplayAddress = 0x00902030
        cohrecOffsets = [0x28, 0x160, 0x4, 0x84, 0x2C, 0x110, 0x0]
        myListOfCOHRECPointers.append([cohrecReplayAddress, cohrecOffsets])

        # 2
        cohrecReplayAddress = 0x00902030
        cohrecOffsets = [0x28, 0x160, 0x4, 0x84, 0x24, 0x110, 0x0]
        myListOfCOHRECPointers.append([cohrecReplayAddress, cohrecOffsets])

        # 3
        cohrecReplayAddress = 0x00902030
        cohrecOffsets = [0x4, 0x160, 0x4, 0x118, 0x110, 0x0]
        myListOfCOHRECPointers.append([cohrecReplayAddress, cohrecOffsets])

        # 4
        cohrecReplayAddress = 0x00902030
        cohrecOffsets = [0x4, 0x160, 0x4, 0x110, 0x110, 0x0]
        myListOfCOHRECPointers.append([cohrecReplayAddress, cohrecOffsets])

        for count, item in enumerate(myListOfCOHRECPointers):
            logging.info(f"{count} {item}")
            ad = self.get_pointer_address(self.baseAddress + item[0], item[1])
            actualCOHRECMemoryAddress = ad
            info = (
                f"actualCOHRECMemoryAddress :"
                f"{str(actualCOHRECMemoryAddress)}"
                )
            logging.info(info)
            if actualCOHRECMemoryAddress:
                try:
                    rd = self.pm.read_bytes(actualCOHRECMemoryAddress, 4000)
                except Exception as e:
                    logging.error(e)
                if rd:
                    if rd[4:12] == bytes("COH__REC".encode('ascii')):
                        logging.info("Pointing to COH__REC")
                        self.gameInProgress = True
                        replayByteData = bytearray(rd)
                        replayParser = ReplayParser(parameters=self.settings)
                        replayParser.data = bytearray(replayByteData)
                        logging.info("Successfully Parsed Replay Data")
                        success = replayParser.process_data()
                        if success:
                            return replayParser

        # Sets gameInProgress to False if COH__REC was not found
        # eg game is not in progress.
        self.gameInProgress = False

    def get_replayParser_by_search(self) -> ReplayParser:
        """Gets an instance of the replayParser containing COH game info."""

        if self.pm:
            with Process.open_process(self.pm.process_id) as p:

                searchString = bytearray("COH__REC".encode('ascii'))
                buff = bytes(searchString)

                replayMemoryAddress = p.search_all_memory(buff)

                if replayMemoryAddress:
                    for address in replayMemoryAddress:
                        # There should be only one COH__REC in memory
                        # if the game is running
                        self.gameInProgress = True
                        try:
                            rd = self.pm.read_bytes(address-4, 4000)
                            rd = bytearray(rd)
                            rp = ReplayParser(parameters=self.settings)
                            rp.data = bytearray(rd)
                            success = rp.process_data()
                            # Processing game data may not be successful
                            # if the memory is being moved
                            # by memory management
                            if success:
                                logging.info(
                                    "Successfully Parsed Replay Data.")
                                return rp
                            else:
                                logging.info(
                                    "Replay Data did not parse correctly.")
                                return None
                        except Exception as e:
                            if e:
                                pass
                else:
                    logging.info("Cannot detect COH__REC in memory.")
                    self.gameInProgress = False

    def get_stats_from_game(self):
        """Provides stats from playerList names from game memory."""

        if self.pm:
            with Process.open_process(self.pm.process_id) as p:
                steamNumberList = []
                steamNumberList.append(self.settings.data.get('steamNumber'))
                # add default value incase it isn't found.
                for player in self.playerList:
                    name = bytearray(str(player.name).encode('utf-16le'))
                    buff = bytes(name)
                    if buff:
                        replayMemoryAddress = p.search_all_memory(buff)
                        for address in replayMemoryAddress:
                            try:
                                ad = address-56
                                dd = p.read_memory(ad, (ctypes.c_byte * 48)())
                                dd = bytearray(dd)
                                steamNumber = dd.decode('utf-16le').strip()
                                if "/steam/" in steamNumber:
                                    int(steamNumber[7:24])
                                    # throws exception if steamNumber
                                    # is not a number
                                    info = (
                                        f"Got steamNumber from memory "
                                        f"{str(steamNumber[7:24])}"
                                    )
                                    logging.info(info)
                                    sNumber = str(steamNumber[7:24])
                                    steamNumberList.append(sNumber)
                                    break
                            except Exception as e:
                                if e:
                                    pass

                statList = []
                for item in steamNumberList:
                    statRquest = StatsRequest(settings=self.settings)
                    stat = statRquest.return_stats(item)
                    statList.append(stat)
                return statList

    def test_output(self):
        """Produces text output according to Preformat."""
        if not self.settings:
            self.settings = Settings()

        steamNumber = self.settings.data.get('steamNumber')
        statsRequest = StatsRequest(settings=self.settings)
        streamerStats = statsRequest.return_stats(str(steamNumber))
        streamerPlayer = Player(name=self.settings.data.get('channel'))
        streamerPlayer.stats = streamerStats
        if streamerPlayer.stats:
            output = (
                "Streamer Full Stat list formatted according"
                " to Custom Chat Output Preformat:"
            )
            self.ircClient.send_to_outputfield(output)
            preformat = self.settings.data.get('customStringPreFormat')
            self.ircClient.send_to_outputfield(preformat)

            for match in MatchType:
                for faction in Faction:
                    for value in streamerPlayer.stats.leaderboardData:
                        ld = streamerPlayer.stats.leaderboardData[value]
                        playerFaction = str(ld.faction)
                        if (playerFaction == str(faction)):
                            playerMatchtype = str(ld.matchType)
                            if (playerMatchtype == str(match)):
                                self.matchType = match
                                streamerPlayer.faction = faction
                                self.__produceOutput(streamerPlayer)

        else:
            output = (
                "I could not get stats from the stat server using steam# "
                f"{steamNumber} it might be down or the steam# might "
                "be invalid."
            )
            self.ircClient.send_to_outputfield(output)

    def __produceOutput(self, streamerPlayer):
        sFD = self.populate_string_formatting_dictionary(streamerPlayer)
        cPFOS = self.settings.data.get('customStringPreFormat')
        theString = self.format_preformatted_string(cPFOS, sFD)
        outputList = list(self.split_by_n(theString, 500))
        for item in outputList:
            self.ircClient.send_to_outputfield(item)

    def output_opponent_data(self):
        """"Outputs game and player information according to formats."""

        logging.info("In output opponent data")
        logging.info(str(self))

        # Prepare outputs
        axisTeam = []
        alliesTeam = []

        if self.playerList:
            for item in self.playerList:
                if (
                    str(item.faction) == str(Faction.US)
                    or str(item.faction) == str(Faction.CW)
                ):
                    if item.name != "":
                        alliesTeam.append(item)
                if (
                    str(item.faction) == str(Faction.WM)
                    or str(item.faction) == str(Faction.PE)
                ):
                    if item.name != "":
                        axisTeam.append(item)

            # output each player to file
            if (self.settings.data.get('useOverlayPreFormat')):
                self.save_overlay_HTML(axisTeam, alliesTeam)

            # output to chat if customoutput ticked
            if (self.settings.data.get('useCustomPreFormat')):
                if (int(self.numberOfComputers) > 0):
                    self.ircStringOutputList.append(
                        "Game with " + str(self.numberOfComputers) +
                        " computer AI, (" + str(self.easyCPUCount) +
                        ") Easy, (" + str(self.normalCPUCount) +
                        ") Normal, (" + str(self.hardCPUCount) +
                        ") Hard, (" + str(self.expertCPUCount) +
                        ") Expert."
                    )
                for item in self.playerList:
                    # check if item has stats if not it is a computer
                    if item.stats:
                        steamNumber = self.settings.data.get('steamNumber')
                        if(item.stats.steamNumber == steamNumber):
                            if (self.settings.data.get('showOwn')):
                                self.ircStringOutputList = (
                                    self.ircStringOutputList +
                                    self.create_custom_output(item)
                                )
                        else:
                            self.ircStringOutputList = (
                                self.ircStringOutputList +
                                self.create_custom_output(item)
                            )

                for item in self.ircStringOutputList:
                    self.ircClient.send_private_message_to_IRC(str(item))
                    # outputs the information to IRC

    def create_custom_output(self, player) -> list:
        stringFormattingDictionary = (
            self.populate_string_formatting_dictionary(player)
        )
        customPreFormattedOutputString = (
            self.settings.data.get('customStringPreFormat')
        )
        theString = (
            self.format_preformatted_string(
                customPreFormattedOutputString,
                stringFormattingDictionary)
        )
        outputList = list(self.split_by_n(theString, 500))

        return outputList

    def populate_string_formatting_dictionary(self, player, overlay=False):
        prefixDiv = ""
        postfixDivClose = ""
        if overlay:
            prefixDiv = '<div class = "textVariables">'
            postfixDivClose = '</div>'
        stringFormattingDictionary = dict(
            self.settings.stringFormattingDictionary)
        # loads default values from parameters into
        # stringFormattingDictionary (Key: Value:None)
        nameDiv = ""
        factionDiv = ""
        matchDiv = ""
        countryDiv = ""
        totalWinsDiv = ""
        totalLossesDiv = ""
        totalWinLossRatioDiv = ""
        winsDiv = ""
        lossesDiv = ""
        disputesDiv = ""
        streakDiv = ""
        dropsDiv = ""
        rankDiv = ""
        levelDiv = ""
        wlRatioDiv = ""
        steamprofile = ""
        cohstatslink = ""

        if overlay:
            nameDiv = '<div class = "name">'
            factionDiv = '<div class = "faction">'
            matchDiv = '<div class = "matchtype">'
            countryDiv = '<div class = "country">'
            totalWinsDiv = '<div class = "totalwins">'
            totalLossesDiv = '<div class = "totallosses">'
            totalWinLossRatioDiv = '<div class = "totalwlratio">'
            winsDiv = '<div class = "wins">'
            lossesDiv = '<div class = "losses">'
            disputesDiv = '<div class = "disputes">'
            streakDiv = '<div class = "streak">'
            dropsDiv = '<div class = "drops">'
            rankDiv = '<div class = "rank">'
            levelDiv = '<div class = "level">'
            wlRatioDiv = '<div class = "wlratio">'
            steamprofile = '<div class = "steamprofile">'
            cohstatslink = '<div class = "cohstatslink">'

        playerName = self.sanatize_user_name(player.name)
        if not playerName:
            playerName = ""
        stringFormattingDictionary['$NAME$'] = (
            prefixDiv + nameDiv + str(playerName) + postfixDivClose +
            postfixDivClose
        )

        if overlay:
            stringFormattingDictionary['$NAME$'] = (
                prefixDiv + nameDiv + str(html.escape(playerName)) +
                postfixDivClose + postfixDivClose
            )

        if type(player.faction) is Faction:
            stringFormattingDictionary['$FACTION$'] = (
                prefixDiv + factionDiv + str(player.faction.name) +
                postfixDivClose + postfixDivClose
            )

        if (self.matchType == MatchType.CUSTOM):
            stringFormattingDictionary['$MATCHTYPE$'] = (
                prefixDiv + matchDiv + "Basic" + postfixDivClose +
                postfixDivClose
            )

        if (self.matchType == MatchType.ONES):
            stringFormattingDictionary['$MATCHTYPE$'] = (
                prefixDiv + matchDiv + "1v1" + postfixDivClose +
                postfixDivClose
            )

        if (self.matchType == MatchType.TWOS):
            stringFormattingDictionary['$MATCHTYPE$'] = (
                prefixDiv + matchDiv + "2v2" + postfixDivClose +
                postfixDivClose
            )

        if (self.matchType == MatchType.THREES):
            stringFormattingDictionary['$MATCHTYPE$'] = (
                prefixDiv + matchDiv + "3v3" + postfixDivClose +
                postfixDivClose
            )

        # if a computer it will have no stats
        if player.stats:
            stringFormattingDictionary['$COUNTRY$'] = (
                prefixDiv + countryDiv + str(player.stats.country) +
                postfixDivClose + postfixDivClose
            )

            stringFormattingDictionary['$TOTALWINS$'] = (
                prefixDiv + totalWinsDiv + str(player.stats.totalWins) +
                postfixDivClose + postfixDivClose
            )

            stringFormattingDictionary['$TOTALLOSSES$'] = (
                prefixDiv + totalLossesDiv + str(player.stats.totalLosses) +
                postfixDivClose + postfixDivClose
            )

            stringFormattingDictionary['$TOTALWLRATIO$'] = (
                prefixDiv + totalWinLossRatioDiv +
                str(player.stats.totalWLRatio) + postfixDivClose +
                postfixDivClose
            )

            stringFormattingDictionary['$STEAMPROFILE$'] = (
                prefixDiv + steamprofile +
                str(player.stats.steamProfileAddress) +
                postfixDivClose + postfixDivClose
            )

            stringFormattingDictionary['$COHSTATSLINK$'] = (
                prefixDiv + cohstatslink + str(player.stats.cohstatsLink) +
                postfixDivClose + postfixDivClose
            )

            # set default null values for all parameters in dictionary
            # these should not be used but are here for reference

            stringFormattingDictionary['$WINS$'] = (
                prefixDiv + winsDiv + "0" + postfixDivClose + postfixDivClose
            )

            stringFormattingDictionary['$LOSSES$'] = (
                prefixDiv + lossesDiv + "0" + postfixDivClose +
                postfixDivClose
            )

            stringFormattingDictionary['$DISPUTES$'] = (
                prefixDiv + disputesDiv + "0" + postfixDivClose +
                postfixDivClose
            )

            stringFormattingDictionary['$STREAK$'] = (
                prefixDiv + streakDiv + "0" + postfixDivClose +
                postfixDivClose
            )

            stringFormattingDictionary['$DROPS$'] = (
                prefixDiv + dropsDiv + "0" + postfixDivClose + postfixDivClose
            )

            stringFormattingDictionary['$RANK$'] = (
                prefixDiv + rankDiv + "" + postfixDivClose + postfixDivClose
            )

            stringFormattingDictionary['$LEVEL$'] = (
                prefixDiv + levelDiv + "0" + postfixDivClose + postfixDivClose
            )

            stringFormattingDictionary['$WLRATIO$'] = (
                prefixDiv + wlRatioDiv + "-" + postfixDivClose +
                postfixDivClose
            )

            for value in player.stats.leaderboardData:
                matchType = str(player.stats.leaderboardData[value].matchType)
                if matchType == str(self.matchType):
                    faction = str(player.stats.leaderboardData[value].faction)
                    if faction == str(player.faction):
                        stringFormattingDictionary['$WINS$'] = (
                            prefixDiv + winsDiv +
                            str(player.stats.leaderboardData[value].wins) +
                            postfixDivClose + postfixDivClose
                        )

                        stringFormattingDictionary['$LOSSES$'] = (
                            prefixDiv + lossesDiv +
                            str(player.stats.leaderboardData[value].losses) +
                            postfixDivClose + postfixDivClose
                        )

                        stringFormattingDictionary['$DISPUTES$'] = (
                            prefixDiv + disputesDiv +
                            str(player.stats.leaderboardData[value].disputes) +
                            postfixDivClose + postfixDivClose
                        )

                        stringFormattingDictionary['$STREAK$'] = (
                            prefixDiv + streakDiv +
                            str(player.stats.leaderboardData[value].streak) +
                            postfixDivClose + postfixDivClose
                        )

                        stringFormattingDictionary['$DROPS$'] = (
                            prefixDiv + dropsDiv +
                            str(player.stats.leaderboardData[value].drops) +
                            postfixDivClose + postfixDivClose
                        )

                        rank = player.stats.leaderboardData[value].rank
                        if rank == -1:
                            rank = ""
                        stringFormattingDictionary['$RANK$'] = (
                            prefixDiv + rankDiv +
                            str(rank) +
                            postfixDivClose + postfixDivClose
                        )

                        rl = player.stats.leaderboardData[value].rankLevel
                        if rl == -1:
                            rl = "0"
                        stringFormattingDictionary['$LEVEL$'] = (
                            prefixDiv + levelDiv + str(rl) + postfixDivClose +
                            postfixDivClose
                        )

                        wlr = player.stats.leaderboardData[value].winLossRatio
                        stringFormattingDictionary['$WLRATIO$'] = (
                            prefixDiv + wlRatioDiv + str(wlr) +
                            postfixDivClose + postfixDivClose
                        )

        return stringFormattingDictionary

    def populate_image_formatting_dictionary(self, player):
        imageOverlayFDict = (
            self.settings.imageOverlayFormattingDictionary)

        # faction icons
        if player.faction:
            fileExists = False
            factionIcon = ""
            if type(player.faction) is Faction:
                factionIcon = "OverlayImages\\Armies\\" + str(
                    player.faction.name).lower() + ".png"
                fileExists = os.path.isfile(factionIcon)
            logging.info(factionIcon)
            if fileExists:
                imageOverlayFDict['$FACTIONICON$'] = (
                    "<div class = 'factionflagimg'>" +
                    f"<img src='{factionIcon}' ></div>"
                )

                logging.info(
                    imageOverlayFDict.get('$FACTIONICON$'))
            else:
                imageOverlayFDict['$FACTIONICON$'] = (
                    '<div class = "factionflagimg">'
                    '<img src="data:," alt></div>'
                )

        # if a computer it will have no stats therefore no country flag or rank
        # set default values for flags and faction rank

        imageOverlayFDict['$FLAGICON$'] = (
            '<div class = "countryflagimg">'
            '<img src="data:," alt></div>'
        )

        defaultFlagIcon = "OverlayImages\\Flagssmall\\unknown_flag.png"
        fileExists = os.path.isfile(defaultFlagIcon)
        if fileExists:
            imageOverlayFDict['$FLAGICON$'] = (
                '<div class = "countryflagimg">'
                '<img src="{0}" ></div>'.format(defaultFlagIcon)
            )

        if player.stats:
            if player.stats.country:
                countryIcon = "OverlayImages\\Flagssmall\\" + str(
                    player.stats.country).lower() + ".png"
                fileExists = os.path.isfile(countryIcon)
                if fileExists:
                    imageOverlayFDict['$FLAGICON$'] = (
                        '<div class = "countryflagimg">'
                        '<img src="{0}" ></div>'.format(countryIcon)
                    )
                else:
                    imageOverlayFDict['$FLAGICON$'] = (
                        '<div class = "countryflagimg">'
                        '<img src="data:," alt></div>'
                    )

            # rank icons
            for value in player.stats.leaderboardData:
                matchType = str(player.stats.leaderboardData[value].matchType)
                if matchType == str(self.matchType):
                    faction = str(player.stats.leaderboardData[value].faction)
                    if faction == str(player.faction):
                        iconPrefix = ""
                        if str(player.faction) == str(Faction.PE):
                            iconPrefix = "panzer_"
                        if str(player.faction) == str(Faction.CW):
                            iconPrefix = "brit_"
                        if str(player.faction) == str(Faction.US):
                            iconPrefix = "us_"
                        if str(player.faction) == str(Faction.WM):
                            iconPrefix = "heer_"

                        level = str(
                            player.stats.leaderboardData[value].rankLevel
                        ).zfill(2)

                        levelIcon = (
                            "OverlayImages\\Ranks\\" + iconPrefix + level +
                            ".png"
                        )

                        logging.info("levelIcon : " + str(levelIcon))
                        fileExists = os.path.isfile(levelIcon)
                        if fileExists:

                            imageOverlayFDict['$LEVELICON$'] = (
                                '<div class = "rankimg">'
                                '<img src="{0}" ></div>'.format(levelIcon)
                            )
                        else:
                            imageOverlayFDict['$LEVELICON$'] = (
                                '<div class = "rankimg">'
                                '<img src="data:," alt></div>'
                            )

                            levelIcon = "OverlayImages\\Ranks\\no_rank_yet.png"
                            if os.path.isfile(levelIcon):
                                imageOverlayFDict['$LEVELICON$'] = (
                                    '<div class = "rankimg">'
                                    '<img src="{0}" ></div>'.format(levelIcon)
                                )

        return imageOverlayFDict

    def sanatize_user_name(self, userName):
        try:
            if userName:
                userName = str(userName)  # ensure type of string
                userName = userName.lstrip("!")
                # add 1 extra whitespace to username if
                # it starts with . or / using rjust to prevent .
                # and / twitch chat commands causing problems
                if (bool(re.match(r"""^[/\.]""", userName))):
                    userName = str(userName.rjust(len(userName)+1))
                # escape any single quotes
                userName = userName.replace("'", "\'")
                # escape any double quotes
                userName = userName.replace('"', '\"')
            return userName
        except Exception as e:
            logging.info("In sanitizeUserName username less than 2 chars")
            logging.exception("Exception : " + str(e))

    def format_preformatted_string(
        self,
        theString,
        sfDict,
        overlay=False
    ):

        if overlay:
            prefixDiv = '<div class = "nonVariableText">'
            postfixDiv = '</div>'

            # compile a pattern for all the keys
            pattern = re.compile(
                r'(' + '|'.join(
                    re.escape(key) for key in sfDict.keys()
                    ) + r')'
            )

            logging.info("pattern " + str(pattern))
            # split the string to include the dictionary keys
            fullSplit = re.split(pattern, theString)

            logging.info("fullSplit " + str(fullSplit))

            # Then replace the Non key values with the postfix and prefix
            for x in range(len(fullSplit)):
                if not fullSplit[x] in sfDict.keys():
                    fullSplit[x] = prefixDiv + fullSplit[x] + postfixDiv

            # This string can then be processed to replace
            # the keys with their appropriate values

            theString = "".join(fullSplit)

        # I'm dammed if I know how this regular expression works but it does.
        pattern = re.compile(
            r'(?<!\w)(' + '|'.join(
                re.escape(key) for key in sfDict.keys()
                ) + r')(?!\w)'
        )
        result = pattern.sub(
            lambda x: sfDict[x.group()], theString
        )
        return result

    def save_overlay_HTML(self, axisTeamList, alliesTeamList):
        try:
            team1 = ""
            team2 = ""
            team1List = []
            team2List = []

            team1List.clear()
            team2List.clear()

            # by default player team is allies unless
            # the player is steam number is present in the axisTeamList
            team1List = alliesTeamList
            team2List = axisTeamList

            for item in axisTeamList:
                if item.stats:
                    steamNumber = str(self.settings.data.get('steamNumber'))
                    if steamNumber == str(item.stats.steamNumber):
                        # logging.info ("Player team is AXIS")
                        team1List = axisTeamList
                        team2List = alliesTeamList

            uopf = self.settings.data.get('useOverlayPreFormat')
            useOverlayPreFormat = bool(uopf)
            if (useOverlayPreFormat):
                for item in team1List:
                    pf = self.settings.data.get('overlayStringPreFormatLeft')
                    preFormattedString = pf
                    # first substitute all the text in the preformat
                    sfDict = self.populate_string_formatting_dictionary(
                        item,
                        overlay=True
                    )
                    # second substitue all the html images if used
                    sfDict.update(
                        self.populate_image_formatting_dictionary(item))
                    theString = self.format_preformatted_string(
                        preFormattedString,
                        sfDict,
                        overlay=True
                    )
                    team1 += str(theString) + str("<BR>") + "\n"
                for item in team2List:
                    preFormattedString = self.settings.data.get(
                        'overlayStringPreFormatRight'
                    )
                    # first substitute all the text in the preformat
                    sfDict.clear()
                    sfDict = self.populate_string_formatting_dictionary(
                        item,
                        overlay=True
                    )

                    # second substitue all the html images if used
                    sfDict.update(
                        self.populate_image_formatting_dictionary(item)
                    )
                    theString = self.format_preformatted_string(
                        preFormattedString,
                        sfDict,
                        overlay=True
                    )
                    team2 += str(theString) + str("<BR>") + "\n"
            else:

                for item in team1List:
                    team1 += str(item.name) + str("<BR>") + "\n"
                for item in team2List:
                    team2 += str(item.name) + str("<BR>") + "\n"

            cssFilePath = self.settings.data.get('overlayStyleCSSFilePath')
            # check if css file exists
            # and if not output the default template to folder
            if not (os.path.isfile(cssFilePath)):
                with open(
                    cssFilePath,
                    'w',
                    encoding="utf-8"
                ) as outfile:
                    outfile.write(OverlayTemplates().overlaycss)

            htmlOutput = OverlayTemplates().overlayhtml.format(
                cssFilePath,
                team1,
                team2
            )
            # create output overlay from template
            with open(
                "overlay.html",
                'w',
                encoding="utf-8"
            ) as outfile:
                outfile.write(htmlOutput)
                # logging.info("Creating Overlay File\n")

        except Exception as e:
            logging.error(str(e))
            logging.exception("Exception : ")

    @staticmethod
    def clear_overlay_HTML():
        try:
            htmlOutput = OverlayTemplates().overlayhtml.format("", "", "")
            # create output overlay from template
            with open("overlay.html", 'w') as outfile:
                outfile.write(htmlOutput)
        except Exception as e:
            logging.error(str(e))
            logging.exception("Exception : ")

    def split_by_n(self, seq, n):
        '''A generator to divide a sequence into chunks of n units.'''
        while seq:
            yield seq[:n]
            seq = seq[n:]

    def find_between(self, s, first, last):
        """Returns the string between two delimiters."""

        try:
            start = s.index(first) + len(first)
            end = s.index(last, start)
            return s[start:end]
        except ValueError:
            return ""

    def __str__(self):
        output = "GameData : \n"
        output += f"Time Last Game Started : {str(self.gameStartedDate)}\n"
        output += f"player List : {str(self.playerList)}\n"
        output += f"numberOfPlayers : {str(self.numberOfPlayers)}\n"
        output += f"Number Of Computers : {str(self.numberOfComputers)}\n"
        output += f"Easy CPU : {str(self.easyCPUCount)}\n"
        output += f"Normal CPU : {str(self.normalCPUCount)}\n"
        output += f"Hard CPU : {str(self.hardCPUCount)}\n"
        output += f"Expert CPU : {str(self.expertCPUCount)}\n"
        output += f"Number Of Humans : {str(self.numberOfHumans)}\n"
        output += f"Match Type : {str(self.matchType.name)}\n"
        output += f"slots : {str(self.slots)}\n"
        output += f"mapName : {str(self.mapName)}\n"
        output += f"mapNameFull : {str(self.mapNameFull)}\n"
        output += f"mapDescription : {str(self.mapDescription)}\n"
        output += f"mapDescriptionFull : {str(self.mapDescriptionFull)}\n"
        output += f"randomStart : {str(self.randomStart)}\n"
        output += f"highResources : {str(self.highResources)}\n"
        output += f"VPCount : {str(self.VPCount)}\n"
        output += f"automatch : {str(self.automatch)}\n"
        output += f"modName : {str(self.modName)}\n"
        output += f"COH running : {str(self.cohRunning)}\n"
        output += f"Game In Progress : {str(self.gameInProgress)}\n"
        output += f"gameStartedDate : {str(self.gameStartedDate)}\n"
        output += f"cohProcessID : {str(self.cohProcessID)}\n"
        output += f"baseAddress : {str(self.baseAddress)}\n"
        output += (
            f"gameDescriptionString : "
            f"{str(self.gameDescriptionString)}\n"
        )

        return output

    def __repr__(self):
        return str(self)