import ida_segment
import ida_ua
import idaapi
import ida_idaapi
import ida_funcs
import ida_bytes
import ida_segregs
import ida_idp
import ida_name
import idautils
import ida_dbg
import ida_xref
import ida_kernwin
import idc
import os
import string
from unicorn import *
from unicorn.arm_const import *
from unicorn.mips_const import *
from unicorn.arm64_const import *
import struct
import hexdump
import math

from EWS.utils.utils import *
from EWS.utils.configuration import Configuration, StubConfiguration
from EWS.emu.emubase import Emulator
from EWS.utils.exec_trace import *
from EWS.utils.configuration import AdditionnalMapping

from EWS.ui.debug_view import *

MAX_INSN_SIZE=8
MAX_EXEC= 0x10000 # max number of executed insn
FOLLOW_PC= False  #TODO move it to the configuration option

class Emucorn(Emulator):

    def __init__(self,conf):

        super().__init__(conf)

        self.nb_insn=0
        self.running_stubs = dict()

        self.exec_trace = Exec_Trace(self.conf.arch,content=None)
        self.exec_trace.content = {}

        self.call_tree = Call_Tree()
        self.call_tree.content = {}
        self.call_tree_depth = 0

        self.debug_view = None # todo create the reference in EWS_Plugin object

        self.filetype = ida_loader.get_file_type_name()

        self.assembler = None

        # Debugger Variables
        self.stop = False
        self.ignore_end_addr = False

        self.amap = dict()
        self.patches = dict()

        self.watchpoints = dict()

        self.tags = dict()

#---------------------------------------------------------------------------------------------
#
#   EMULATOR Mapping functions
#
#---------------------------------------------------------------------------------------------
    @staticmethod
    def do_required_mappng(uc: Uc,
                           s_ea: int,
                           e_ea:int,
                           p_size:int,
                           perms:int,
                           warn:bool=False,
                           warn_msg:str=''):

        """ !Use this function to avoid page mappings
                that already exist.
                Usefull when mapping user provided ranges,
                or for small sections that will share pages.

            @param uc Unicorn Engine
            @param s_ea Effective Start Address
            @param e_ea Effective Stop Address
            @param p_size Page Size
            @param perms Permissions on the Page
            @param warn Alert in case of already existing mapping
            @param warm_msg Message to display in console

        """

        b_page = s_ea & ~(p_size -1)

        finally_mem_map = False

        while b_page < e_ea:

            alrdy_map = False

            for rsta,rsto,rpriv in uc.mem_regions():

                if b_page == rsta:

                    alrdy_map = True
                    break

            if not alrdy_map:

                logger.console(LogType.INFO,f'Map page {b_page:x}')
                nb_pages, r = divmod(e_ea-b_page,p_size)
                if r: nb_pages+=1
                uc.mem_map(b_page,nb_pages*p_size,perms)
                finally_mem_map = True

            b_page += rsto +1

            if not finally_mem_map and warn:
                logger.console(LogType.WARN,warn_msg)

    def flush(self):

        """
        !flush the trace list instructions. Somehow there is a cache issue/leak.
        this function is executed from the UI when plugin is reset.

        """

        for i in range(len(self.exec_trace.content.keys())-1):
            try:
                self.exec_trace.addr.pop()
            except:
                pass

    def add_mapping(self,
                    addr:int,
                    mem:bytes,
                    perms:int=UC_PROT_ALL) -> int :
        """
            !Create and Write into memory mapping.

            @params addr: base address
            @param  mem: content of the mapping
            @params perms : permissions

            @return 0 for Success -1 for Fail
        """

        for rsta,rsto,rpriv in self.uc.mem_regions():

            if addr in range(rsta,rsto):

                logger.console(LogType.WARN,f'Address 0x{addr:x} is already mapped.')
                return -1

        Emucorn.do_required_mappng(self.uc,addr,addr+len(mem),self.conf.p_size,perms)

        self.uc.mem_write(addr,mem)

        logger.console(LogType.INFO,f'Created new mapping starting at 0x{addr:x}')

        self.amap[addr] = mem

        return 0

    @staticmethod
    def do_mapping(uc: Uc,
                   conf:Configuration):

        """
            !Do required mapping according the
            configuration object.

            @param uc Unicorn Engine reference
            @param conf Configuration Object Reference

        """

        inf = ida_idaapi.get_inf_structure()
        last_vb = None

        # Maps program segment
        if conf.map_with_segs:

            for seg in conf.segms:

                vbase = Emucorn.do_required_mappng(uc,seg.start_ea, seg.end_ea, conf.p_size, UC_PROT_ALL if not conf.use_seg_perms else seg.perm)
                uc.mem_write(seg.start_ea,ida_bytes.get_bytes(seg.start_ea,seg.end_ea-seg.start_ea))
                logger.logfile(LogType.INFO,f'Map seg {ida_segment.get_segm_name(seg)}')

        else:

            for seg in get_seg_list():

                vbase=seg.start_ea&~(conf.p_size-1)

                if last_vb == None:

                    nb_pages, r = divmod(seg.end_ea- vbase,conf.p_size)
                    if nb_pages == 0:
                        nb_pages = 1
                    elif nb_pages > 0 and r > 0:
                        nb_pages +=1

                    try:

                        uc.mem_map(vbase,nb_pages*conf.p_size)
                    except Exception as e:

                        logger.console(LogType.ERRR,f"Map at {vbase:x}  for {nb_pages} returns : {str(e)}")

                    last_vb = vbase + (nb_pages)*conf.p_size

                else:

                    if seg.end_ea > (last_vb):

                        if vbase <= last_vb:
                            vbase = last_vb

                        nb_pages, r = divmod(seg.end_ea- vbase,conf.p_size)
                        if nb_pages == 0:
                            nb_pages = 1
                        elif nb_pages > 0 and r > 0:
                            nb_pages +=1

                        try:
                            uc.mem_map(vbase,nb_pages*conf.p_size)
                        except Exception as e:
                            logger.console(LogType.ERRR,"mapping at %x  for %d returns : %s"%(vbase,nb_pages,str(e)))

                        last_vb = vbase + (nb_pages)*conf.p_size

                logger.console(LogType.INFO,'Mapped segment %s [%x:%x]'%(ida_segment.get_segm_name(seg),
                                                                         seg.start_ea,
                                                                         seg.end_ea))

                # If segment is tagged as unitialized, skip it
                if ida_segment.segtype(seg.start_ea) == ida_segment.SEG_BSS:
                    continue

                try:

                    size = seg.size()

                    # get_bytes API does not allow to get more than 0xffffffff bytes.
                    # nonetheless such mapping should be discarded ?

                    if seg.size() > (math.pow(2,32) -1):
                        logger.console(LogType.WARN,f"Segment size is too big: {size:x}, restraining to 4GB")
                        size =  int(math.pow(2,32) - 1) & 0xffffffff

                    uc.mem_write(seg.start_ea,ida_bytes.get_bytes(seg.start_ea,size))

                except Exception as e:

                    logger.console(LogType.ERRR,"writing segment %x to %x content returns : %s"%(seg.start_ea,seg.end_ea,str(e)))

        #Map user provided areas
        for m_ea,content in conf.amap_conf.mappings.items():

            idaapi.show_wait_box("Mapping user content")

            Emucorn.do_required_mappng(uc,m_ea,m_ea+len(content),conf.p_size,UC_PROT_ALL)
            uc.mem_write(m_ea,content)

            logger.console(LogType.INFO,'[%s] Additionnal mapping for data at %8X'%('Emucorn',m_ea))

            idaapi.hide_wait_box()

        idaapi.show_wait_box("Map stack")
        stk_p,r = divmod(conf.stk_size,conf.p_size)

        if r: stk_p+=1

        #uc.mem_map(conf.stk_ba,stk_p*conf.p_size)
        Emucorn.do_required_mappng(uc,
                                   conf.stk_ba,
                                   conf.stk_ba+stk_p*conf.p_size,
                                   conf.p_size,
                                   UC_PROT_READ | UC_PROT_WRITE,
                                   True,
                                   f"Warning map stack in already mapped area {conf.stk_ba}, edit consts file for corresponding architecture")

        logger.console(LogType.INFO,' [%s] mapped stack at 0x%.8X '%('Emucorn',conf.stk_ba))

        idaapi.hide_wait_box()

        logger.console(LogType.INFO, "All IDB segment has been successfully mapped in emulator")

#---------------------------------------------------------------------------------------------
#
#Emulator reg/mem accesses
#
#---------------------------------------------------------------------------------------------

    def mem_read(self,
                 addr:int,
                 size:int) -> bytes:

        """
        !mem_read

        @param addr Address to read from
        @param size Size to read

        @return bytes with memory content

        """

        return self.uc.mem_read(addr,size)

    def mem_write(self,
                  addr:int,
                  data:bytes) -> None:

        """
        !mem_write

        @param addr Address to write to
        @param size Size of the data to write

        @return bytes with memory content
        """

        self.uc.mem_write(addr,data)

    def reg_read(self,
                 r_id) -> int:

        """
        !reg_read

        @param r_id (string/int) register identifier

        @return register value

        """

        return self.uc.reg_read(r_id)

    def reg_write(self,
                  r_id,
                  value:int) -> None:

        """

        !reg_write

        @param r_id (string/int) register identifier
        @param value register value

        """

        self.uc.reg_write(r_id,value)

#---------------------------------------------------------------------------------------------
#
#   Hooking events functions
#
#---------------------------------------------------------------------------------------------
    @staticmethod
    def unmp_read(uc:Uc,
                  access: int,
                  addr: int,
                  value:int,
                  size:int,
                  user_data) -> None:
        """
        ! unmp_read Unicorn Hook Declaration for unmapped address.

        @param Unicorn Egine
        @param access Access Fashion
        @param addr address which raised the Fault
        @param value ignore_end_addr
        @param size size of the read operation
        @param user_data Reference to the object

        """

        _self = user_data

        logger.console(LogType.WARN,'[!] Read Access Exception',
                       'Cannot read 0x%.8X'%addr,
                       'for size %d (reason: unmapped page)'%size)
        logger.console(LogType.WARN,'[!] Fault instruction at %x'%_self.helper.get_pc())

        conf = _self.conf

        if conf.autoMap:

            base_addr = addr & ~(conf.p_size-1)

            uc.mem_map(base_addr,conf.p_size)
            uc.mem_write(base_addr,b'\xff'*conf.p_size)

            logger.console(LogType.INFO,'[*] Automap: added page 0x%.8X'%base_addr)

            return True

        logger.console(LogType.ERRR,'Automap is not enabled. Aborting()')
        return False

    @staticmethod
    def unmp_write(uc:Uc,
                   access:int,
                   addr:int,
                   size:int,
                   value:int,
                   user_data):

        """
        ! unmp_write Unicorn Hook Declaration for unmapped address.

        @param Unicorn Egine
        @param access Access Fashion
        @param addr address which raised the Fault
        @param value ignore_end_addr
        @param size size of the write operation
        @param user_data Reference to the object

        """

        _self = user_data

        logger.console(LogType.WARN,
                       '[!] Write Access Excpetion:'
                       'cannot write value 0x%.8X at address 0x%.8X'%(value,addr),
                       '(reason: unmapped page)')

        logger.console(LogType.WARN,'[!] Fault instruction at %x'%_self.helper.get_pc())

        conf = _self.conf

        if conf.autoMap:

            base_addr = addr & ~(conf.p_size-1)

            try:
                uc.mem_map(base_addr,conf.p_size)
            except UcError:
                logger.console(LogType.WARN,'[*] Automap not supported for this arch')
                return False

            logger.console(LogType.INFO,'[*] Automap: added page 0x%.8X'%base_addr)

            return True

        logger.console(LogType.ERRR,'Automap is not enabled. Aborting()')

        return False

    @staticmethod
    def hk_read(uc:Uc,
                access:int,
                addr:int,
                size:int,
                value:int,
                user_data):
        """
        ! hk_read Unicorn Hook Declaration for memory access.

        @param Unicorn Egine
        @param access Access Fashion
        @param addr address which is accessed
        @param value ignore_end_addor
        @param size size of the read operation
        @param user_data Reference to the object

        """

        _self= user_data

        out= ': [read]  '
        out +='with size: %d '%size
        out +=hexdump.hexdump(uc.mem_read(addr,size),result='return').replace('00000000','')

        _self.exec_trace.add_instruction(addr=addr,
                                        assembly=out,
                                        regs=_self.get_regs(),
                                        color=get_insn_color(addr),
                                        tainted=False)

        logger.logfile(LogType.INFO,out)

    @staticmethod
    def hk_write(uc:Uc,
                 access:int,
                 addr:int,
                 size:int,
                 value:int,
                 user_data):
        """
        ! hk_read Unicorn Hook Declaration for memory access.

        @param Unicorn Egine
        @param access Access Fashion
        @param addr address which is accessed
        @param value ignore_end_addor
        @param size size of the read operation
        @param user_data Reference to the object

        """

        _self = user_data

        out = ': [write] '
        out += f'with size {size} '
        out += hexdump.hexdump(int_to_bytes(value,size),
                               result='return').replace('00000000','')

        _self.exec_trace.add_instruction(addr=addr,
                                        assembly=out,
                                        regs=_self.get_regs(),
                                        color=get_insn_color(addr),
                                        tainted=False)

        logger.logfile(LogType.INFO,out)

    def hook_code(self,uc:Uc,
                  addr:int,
                  size:int,
                  user_data):

        """
        ! hook_code Unicorn Hook Declaration for memory access.

        @param uc Unicorn Egine
        @param addr address which is accessed
        @param size size of the read operation
        @param user_data Reference to the object

        """
        asm = ''

        if self.stop:

            uc.emu_stop()

            logger.console(LogType.INFO,f'Breakpoint at 0x{addr:x} reached.',
            f'{self.nb_insn} instruction(s) executed.')

            self.nb_insn = 0

            return False

        elif addr in self.running_stubs.keys():

            try:

                self.running_stubs[addr].do_it()

                s_name = self.running_stubs[addr].name

                asm = log = f'0x{addr}:\t{s_name}'

            except Exception as e:

                logger.console(LogType.WARN,'Error in stub, aborting')

                uc.emu_stop()

                logger.console(LogType.ERRR,'backtrace:\n',e.__str__())

                return False

        elif addr in self.user_breakpoints or ida_dbg.exist_bpt(addr):

            self.stop = True

        self.color_map[addr] = get_insn_color(addr)

        if asm == '':

                asm = get_captsone_repr(self,addr)
                log = build_insn_repr(self,addr)

        trace_id = self.exec_trace.add_instruction(addr=addr,
                                            assembly=asm,
                                            regs=self.get_regs(),
                                            color=get_insn_color(addr),
                                            tainted=False)

        """
        TODO:

        if is_call_insn(addr) or is_ret_addr(addr):
            if is_call_insn(addr):
                self.calltree_depth +=1
            else:
                self.call_tree_depth -=1

            self.calltree[self.nb_insn] = {}
            self.calltree[self.nb_insn]['addr'] = addr

        """

        logger.logfile(LogType.INFO,log)

        self.last_pc = self.helper.get_pc()

        if FOLLOW_PC:

            ida_kernwin.jumpto(self.last_pc)

        if self.nb_insn >= self.conf.max_insn:

            logger.console(LogType.WARN,
                          f"Execution reach the max number of insn {self.conf.max_insn}")

            self.stop = True

            self.nb_insn = 0

            return True

        self.nb_insn+=1
        return True

#---------------------------------------------------------------------------------------------
#  STUB MECHANISM
#---------------------------------------------------------------------------------------------

    def stub_ELF_sym(self,
                     ea: int,
                     stub_func,
                     name:str=''):

        """
            !stub_elf_sym stub the function given a effective address.

            @param ea Effective Address in the .plt
            @param stub Stub Object
            @param name Symbol of the function
        """

        self.stub_func_addr(ea,
                            stub_func,
                            stub_type=StubType.BUILTIN,
                            name=name)

        logger.logfile(LogType.INFO,f"{name} is now stubbed at {ea:x}")

    def stub_PE_sym(self,
                    ea:int,
                    stub_func,
                    name: str = ''):

        """
            !stub_elf_sym stub the function given a effective address.

            @param ea Effective Address in the .plt
            @param stub Stub Object
            @param name Symbol of the function
        """

        stub =    self.get_new_stub(stub_func,
                                    stub_type=StubType.BUILTIN,
                                    name=name)
        self.running_stubs[ea] = stub
        self.nop_insn(ea)
        self.stubbed_bytes[ea] = get_insn_bytecode(ea)

        logger.logfile(LogType.INFO,f"{name} is now stubbed at {ea:x}")

    def stub_sym(self,
                 ea: int,
                 stub_func,
                 name: str = ''):

        """
            !stub the right way according the architecture.

            @param ea Effective Address in the .plt
            @param stub Stub Object
            @param name Symbol of the function
        """

        if hasattr(self,'loader_type'):
            if self.loader_type == LoaderType.PE:
                self.stub_PE_sym(ea,stub_func,name)
                return

        # default behavior is based on ELF format.
        self.stub_ELF_sym(ea,
                          stub_func,
                          name)

    def stub_func_addr(self,
                       ea:int,
                       stub_func,
                       stub_type:StubType,
                       name:str=''):

        """
            stub a function by it address.
            a return instruction will replace
            the first instruction of the targeted
            function.
            It creates a new stub object recorded
            in running_stubs list.

            @param ea Effective Address in the .plt
            @param stub Stub Object
            @param stub_type Stub Type
            @param name Symbol of the function
        """

        if not ida_funcs.get_func(ea):
            logger.logfile(LogType.WARN,"%s %x not a function"%\
                           (sys._getframe().f_code.co_name,
                            ea))
            return

        if stub_type == StubType.NULL:
            ea = ida_funcs.get_func(ea).start_ea

        stub =    self.get_new_stub(stub_func,
                                    stub_type=stub_type,
                                    name=name)
        self.running_stubs[ea] = stub
        self.uc.mem_write(ea,self.get_retn_insn(ea))

        self.stubbed_bytes[ea] = get_insn_bytecode(ea)

    def unstub_func_addr(self,
                         ea: int):

        """
            !unstub_func_addr sunstubbing consists in :
                conf cleaning
                removing stub object from stub list
                repatching memory with original bytecode

            @param ea

        """

        if ea in self.running_stubs.keys():

            """
            if self.running_stubs[ea].stub_type == StubType.TAG:
                self.conf.remove_tag(ea)
            elif self.running_stubs[ea].stub_type == StubType.NULL:
                self.conf.remove_null_stub(ea)
            """

            del self.running_stubs[ea]

            self.helper.mem_write(ea,self.stubbed_bytes[ea])

            del self.stubbed_bytes[ea]

    def unstub_all(self):

        """
            !unstub_all remove all stubs for symbols and user defined stubs

        """

        for ea, v in self.running_stubs.items():
            if clean_configuration:
                if v.stub_type == StubType.TAG:
                    self.conf.remove_tag(ea)
                elif v.stub_type == StubType.NULL:
                    self.conf.remove_null_stub(ea)

            del self.running_stubs[ea]
            del self.stubbed_bytes[ea]

    def stub_PLT(self):

        """
            stubs PLT
        """

        for k,v in self.reloc_map.items():

            # TODO change by ida_xref.get_first_dref_to(v) ?
            xref_g = idautils.XrefsTo(v)

            try:

             while True:

                xref = next(xref_g)

                if k in self.stubs.keys():

                    self.stubs[k].set_helper(self.helper)
                    self.stub_ELF_sym(xref.frm,self.stubs[k].do_it,k)

                elif k == '__libc_start_main':

                        logger.logfile(LogType.INFO,'libc_start_main stubbed!')
                        self.uc.mem_write(v,
                                        int.to_bytes(self.libc_start_main_trampoline,
                                        8 if idc.__EA64__ else 4,
                                     'little'))

                else:

                     if self.conf.s_conf.auto_null_stub:

                            self.add_null_stub(xref.frm)

            except StopIteration:

                pass

        for s_ea in self.conf.s_conf.nstubs.keys():
                self.add_null_stub(s_ea)

        for k,v in self.conf.s_conf.tags.items():
            self.tag_func(k,v)

    def stub_PE(self):

        """
            stubs PE
        """

        for k,v in self.reloc_map.items():

            # TODO change by ida_xref.get_first_dref_to(v) ?
            xref_g = idautils.XrefsTo(v)

            try:

             while True:

                xref = next(xref_g)

                if k in self.stubs.keys():

                    self.stubs[k].set_helper(self.helper)
                    self.stub_PE_sym(xref.frm,self.stubs[k].do_it,k)

                else:

                     if self.conf.s_conf.auto_null_stub:
                         self.nop_insn(xref.frm)

            except StopIteration:

                pass

        for s_ea in self.conf.s_conf.nstubs.keys():
                self.add_null_stub(s_ea)

        for k,v in self.conf.s_conf.tags.items():
            self.tag_func(k,v)

    def add_null_stub(self,
                      ea:int):

        """
            Null stubs allow to directly bypass
            a function call.

            @param Function Address to Stub with a direct return sequence
        """

        if ea in self.running_stubs.keys():
            self.unstub_func_addr(ea)

        self.stub_func_addr(ea,
                            self.nstub_obj.do_it,
                            stub_type=StubType.NULL)

    def remove_null_stub(self,
                         ea:int):

        """
        !remove_null_stub remove a null stub given its address.

        @param ea Address where the stub was registred.
        """

        self.unstub_func_addr(ea)

    def add_custom_stub(self,
                        ea: int,
                        func,
                        name:str='user stub'):

        """
            !add_custom_stub Custom stub allows the user to define a function
            that will be called instead a stubbed address / symbols.

            @param Address of the function
            @param function function declaration (in python)
            @param name associated Name

        """

        stub = self.get_new_stub(func,StubType.USER,name=name)
        if ea in self.running_stubs.keys():
            logger.console(LogType.WARN,"Function at %x is already stubbed."
                           "Overwritting stub with new tag"%ea)
            self.unstub_func_addr(ea)

        self.stub_func_addr(ea,stub.do_it,stub_type=StubType.USER)
        logger.console(LogType.INFO,'[+] %s is now stubbed'%get_func_name(ea))

    def remove_custom_stub(self,
                           ea:int):

        """
        !remove_custom_stub remove a user provided stub given its address

        @param ea Effectiva Address where the stub was registred.
        """

        self.unstub_func_addr(ea)

    def tag_func(self,
                 ea: int,
                 stub_name: str):

        """
            !tag_func Function tagging consists in associating
            a function to a stub. If you find a memcpy like
            function, you can tag it with the internal memcpy
            stub. You will then be able to track its argument.

            @param ea Effective Address of the target function
            @param stub_name User provided name

        """

        if not stub_name in self.stubs.keys():
            logger.console(LogType.WARN,'[!] %s is not among available stubs. '%stub_name,
                           'Please refers to list_stubs command to get the list of available stubs')
            return

        if ea in self.running_stubs.keys():
            logger.console(LogType.WARN,f"Function at {ea:x} is already stubbed. ",
                               f'Overwritting stub with new tag {stub_name}')
            self.unstub_func_addr(ea)

        else:
                self.stubs[stub_name].set_helper(self.helper)

        self.stub_func_addr(ea,self.stubs[stub_name].do_it,stub_type=StubType.TAG,name=stub_name)

        logger.console(LogType.INFO,'[+] %x is now stubbed with %s function'%(ea,stub_name))

    def remove_tag(self,
                   ea:int):

        """
        !remove_tag remove a tag association given its Effective Address

        @param ea Address

        """

        self.unstub_func_addr(ea)

#---------------------------------------------------------------------------------------------
#
# DEBUGGING FUNCTION
#
#---------------------------------------------------------------------------------------------

    def start(self,
              cnt: int = 0,
              saddr: int = -1):
        """
            !This function launch unicorn **start** function.
            It will run a limited instructions from the start address.

            @param cnt Maximum instruction to be executed.
            @param saddr Address of the first instruction to be executed.
        """

        self.nb_insn = 0

        if saddr == -1:
            saddr = self.conf.exec_saddr

        if self.isThumb():

            saddr |= 1

        if self.ignore_end_addr:

            end_addr = 0xffffffffffffffff

        else:

            end_addr = self.conf.exec_eaddr

        try:

            idaapi.show_wait_box("Running...")

            self.uc.emu_start(saddr,end_addr,timeout=0,count=cnt)
            self.is_running = True

        except UcError as e:

            logger.console(LogType.ERRR,'Error in unicorn engine')
            raise e

        except Exception as e:

            logger.console(LogType.WARN,
                          '[!] Exception in program : %s' % e.__str__())
        finally:

            idaapi.hide_wait_box()

        # For testing purpose
        """
        # DUMP HERE

        with open("/tmp/exec_trace_serial.txt",'w+') as fout:
            json.dump(self.exec_trace,fp=fout,cls=Exec_Trace_Serializer)
        """

    def step_n(self,
               n:int) -> None:

        """
        !step_n

        @param n Number of instructions to be executed.

        """

        if self.stop:

            insn = get_insn_at(self.helper.get_pc())
            pc = self.helper.get_pc() + insn.size
            self.stop = False

        else:

            pc = self.helper.get_pc()

        pc = self.helper.get_pc()

        if pc >= self.conf.exec_eaddr and not self.ignore_end_addr:

            uret = ida_kernwin.ask_yn(True,
            f"Execution reached the limit address specified in configuration {self.conf.exec_eaddr:x}"\
                    ", do you want to continue?")
            if uret == ida_kernwin.ASKBTN_YES:
                self.ignore_end_addr = True

        self.start(cnt=n,saddr=pc)

    def step_in(self):

        """
        !step_in executes a single instruction

        """

        self.step_n(1)

    def continuee(self):

        """
        !continue

        """

        self.step_n(0)

    def step_over(self):

        """
        !step_over Try to detect the target using IDA API.
        Use IDA breakpoint to notify the user that breakpoint are added (there is no wizard)
        It will then have to remove it if he/she restarts the program.
        It handles call, direct jump, and jump tables

        """

        insn = get_insn_at(self.helper.get_pc())
        bp_addr = []

        if ida_idp.is_call_insn(insn) or ida_idp.has_insn_feature(insn.itype,ida_idp.CF_STOP):
            self.uc.emu_start(insn.ea,insn.ea+insn.size,0,0)
        elif ida_idp.is_indirect_jump_insn(insn):
            logger.console(LogType.WARN,
                           "Indirect jump incompatible with step_over feature.",
                           "Please do it manually")
        else:
            self.step_in()

    def add_watchpoint(self,base_addr, rang, mode=0x3):

        """
        !add_watchpoint Enable a watchpoint to track data during execution.

        @param base_addr address of the first byte of the data to be tracked.
        @param rang size of the data to be tracked.
        @param mode READ, WRITE or BOTH

        """

        def hk_read_wp(uc,access,addr,size,value,user_data):
            if addr >= base_addr and addr < base_addr + rang:
                logger.console(LogType.INFO,"[Watchpoint] read access for addr "
                               "%x reached at pc %x"% (addr,self.helper.get_pc()))

        def hk_write_wp(uc,access,addr,size,value,user_data):

            if addr >= base_addr and addr < base_addr + rang:
                logger.console(LogType.INFO,"[Watchpoint] write access for addr "
                               "%x reached at pc %x"% (addr,self.helper.get_pc()))

        if mode & 0x1:

            self.uc.hook_add(UC_HOOK_MEM_READ,
                       hk_read_wp,
                       self)
            logger.console(LogType.INFO,"Add read watchpoint "
                           "for [%x: %x]"%(base_addr,(base_addr+rang)))
        if (mode >> 1) & 0x1:
            self.uc.hook_add(UC_HOOK_MEM_WRITE,
                       hk_write_wp,
                       self)

            logger.console(LogType.INFO,"Add write watchpoint "
                           "for [%x: %x]"%(base_addr,(base_addr+rang)))

        self.watchpoints[base_addr]= mode << 24 | rang

    def reset_color_graph(self):
        pass

    def patch_insn(self,
                   addr:int,
                   asm:bytes) -> None:

        """
        !patch_insn Patch instruction(s) with the bytecode

        @param addr Effective Address to apply the patch from
        @param asm  compiled assebmly.

        """

        bytecode = self.assembler.assemble(asm,addr)
        self.uc.mem_write(addr,bytecode)
        logger.console(LogType.INFO,f"Instruction(s) at {addr:x} patched")
        self.patches[addr] = bytecode

        # This handles use case where new bytecode is bigger than
        # the original one. Stop address might not be detected
        # causing execution drift.
        if addr >= self.conf.exec_saddr:
            if addr + len(bytecode) > self.conf.exec_eaddr:
                self.conf.exec_eaddr = addr + len(bytecode)

    def isThumb(self) -> bool:

        """
        ! necessary for arm32 architecture.
        """

        return False

    def extract_current_configuration(self) -> Configuration:

        cur_nstubs = dict()
        cur_tagstub = dict()

        for ea,s in self.running_stubs.items():
            if s.stub_type == StubType.TAG:
                cur_tagstub[ea] = s.name
            elif s.stub_type == StubType.NULL:
                cur_nstubs[ea] = s

        return Configuration(
                 path=self.conf.path,
                 arch=self.conf.arch,
                 emulator=self.conf.emulator,
                 p_size=self.conf.p_size,
                 stk_ba=self.conf.stk_ba,
                 stk_size=self.conf.stk_size,
                 autoMap=self.conf.autoMap,
                 showRegisters=self.conf.showRegisters,
                 exec_saddr=self.conf.exec_saddr,
                 exec_eaddr=self.conf.exec_eaddr,
                 mapping_saddr=self.conf.mapping_saddr,
                 mapping_eaddr=self.conf.mapping_eaddr,
                 segms=self.conf.segms,
                 map_with_segs=self.conf.map_with_segs,
                 use_seg_perms=self.conf.use_seg_perms,
                 useCapstone=self.conf.useCapstone,
                 registers=self.get_regs(),
                 showMemAccess=self.conf.showMemAccess,
                 s_conf=StubConfiguration(nstubs={**self.conf.s_conf.nstubs, **cur_nstubs},
                                            tag_func_tab = self.conf.s_conf.tag_func_tab,
                                            activate_stub_mechanism=self.conf.s_conf.activate_stub_mechanism,
                                            orig_filepath=self.conf.s_conf.orig_filepath,
                                            custom_stubs_file=self.conf.s_conf.custom_stubs_file,
                                            auto_null_stub=self.conf.s_conf.auto_null_stub,
                                            tags={**self.conf.s_conf.tags,**cur_tagstub}),
                 amap_conf=AdditionnalMapping({**self.conf.amap_conf.mappings ,**self.amap}),
                 memory_init=self.conf.memory_init,
                 color_graph=self.conf.color_graph,
                 breakpoints=self.user_breakpoints,
                 watchpoints=self.watchpoints, #TODO
                 patches= {**self.patches, ** self.conf.patches}, #TODO
                 max_insn=0x10000)


#!/usr/bin/env python

# This is an example file to show how to make use of pyrow
# Have the rowing machine on and plugged into the computer before starting the program.

from flask import Flask, jsonify
from pyrow import pyrow
from flask_cors import CORS
app = Flask(__name__)
CORS(app)

cur_row_1 = 0
prev_row_1 = 0
total_row_1 = 0
total_row_before_rst_1 = 0
row_id_1 = '430391865'

cur_row_2 = 0
prev_row_2 = 0
total_row_2 = 0
total_row_before_rst_2 = 0
row_id_2 = '430331314'

cur_row_3 = 0
prev_row_3 = 0
total_row_3 = 0
total_row_before_rst_3 = 0
row_id_3 = 3

cur_row_4= 0
prev_row_4 = 0
total_row_4 = 0
total_row_before_rst_4 = 0
row_id_4 = 4

cur_row_5 = 0
prev_row_5 = 0
total_row_5 = 0
total_row_before_rst_5 = 0
row_id_5 = 5

rowers_list = []

@app.route('/')
@app.route('/rowers')
def index():

    global cur_row_5, cur_row_4, cur_row_3, cur_row_2, cur_row_1
    global prev_row_1, prev_row_2, prev_row_3, prev_row_4, prev_row_5
    global row_id_5, row_id_4, row_id_3, row_id_2, row_id_1
    global total_row_1, total_row_2, total_row_3, total_row_4, total_row_5
    global total_row_before_rst_1, total_row_before_rst_2, total_row_before_rst_3, total_row_before_rst_4, total_row_before_rst_5
    global rowers_list

    for pm in rowers_list:
        print("in distance calc loop...")
        monitor = pm.get_monitor()
        pm_info = pm.get_erg()
        if pm_info['serial'] == row_id_1:
            print("in row 1")
            cur_row_1 = monitor['distance']
            if cur_row_1 < prev_row_1:
                print("---Rower1 was reset---")
                total_row_before_rst_1 = total_row_1
                total_row_1 = total_row_before_rst_1 + cur_row_1
            elif cur_row_1 == prev_row_1 and cur_row_1 < total_row_1:
                print("--rower1 continue after reset---")
                total_row_1 = total_row_before_rst_1 + cur_row_1
            else:
                print("---rower1 normal operation---")
                total_row_before_rst_1 = cur_row_1
                total_row_1 = cur_row_1
            prev_row_1 = cur_row_1

        elif pm_info['serial'] == row_id_2:
            print("in row 2")
            cur_row_2 = monitor['distance']
            if cur_row_2 < prev_row_2:
                print("---Rower2 was reset---")
                total_row_before_rst_2 = total_row_2
                total_row_2 = total_row_before_rst_2 + cur_row_2
            elif cur_row_2 == prev_row_2 and cur_row_2 < total_row_2:
                print("--rower2 continue after reset---")
                total_row_2 = total_row_before_rst_2 + cur_row_2
            else:
                print("---rower2 normal operation---")
                total_row_before_rst_2 = cur_row_2
                total_row_2 = cur_row_2
            prev_row_2 = cur_row_2

        elif pm_info['serial'] == row_id_3:
            cur_row_3 = monitor['distance']
            if cur_row_3 < prev_row_3:
                print("---Rower3 was reset---")
                total_row_before_rst_3 = total_row_3
                total_row_3 = total_row_before_rst_3 + cur_row_3
            elif cur_row_3 == prev_row_1 and cur_row_3 < total_row_3:
                print("--rower3 continue after reset---")
                total_row_3 = total_row_before_rst_3 + cur_row_3
            else:
                print("---rower3 normal operation---")
                total_row_before_rst_3 = cur_row_3
                total_row_3 = cur_row_3
            prev_row_3 = cur_row_3

        elif pm_info['serial'] == row_id_4:
            cur_row_4 = monitor['distance']
            if cur_row_4 < prev_row_4:
                print("---Rower4 was reset---")
                total_row_before_rst_4 = total_row_4
                total_row_4 = total_row_before_rst_4 + cur_row_4
            elif cur_row_4 == prev_row_4 and cur_row_4 < total_row_4:
                print("--rower4 continue after reset---")
                total_row_4 = total_row_before_rst_4 + cur_row_4
            else:
                print("---rower4 normal operation---")
                total_row_before_rst_4 = cur_row_4
                total_row_4 = cur_row_4
            prev_row_4 = cur_row_4

        elif pm_info['serial'] == row_id_5:
            cur_row_5 = monitor['distance']
            if cur_row_5 < prev_row_5:
                print("---Rower5 was reset---")
                total_row_before_rst_5 = total_row_5
                total_row_5 = total_row_before_rst_5 + cur_row_5
            elif cur_row_5 == prev_row_5 and cur_row_5 < total_row_5:
                print("--rower5 continue after reset---")
                total_row_5 = total_row_before_rst_5 + cur_row_5
            else:
                print("---rower5 normal operation---")
                total_row_before_rst_5 = cur_row_5
                total_row_5 = cur_row_5
            prev_row_5 = cur_row_5

    # Open and prepare file
    write_file = open('workout.json', 'w')
    write_file.write('[\n')
    # Write data to write_file
    workoutdata = str('{ "rower": ' + str(row_id_1) + ', "distance": ' + str(total_row_1) + '},')
    write_file.write(workoutdata + '\n')
    workoutdata2 = str('{ "rower": ' + str(row_id_2) + ', "distance": ' + str(total_row_2) + '},')
    write_file.write(workoutdata2 + '\n')
    workoutdata3 = str('{ "rower": ' + str(row_id_3) + ', "distance": ' + str(total_row_3) + '},')
    write_file.write(workoutdata3 + '\n')
    workoutdata4 = str('{ "rower": ' + str(row_id_4) + ', "distance": ' + str(total_row_4) + '},')
    write_file.write(workoutdata4 + '\n')
    workoutdata5 = str('{ "rower": ' + str(row_id_5) + ', "distance": ' + str(total_row_5) + '}]')
    write_file.write(workoutdata5 + '\n')
    write_file.close()

    # with open('workout.json') as f:
    #     data = json.load(f)
    rowers = [
        {'rower': row_id_1, 'distance': total_row_1},
        {'rower': row_id_2, 'distance': total_row_2},
        {'rower': row_id_3, 'distance': total_row_3},
        {'rower': row_id_4, 'distance': total_row_4},
        {'rower': row_id_5, 'distance': total_row_5}
    ]

    return jsonify(rowers)

def get_rowers():
    # Connecting to erg
    ergs = list(pyrow.find())
    if len(ergs) == 0:
        print("No ergs found.")
    else:
        print("number of ergs found: " + str(len(ergs)))

    pm_list = []
    for erg in ergs:
        pm = pyrow.PyErg(erg)
        pm_info = pm.get_erg()
        print("Connected to erg." + str(pm_info['serial']))
        pm_list.append(pm)

    return pm_list

if __name__ == '__main__':
    rowers_list = get_rowers()
    app.run(debug=True)


import collections
from calendar import monthrange
from pprint import pprint

from settings import ROUTES, FLIGHTS
from datetime import timedelta, date
DAY, WEEK = timedelta(days=1), timedelta(weeks=1)

def get_week_schedule(from_city, to_city, start_date):
    # return dict { date0(datetime.date object): [flight1, flight2, flight3], date1: [...], ...}
    result = collections.defaultdict(list)
    flights = ROUTES[from_city][to_city]
    for key, val in FLIGHTS.items():
        if key not in flights:
            continue
        if val['period'] == 'week':
            for day_number in val['days']:
                _date = next_week_day(start_date, day_number)
                result[_date].append(key)
        elif val['period'] == 'month':
            for day_number in val['days']:
                _date = next_month_day(start_date, day_number)
                if _date < start_date + WEEK:
                    result[_date].append(key)
        elif val['period'] == 'day':
            for i in range(7):
                _date = start_date + i * DAY
                result[_date].append(key)
    return result

def next_week_day(start_date, day_number):
    delta = (day_number - start_date.weekday()) % 7
    return start_date + timedelta(days=delta)

def next_month_day(start_date, day_number):
    delta = (day_number - start_date.day) % monthrange(start_date.year, start_date.month)[1]
    return start_date + timedelta(days=delta)

def dispatcher(from_city, to_city, departure_date):
    flights = []
    _date = departure_date
    while True:
        schedule = get_week_schedule(from_city, to_city, _date)
        for i in range(7):
            for flight in schedule[_date]:
                flights.append((flight, date.strftime(_date, "%d-%m-%Y")))
                if len(flights) == 5:
                    return flights
            _date += DAY
        if _date - departure_date == timedelta(days=365):
            return flights

if __name__ == '__main__':
    pprint(dispatcher('Москва', 'Сыктывкар', date.today()))


#----------------------------------
#
#    Towers of Hanoi
#    By: Darren Kent
#
#    MainMenuState.py
#    This Class Handles The Main Menu Screen
#
#----------------------------------

# Imports
import os
import pygame
from State import *
import Button
import CheckBox
import Arrow
import PlayState

class MainMenuState( State ):
    def __init__( self , screen , gamecontroller ):
        self.mouseOverPlayed = False
        State.__init__( self )

        self.Screen = screen
        self.MenuStates = [ 'MainMenu' , 'SettingsMenu' , 'HighScores' , 'PlayMenu' ]
        self.CurrentMenu = self.MenuStates[0]
        self.MouseReleased = True
        self.GameController = gamecontroller
        self.StartSlider = False
        self.ScoresPage = 1
        self.NumDisks = 3
        self.ColorNum = 0
        self.Colors = [ (255,0,0) , (0,255,0) , (0,0,255) , (255,0,255) , (255,255,0) , (0,255,255) ]

        # Background Images
        self.Background = pygame.image.load( os.path.join( os.path.join( 'data' , 'textures' ) , 'jungle_background.png' ))
        self.Logo = pygame.image.load( os.path.join( os.path.join( 'data' , 'textures' ) , 'logo.png' ))
        self.LeftOverlay = pygame.image.load( os.path.join( os.path.join( 'data' , 'textures' ) , 'jungle_left.png' ))
        self.RightOverlay = pygame.image.load( os.path.join( os.path.join( 'data' , 'textures' ) , 'jungle_right.png' ))
        self.MenuBackgroundOverlay = pygame.image.load( os.path.join( os.path.join( 'data' , 'textures' ) , 'background_overlay.png' ))
        self.LeftForground = pygame.image.load( os.path.join( os.path.join( 'data' , 'textures' ) , 'jungle_left_forground.png' ))
        self.RightForground = pygame.image.load( os.path.join( os.path.join( 'data' , 'textures' ) , 'jungle_right_forground.png' ))
        self.Disk = pygame.image.load( os.path.join( os.path.join( 'data' , 'textures' ) , 'disk_1.png' ))

        # Sound Effects
        self.mouseClickSFX = pygame.mixer.Sound( os.path.join( os.path.join( 'data' , 'sfx' ) , 'sound5a.wav' ))
        self.mouseClickSFX.set_volume(self.GameController.UserSettings["[SFXVolume]"]/100.0)

        self.InitializeButtons()

    ''' Method: PlayBGMusic
        Start up the background music and set
        it to loop.'''
    def PlayBGMusic( self ):
        pygame.mixer.music.load( os.path.join( os.path.join( 'data' , 'music' ) , "ibnal-noor.mp3" ))
        pygame.mixer.music.set_volume(self.GameController.UserSettings["[MusicVolume]"]/100.0)
        pygame.mixer.music.play(-1)

    ''' Method: InitializeButtons
        Create instances of all of the buttons
        used in the Main Menu and sub Menus.'''
    def InitializeButtons( self ):
        # Create Text Objects
        self.Text = pygame.font.SysFont( "times", 24 , True , False )
        self.SFXVolumeText = self.Text.render( "SFX Volume" , 1 , (255,255,255) )
        self.MusicVolumeText = self.Text.render( "Music Volume" , 1 , (255,255,255) )
        self.PlayTitleText = self.Text.render( "Play" , 1 ,  (255,255,255) )
        self.NumDisksText = self.Text.render( "Number of Disks" , 1 , (255,255,255) )

        # Create Slider Bar and Check Box images
        self.SliderBar = pygame.image.load( os.path.join( os.path.join( 'data' , 'textures' ) , 'slider_bar.png' ))
        self.CheckBoxEmpty = pygame.image.load( os.path.join( os.path.join( 'data' , 'textures' ) , 'checkbox_unchecked.png' ))
        self.CheckBoxChecked = pygame.image.load( os.path.join( os.path.join( 'data' , 'textures') , 'checkbox_checked.png' ))

        # Create Button Objects
        self.MenuButtons = []
        ButtonImages = [ 'Quit' , 'Settings' , 'High Scores' , 'Play' ]
        for button in range( len( ButtonImages )):
            tNewButton = Button.Button( self.Screen.get_width() / 2 - 75 , self.Screen.get_height() - 50 * (2 + button) - (button * 10) , ButtonImages[button] , 'button_standard.png' , 'button_hover.png' , self.ButtonHandler )
            self.MenuButtons.append(tNewButton)
        self.MenuButtons.append(Button.Button( self.Screen.get_width() / 2 + self.MenuBackgroundOverlay.get_width() / 2 - 160 , self.Screen.get_height() / 2 + self.MenuBackgroundOverlay.get_height() / 2 - 60, 'Back' , 'button_standard.png' , 'button_hover.png' , self.ButtonHandler ))
        self.MenuButtons.append(Button.Button( self.Screen.get_width() / 2 + self.MenuBackgroundOverlay.get_width() / 2 - 320 , self.Screen.get_height() / 2 + self.MenuBackgroundOverlay.get_height() / 2 - 60, 'Save' , 'button_standard.png' , 'button_hover.png' , self.ButtonHandler ))
        self.MenuButtons.append(Button.Button( self.Screen.get_width() / 2 + self.MenuBackgroundOverlay.get_width() / 2 - 160 , self.Screen.get_height() / 2 + self.MenuBackgroundOverlay.get_height() / 2 - 120, 'Default' , 'button_standard.png' , 'button_hover.png' , self.ButtonHandler ))
        self.MenuButtons.append(Button.Button( self.Screen.get_width() / 2 + self.GameController.UserSettings["[MusicVolume]"] - 4, self.Screen.get_height() / 2 + self.MusicVolumeText.get_height() / 2 - 7 , 'Music' , 'button_slider_standard.png' , 'button_slider_hover.png' , self.SliderHandler ))
        self.MenuButtons.append(Button.Button( self.Screen.get_width() / 2 + self.GameController.UserSettings["[SFXVolume]"] - 4, self.Screen.get_height() / 2 + self.SFXVolumeText.get_height() / 2 + 23 , 'SFX' , 'button_slider_standard.png' , 'button_slider_hover.png' , self.SliderHandler ))
        self.MenuButtons.append(Button.Button( self.Screen.get_width() / 2 + self.MenuBackgroundOverlay.get_width() / 2 - 160 , self.Screen.get_height() / 2 + self.MenuBackgroundOverlay.get_height() / 2 - 120 , 'Start' , 'button_standard.png' , 'button_hover.png' , self.ButtonHandler ))
        self.MenuButtons.append(Button.Button( self.Screen.get_width() / 2 + self.MenuBackgroundOverlay.get_width() / 2 - 320 , self.Screen.get_height() / 2 + self.MenuBackgroundOverlay.get_height() / 2 - 120, 'Reset Scores' , 'button_standard.png' , 'button_hover.png' , self.ButtonHandler ))

        # Create Arros Objects
        self.Arrows = []
        self.Arrows.append(Arrow.Arrow( self.Screen.get_width() / 2 - self.MenuBackgroundOverlay.get_width() / 2 + 30 , self.Screen.get_height() / 2 + self.MenuBackgroundOverlay.get_height() / 2 - 50 , 'ScoresLeft' , 'arrow_left_standard.png' , 'arrow_left_hover.png' , 'arrow_left_gray.png' , self.ArrowHandler ))
        self.Arrows.append(Arrow.Arrow( self.Screen.get_width() / 2 - self.MenuBackgroundOverlay.get_width() / 2 + 70 , self.Screen.get_height() / 2 + self.MenuBackgroundOverlay.get_height() / 2 - 50 , 'ScoresRight' , 'arrow_right_standard.png' , 'arrow_right_hover.png' , 'arrow_right_gray.png' , self.ArrowHandler ))
        self.Arrows.append(Arrow.Arrow( self.Screen.get_width() / 2 - self.MenuBackgroundOverlay.get_width() / 2 + 30 , self.Screen.get_height() / 2 - 100 , 'DisksLeft' , 'arrow_left_standard.png' , 'arrow_left_hover.png' , 'arrow_left_gray.png' , self.ArrowHandler ))
        self.Arrows.append(Arrow.Arrow( self.Screen.get_width() / 2 - self.MenuBackgroundOverlay.get_width() / 2 + 120 , self.Screen.get_height() / 2 - 100 , 'DisksRight' , 'arrow_right_standard.png' , 'arrow_right_hover.png' , 'arrow_right_gray.png' , self.ArrowHandler ))
        self.Arrows.append(Arrow.Arrow( self.Screen.get_width() / 2 - self.MenuBackgroundOverlay.get_width() / 2 + 30 , self.Screen.get_height() / 2 - 50, 'DiskColorLeft' , 'arrow_left_standard.png' , 'arrow_left_hover.png' , 'arrow_left_gray.png' , self.ArrowHandler ))
        self.Arrows.append(Arrow.Arrow( self.Screen.get_width() / 2 - self.MenuBackgroundOverlay.get_width() / 2 + 180 , self.Screen.get_height() / 2 - 50, 'DiskColorRight' , 'arrow_right_standard.png' , 'arrow_right_hover.png' , 'arrow_right_gray.png' , self.ArrowHandler ))

        # Create Check Box Objects
        self.CheckBoxes = []
        Checks = [ 'Fullscreen:' , '800x600' , '1024x768' , '1280x768' , '1360x768' , '1366x768' , '1600x900' ]
        columns = [ self.Screen.get_width() / 2 - self.MenuBackgroundOverlay.get_width() / 2 + 30 , self.Screen.get_width() / 2 ]
        Fullscreen = CheckBox.CheckBox( columns[0] , self.Screen.get_height() / 2 - self.MenuBackgroundOverlay.get_height() / 2 + 30 , Checks[0] , Checks[0] , self.Text )
        if( self.GameController.UserSettings['[Fullscreen]'] == 1 ):
            Fullscreen.Checked = True
        self.CheckBoxes.append( Fullscreen )
        resolution = str( self.GameController.UserSettings['[ScreenWidth]'] ) + 'x' + str( self.GameController.UserSettings['[ScreenHeight]'] )
        count = 1
        for col in range( 2 ):
            for row in range( 3 ):
                tCheckBox = CheckBox.CheckBox( columns[col] , self.Screen.get_height() / 2 - self.MenuBackgroundOverlay.get_height() / 2 + 83 + 30 * row , Checks[count] , Checks[count] , self.Text )
                if( resolution == tCheckBox.ButtonId ):
                    tCheckBox.Checked = True
                self.CheckBoxes.append(tCheckBox)
                count += 1

    ''' Method: ExecuteStateLogic
        Executes one frame of game operations based
        on what the current menu is.'''
    def ExecuteStateLogic( self , KeysHeld , KeysPressed , clock ):
        if( not pygame.mixer.music.get_busy() ):
            self.PlayBGMusic()

        # Reset Mouse
        if( not pygame.mouse.get_pressed()[0] ):
            self.MouseReleased = True
            self.StartSlider = False

        # Execute Proper Menu Logic
        if( self.CurrentMenu == self.MenuStates[0] ):
            self.ExecuteMainMenu()
        elif( self.CurrentMenu == self.MenuStates[1] ):
            self.ExecuteSettingsMenu()
        elif( self.CurrentMenu == self.MenuStates[2] ):
            self.ExecuteHighScores()
        elif( self.CurrentMenu == self.MenuStates[3] ):
            self.ExecutePlayMenu()

    ''' Method: DrawStateFrame
        Draws One frame of the Main Menu State.'''
    def DrawStateFrame( self , screen , clock ):
        screen.fill( (0,253,251) )

        # Draw Background
        for tile in range( 1 + screen.get_width() / self.Background.get_width() ):
            screen.blit( self.Background , ( tile * self.Background.get_width() , screen.get_height() - self.Background.get_height() ))
        screen.blit( self.LeftOverlay , ( 0 , screen.get_height() - self.LeftOverlay.get_height() ))
        screen.blit( self.RightOverlay , ( screen.get_width() - self.RightOverlay.get_width() , screen.get_height() - self.RightOverlay.get_height() ))
        screen.blit( self.LeftForground , ( 0 , screen.get_height() - self.LeftForground.get_height() ))
        screen.blit( self.RightForground , ( screen.get_width() - self.RightForground.get_width() , screen.get_height() - self.RightForground.get_height() ))

        # Draw the Proper Menu
        if( self.CurrentMenu == self.MenuStates[0] ):
            self.DrawMainMenu()
        elif( self.CurrentMenu == self.MenuStates[1] ):
            self.DrawSettingsMenu()
        elif( self.CurrentMenu == self.MenuStates[2] ):
            self.DrawHighScores()
        elif( self.CurrentMenu == self.MenuStates[3] ):
            self.DrawPlayMenu()

    ''' Method: ExecuteMainMenu
        Executes one frame of Main Menu operations.'''
    def ExecuteMainMenu( self ):
        # Get Mouse Coords
        tX,tY = pygame.mouse.get_pos()

        # Check To See If Mouse is Over any Buttons
        for button in self.MenuButtons:
            button.SetMouseHover( tX, tY )

            # Press Button Check
            if( button.IsMouseInside() ):
                if( pygame.mouse.get_pressed()[0] and self.MouseReleased ):
                    self.MouseReleased = False
                    self.mouseClickSFX.play(0)
                    button.ExecuteButton()

    ''' Method: ExecuteSettingsMenu
        Executes one frame of Settings Menu operations'''
    def ExecuteSettingsMenu( self ):
        # Get Mouse Coords
        tX,tY = pygame.mouse.get_pos()

        # Check To See If Mouse is Over any Buttons
        self.MenuButtons[4].SetMouseHover( tX, tY )
        self.MenuButtons[5].SetMouseHover( tX, tY )
        self.MenuButtons[6].SetMouseHover( tX, tY )
        self.MenuButtons[7].SetMouseHover( tX, tY )
        self.MenuButtons[8].SetMouseHover( tX, tY )
        self.MenuButtons[10].SetMouseHover( tX, tY )
        for check in self.CheckBoxes:
            check.SetMouseHover( tX, tY )

        # Button Press Check
        for button in range( 3 ):
            if( self.MenuButtons[button+4].IsMouseInside() ):
                if ( pygame.mouse.get_pressed()[0] and self.MouseReleased ):
                    self.MenuButtons[button+4].ExecuteButton()
                    self.mouseClickSFX.play()

        self.MenuButtons[7].ExecuteButton()
        self.MenuButtons[8].ExecuteButton()

        if( self.MenuButtons[10].IsMouseInside() ):
                if ( pygame.mouse.get_pressed()[0] and self.MouseReleased ):
                    self.MenuButtons[10].ExecuteButton()
                    self.mouseClickSFX.play()

        # CheckBox Press Checks
        if( self.CheckBoxes[0].IsMouseInside() and pygame.mouse.get_pressed()[0] and self.MouseReleased ):
            self.MouseReleased = False
            if( self.CheckBoxes[0].Checked ):
                self.GameController.UserSettings['[Fullscreen]'] = 0
                self.CheckBoxes[0].Checked = False
            elif( not self.CheckBoxes[0].Checked ):
                self.GameController.UserSettings['[Fullscreen]'] = 1
                self.CheckBoxes[0].Checked = True

        resolution = str( self.GameController.UserSettings['[ScreenWidth]'] ) + 'x' + str( self.GameController.UserSettings['[ScreenHeight]'] )
        for check in range( 6 ):
            if( resolution == self.CheckBoxes[check+1].ButtonId ):
                self.CheckBoxes[check+1].Checked = True
            else:
                self.CheckBoxes[check+1].Checked = False
            if( self.CheckBoxes[check+1].IsMouseInside() and pygame.mouse.get_pressed()[0] and self.MouseReleased ):
                self.MouseReleased = False
                size = self.CheckBoxes[check+1].ButtonId.split('x')
                self.GameController.UserSettings['[ScreenWidth]'] = int(size[0])
                self.GameController.UserSettings['[ScreenHeight]'] = int(size[1])

    def ExecuteHighScores( self ):
        # Get Mouse Coords
        tX,tY = pygame.mouse.get_pos()

        # Check To See If Mouse is Over any Buttons
        self.MenuButtons[4].SetMouseHover( tX, tY )
        self.Arrows[0].SetMouseHover( tX, tY )
        self.Arrows[1].SetMouseHover( tX, tY )

        if( self.MenuButtons[4].IsMouseInside() and pygame.mouse.get_pressed()[0] and self.MouseReleased ):
            self.MenuButtons[4].ExecuteButton()
            self.mouseClickSFX.play()

        if( self.Arrows[0].IsMouseInside() and pygame.mouse.get_pressed()[0] and self.MouseReleased and self.ScoresPage != 1):
            self.Arrows[0].ExecuteButton()
            self.mouseClickSFX.play()

        if( self.Arrows[1].IsMouseInside() and pygame.mouse.get_pressed()[0] and self.MouseReleased and self.ScoresPage != 6):
            self.Arrows[1].ExecuteButton()
            self.mouseClickSFX.play()

        if( self.ScoresPage == 1 ):
            self.Arrows[0].Gray = True
        else:
            self.Arrows[0].Gray = False

        if( self.ScoresPage == 6 ):
            self.Arrows[1].Gray = True
        else:
            self.Arrows[1].Gray = False

    def ExecutePlayMenu( self ):
        # Get Mouse Coords
        tX,tY = pygame.mouse.get_pos()

        # Check To See If Mouse is Over any Buttons
        self.MenuButtons[4].SetMouseHover( tX , tY )    # Back Button
        self.MenuButtons[9].SetMouseHover( tX , tY )
        self.Arrows[2].SetMouseHover( tX , tY )
        self.Arrows[3].SetMouseHover( tX , tY )
        self.Arrows[4].SetMouseHover( tX , tY )
        self.Arrows[5].SetMouseHover( tX , tY )

        if( self.MenuButtons[4].IsMouseInside() and pygame.mouse.get_pressed()[0] and self.MouseReleased ):
            self.MenuButtons[4].ExecuteButton()
            self.mouseClickSFX.play()

        if( self.MenuButtons[9].IsMouseInside() and pygame.mouse.get_pressed()[0] and self.MouseReleased ):
            self.MenuButtons[9].ExecuteButton()
            self.mouseClickSFX.play()

        if( self.Arrows[2].IsMouseInside() and pygame.mouse.get_pressed()[0] and self.MouseReleased and self.NumDisks != 3):
            self.Arrows[2].ExecuteButton()
            self.mouseClickSFX.play()

        if( self.Arrows[3].IsMouseInside() and pygame.mouse.get_pressed()[0] and self.MouseReleased and self.NumDisks != 8):
            self.Arrows[3].ExecuteButton()
            self.mouseClickSFX.play()

        if( self.Arrows[4].IsMouseInside() and pygame.mouse.get_pressed()[0] and self.MouseReleased):
            self.Arrows[4].ExecuteButton()
            self.mouseClickSFX.play()

        if( self.Arrows[5].IsMouseInside() and pygame.mouse.get_pressed()[0] and self.MouseReleased):
            self.Arrows[5].ExecuteButton()
            self.mouseClickSFX.play()

        if( self.NumDisks == 3 ):
            self.Arrows[2].Gray = True
        else:
            self.Arrows[2].Gray = False

        if( self.NumDisks == 8 ):
            self.Arrows[3].Gray = True
        else:
            self.Arrows[3].Gray = False

    def DrawMainMenu( self ):
        self.Screen.blit( self.Logo , ( self.Screen.get_width() / 2 - self.Logo.get_width() / 2 , self.Screen.get_height() / 2 - self.Logo.get_height()))
        # Draw Buttons
        for button in range( 4 ):
            self.MenuButtons[button].DrawButtonWithText( self.Screen )

    def DrawSettingsMenu( self ):
        # Draw Background
        self.Screen.blit( self.MenuBackgroundOverlay , ( self.Screen.get_width() / 2 - self.MenuBackgroundOverlay.get_width() / 2, self.Screen.get_height() / 2 - self.MenuBackgroundOverlay.get_height() / 2 ))

        # Draw Sliders
        self.Screen.blit( self.MusicVolumeText , ( self.Screen.get_width() / 2 - self.MenuBackgroundOverlay.get_width() / 2 + 30 , self.Screen.get_height() / 2 ))
        self.Screen.blit( self.SFXVolumeText , ( self.Screen.get_width() / 2 - self.MenuBackgroundOverlay.get_width() / 2 + 30 , self.Screen.get_height() / 2 + 30 ))

        self.Screen.blit( self.SliderBar , ( self.Screen.get_width() / 2 , self.Screen.get_height() / 2 + self.MusicVolumeText.get_height() / 2 ))
        self.Screen.blit( self.SliderBar , ( self.Screen.get_width() / 2 , self.Screen.get_height() / 2 + self.SFXVolumeText.get_height() / 2 + 30))

        # Draw Buttons
        self.MenuButtons[4].DrawButtonWithText( self.Screen )    # Back Button
        self.MenuButtons[5].DrawButtonWithText( self.Screen )    # Save Button
        self.MenuButtons[6].DrawButtonWithText( self.Screen )    # Default Button
        self.MenuButtons[7].DrawButton( self.Screen )    # Music Slider
        self.MenuButtons[8].DrawButton( self.Screen )    # SFX Slider
        self.MenuButtons[10].DrawButtonWithText( self.Screen )

        # Draw CheckBoxes
        for checkbox in self.CheckBoxes:
            checkbox.DrawButton( self.Screen )

    def DrawHighScores( self ):
        # Draw Background
        self.Screen.blit( self.MenuBackgroundOverlay , ( self.Screen.get_width() / 2 - self.MenuBackgroundOverlay.get_width() / 2, self.Screen.get_height() / 2 - self.MenuBackgroundOverlay.get_height() / 2 ))

        # Draw Buttons
        self.MenuButtons[4].DrawButtonWithText( self.Screen )    # Back Button

        # Draw Scores
        tScoreTitle = self.Text.render( "Fastest Times: " + str(self.ScoresPage+2) + " Disks" , 1 , (255,255,255) )
        self.Screen.blit ( tScoreTitle , ( self.Screen.get_width()  / 2 - self.MenuBackgroundOverlay.get_width() / 2 + 30 , self.Screen.get_height() / 2 - self.MenuBackgroundOverlay.get_height() / 2 + 20 ))
        for score in range( 10 ):
            tPlaceText = self.Text.render( str( score + 1 ) + ":" , 1 , (255,255,255) )
            self.Screen.blit( tPlaceText , ( self.Screen.get_width()  / 2 - self.MenuBackgroundOverlay.get_width() / 2 + 30 , self.Screen.get_height() / 2 - self.MenuBackgroundOverlay.get_height() / 2 + 25 * ( score + 3 ) ))
            tPlayerText = self.Text.render( self.GameController.HighScores['['+str(self.ScoresPage+2)+'-'+str(score+1)+']'][1] , 1 , (255,255,255) )
            self.Screen.blit( tPlayerText , ( self.Screen.get_width()  / 2 - self.MenuBackgroundOverlay.get_width() / 2 + 80 , self.Screen.get_height() / 2 - self.MenuBackgroundOverlay.get_height() / 2 + 25 * ( score + 3 ) ))
            tScoreText = self.Text.render( self.GameController.HighScores['['+str(self.ScoresPage+2)+'-'+str(score+1)+']'][0] , 1 , (255,255,255) )
            self.Screen.blit( tScoreText , ( self.Screen.get_width()  / 2 - self.MenuBackgroundOverlay.get_width() / 2 + 180 , self.Screen.get_height() / 2 - self.MenuBackgroundOverlay.get_height() / 2 + 25 * ( score + 3 ) ))

        # Draw Page Arrows
        self.Arrows[0].DrawButton( self.Screen )
        self.Arrows[1].DrawButton( self.Screen )

    def DrawPlayMenu( self ):
        # Draw Background
        self.Screen.blit( self.MenuBackgroundOverlay , ( self.Screen.get_width() / 2 - self.MenuBackgroundOverlay.get_width() / 2, self.Screen.get_height() / 2 - self.MenuBackgroundOverlay.get_height() / 2 ))

        # Draw Buttons
        self.MenuButtons[4].DrawButtonWithText( self.Screen )    # Back Button
        self.MenuButtons[9].DrawButtonWithText( self.Screen )    # Play Button

        # Draw Text
        self.Screen.blit ( self.PlayTitleText , ( self.Screen.get_width()  / 2 - self.MenuBackgroundOverlay.get_width() / 2 + 30 , self.Screen.get_height() / 2 - self.MenuBackgroundOverlay.get_height() / 2 + 20 ))
        self.Screen.blit ( self.NumDisksText , ( self.Screen.get_width()  / 2 - self.MenuBackgroundOverlay.get_width() / 2 + 30 , self.Screen.get_height() / 2 - self.MenuBackgroundOverlay.get_height() / 2 + 70 ))
        tDiskCount = self.Text.render( str(self.NumDisks) , 1 , (255,255,255) )
        self.Screen.blit( tDiskCount , ( self.Screen.get_width() / 2 - self.MenuBackgroundOverlay.get_width() / 2 + 95 - tDiskCount.get_width() / 2 , self.Screen.get_height() / 2 - 95 ))

        # Draw Disk
        self.Screen.blit( self.Disk , ( self.Screen.get_width() / 2 - self.MenuBackgroundOverlay.get_width() / 2 + 77 , self.Screen.get_height() / 2 - 45 ))
        rect = pygame.Surface( ( self.Disk.get_width() , self.Disk.get_height() ) , pygame.SRCALPHA , 32 )
        rect.fill( self.Colors[self.ColorNum] )
        self.Screen.blit( rect , ( self.Screen.get_width() / 2 - self.MenuBackgroundOverlay.get_width() / 2 + 77 , self.Screen.get_height() / 2 - 45 ),None,pygame.BLEND_RGB_MULT)

        # Draw Disk Arrows
        self.Arrows[2].DrawButton( self.Screen )
        self.Arrows[3].DrawButton( self.Screen )
        self.Arrows[4].DrawButton( self.Screen )
        self.Arrows[5].DrawButton( self.Screen )

    ## Button Handlers
    def ButtonHandler( self , button ):
        self.MouseReleased = False
        if( button.ButtonId == 'Default'):
            self.GameController.ReadUserSettings(True)
            self.GameController.WriteUserSettings()
            self.GameController.InitializeWindow("TowersOfHanoi")
            self.InitializeButtons()
        elif( button.ButtonId == 'Save' ):
            self.GameController.WriteUserSettings()
            self.GameController.InitializeWindow("TowersOfHanoi")
            self.InitializeButtons()
        elif( button.ButtonId == 'Back' ):
            self.CurrentMenu = self.MenuStates[0]
        elif( button.ButtonId == 'Quit' ):
            self.StateQuit = True
        elif( button.ButtonId == 'Settings' ):
            self.CurrentMenu = self.MenuStates[1]
        elif( button.ButtonId == 'High Scores' ):
            self.CurrentMenu = self.MenuStates[2]
            self.GameController.RetrieveHighScores( False )
        elif( button.ButtonId == 'Play' ):
            self.CurrentMenu = self.MenuStates[3]
        elif( button.ButtonId == 'Start' ):
            self.GameController.StateMgr.AddState( 'PlayState' , PlayState.PlayState( self.Screen , self.GameController , self.NumDisks , self.Colors[self.ColorNum] ))
            self.GameController.StateMgr.SetState( 'PlayState' )
            pygame.mixer.music.stop()
        elif( button.ButtonId == 'Reset Scores' ):
            self.GameController.RetrieveHighScores( True )
            self.GameController.WriteHighScores()

    def SliderHandler( self , button ):
        tX , tY = pygame.mouse.get_pos()
        if( button.IsMouseInside() and pygame.mouse.get_pressed()[0] and self.MouseReleased ):
            self.StartSlider = button.ButtonId
            self.MouseReleased = False
        elif( self.StartSlider == button.ButtonId and pygame.mouse.get_pressed()[0] and not self.MouseReleased ):
            if( tX - self.Screen.get_width() / 2 <= 0 ):
                button.X = self.Screen.get_width() / 2 - 4
                self.GameController.UserSettings["["+button.ButtonId+"Volume]"] = 0
            elif( tX - self.Screen.get_width() / 2 >= 100 ):
                button.X = self.Screen.get_width() / 2 + 96
                self.GameController.UserSettings["["+button.ButtonId+"Volume]"] = 100
            else:
                button.X = tX - 4
                self.GameController.UserSettings["["+button.ButtonId+"Volume]"] = tX - self.Screen.get_width() / 2
        if(button.ButtonId == "Music"):
            pygame.mixer.music.set_volume( self.GameController.UserSettings["[MusicVolume]"] / 100.0 )
        if(button.ButtonId == "SFX"):
            self.mouseClickSFX.set_volume( self.GameController.UserSettings["[SFXVolume]"] / 100.0 )

    def ArrowHandler( self , button ):
        self.MouseReleased = False
        if( button.ButtonId == 'ScoresLeft' ):
            self.ScoresPage -= 1
            if( self.ScoresPage < 1 ):
                self.ScoresPage = 1
        elif( button.ButtonId == 'ScoresRight' ):
            self.ScoresPage += 1
            if( self.ScoresPage > 6 ):
                self.ScoresPage = 6
        elif( button.ButtonId == 'DisksLeft' ):
            self.NumDisks -= 1
            if( self.NumDisks < 3 ):
                self.NumDisks = 3
        elif( button.ButtonId == 'DisksRight' ):
            self.NumDisks += 1
            if( self.NumDisks > 8 ):
                self.NumDisks = 8
        elif( button.ButtonId == 'DiskColorLeft' ):
            self.ColorNum -= 1
            if( self.ColorNum < 0 ):
                self.ColorNum = len(self.Colors)-1
        elif( button.ButtonId == 'DiskColorRight' ):
            self.ColorNum += 1
            if( self.ColorNum >= len(self.Colors) ):
                self.ColorNum = 0


from typing import List
class Solution:
    def gameOfLife(self, board: List[List[int]]) -> None:
        """
        Do not return anything, modify board in-place instead.
        """
        def get_neighbors_count(i,j):
            directions=[
                (0,1),
                (1,0),
                (1,1),
                (0,-1),
                (-1,0),
                (-1,-1),
                (1,-1),
                (-1,1)
            ]
            count=0
            for direct in directions:
                x,y=i+direct[0],j+direct[1]
                if x>=0 and x<const_x and y>=0 and y<const_y and board[x][y]%2==1:
                    count+=1
            return count
        const_x=len(board)
        const_y=len(board[0])
        for ri in range(const_x):
            for cj in range(const_y):
                lives_count=get_neighbors_count(ri,cj)
                if board[ri][cj]==0 and lives_count==3:
                    board[ri][cj]+=2
                elif  board[ri][cj]==1 and lives_count<2:
                    board[ri][cj]+=2
                elif board[ri][cj]==1 and lives_count==2 or lives_count==3:
                    pass
                elif board[ri][cj]==1 and lives_count>3:
                    board[ri][cj]+=2
        for ri in range(const_x):
            for cj in range(const_y):
                if board[ri][cj]==2:
                    board[ri][cj]=1
                elif board[ri][cj]==3:
                    board[ri][cj]=0

if __name__ =="__main__":
    instance=Solution()
    instance.gameOfLife([[0,1,0],[0,0,1],[1,1,1],[0,0,0]])


import subprocess
import shutil
import json
from pathlib import Path

def kaggle_upload(run_dir, epochs):
    # create the directory to contain our new dataset
    run_dir = Path(run_dir)
    upload_dir = Path('./upload')
    target_dir = upload_dir / run_dir.parent.name / run_dir.name
    target_dir.mkdir(parents=True, exist_ok=True)

    # copy in the config file
    config = run_dir / 'checkpoints/config.yaml'
    shutil.copy(config, target_dir)

    # copy in the saved weights for the selected epochs
    for e in epochs:
        checkpoint = run_dir / f'checkpoints/checkpoint-epoch{e}.pth'
        shutil.copy(checkpoint, target_dir)

    # create metadata file
    meta = {
        'title': str(run_dir.name).replace('_', '-'),
        'subtitle': str(run_dir.parent.name).replace('_', '-'),
        'id': f'khornlund/{run_dir.name}'.replace('_', '-'),
        'licenses': [{'name': 'CC0-1.0'}]
    }

    with open(target_dir / 'dataset-metadata.json', 'w') as fh:
        json.dump(meta, fh, indent=4)

    # upload
    p = subprocess.Popen(['kaggle', 'datasets', 'create', '-p', str(target_dir), '-r', 'zip'])
    p.communicate()


import os
import sys
import cgi
import logging
import requests
import logging
from http.server import HTTPServer, SimpleHTTPRequestHandler
from ipaddress import ip_interface

HOST_NAME = '0.0.0.0'
PORT = 8080
DISCORD_WEBHOOK = 'https://discord.com/api/XXX'

logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)

# this is just to make the output look nice
formatter = logging.Formatter(fmt="%(asctime)s %(name)s.%(levelname)s: %(message)s", datefmt="%Y.%m.%d %H:%M:%S")

# this logs to stdout and I think it is flushed immediately
handler = logging.StreamHandler(stream=sys.stdout)
handler.setFormatter(formatter)
logger.addHandler(handler)

def ip_check(ipnet: list):
    ipaddressSet = set()
    for singleip in ipnet:
        try:
            ip_interface(singleip).with_prefixlen
        except ValueError:
            logger.info(f'does not appear to be an IPv4 or IPv6 interface: {singleip}')
        else:
            ipaddressSet.add(singleip)
    return ipaddressSet

def get_ipv4(ipaddressSet: set):
    ipv4addressSet = set()
    for singleip in ipaddressSet:
        if ip_interface(singleip).version == 4:
            ipv4addressSet.add(ip_interface(singleip).with_prefixlen)
    return ipv4addressSet

def get_ipv6(ipaddressSet: set):
    ipv6addressSet = set()
    for singleip in ipaddressSet:
        if ip_interface(singleip).version == 6:
            ipv6addressSet.add(ip_interface(singleip).with_prefixlen)
    return ipv6addressSet

# discord webhook works WOW
def web_hook(ipaddressSet: set, reason: str):
    data = {'content': 'IPs added to list', 'username': 'IP FEEDER',
        'embeds': [
            {
                'title' : ''.join(reason),
                'description' : '\r\n'.join(ipaddressSet)
            }
        ]
    }
    requests.post(DISCORD_WEBHOOK, json=data)
    return

def delete_ip_from_file(ipaddressSet: set):
    ipaddressesremoved = set()
    ListOfipv4addresses = get_ipv4(ipaddressSet)
    ListOfipv6addresses = get_ipv6(ipaddressSet)
    if ListOfipv4addresses:
        with open('ipv4.txt', 'r') as input:
            with open('tmpv4.txt', 'w') as output:
                for line in input.read().splitlines():
                    if line not in ListOfipv4addresses:
                        output.write(f'{line}\r\n')
                    else:
                        ipaddressesremoved.add(line)
                        logger.info(f'IPs removed {line}')
        os.replace('tmpv4.txt', 'ipv4.txt')
    if ListOfipv6addresses:
        with open('ipv6.txt', 'r') as input:
            with open('tmpv6.txt', 'w') as output:
                for line in input.read().splitlines():
                    if line not in ListOfipv6addresses:
                        output.write(f'{line}\r\n')
                    else:
                        ipaddressesremoved.add(line)
                        logger.info(f'IPs removed {line}')
        os.replace('tmpv6.txt', 'ipv6.txt')
    return ipaddressesremoved

# Need to check for duplicate ips
def add_ip_to_file(ipaddressSet: set):
    ipaddressesadded = set()
    ListOfipv4addresses = get_ipv4(ipaddressSet)
    ListOfipv6addresses = get_ipv6(ipaddressSet)
    with open('ipv4.txt', 'r+') as file:
        filelines = file.read().splitlines()
        for singleipv4adress in ListOfipv4addresses:
            if not singleipv4adress in filelines:
                file.write(f'{singleipv4adress}\r\n')
                ipaddressesadded.add(singleipv4adress)
                logger.info(f'IPs added {singleipv4adress}')
    with open('ipv6.txt', 'r+') as file:
        filelines = file.read().splitlines()
        for singleipv6adress in ListOfipv6addresses:
            if not singleipv6adress in filelines:
                file.write(f'{singleipv6adress}\r\n')
                ipaddressesadded.add(singleipv6adress)
                logger.info(f'IPs added {singleipv6adress}')
    return ipaddressesadded

class ip_feeder(SimpleHTTPRequestHandler):
    protocol_version = 'HTTP/1.1'
    error_content_type = 'text/plain'
    error_message_format = 'Error %(code)d: %(message)s'

    def do_GET(self):
        if self.path == '/ipv4':
            self.path = 'ipv4.txt'
            with open('ipv4.txt', 'r') as file:
                self.write_response(200, 'text/plain', file.read())
        if self.path == '/ipv6':
            self.path = 'ipv6.txt'
            with open('ipv6.txt', 'r') as file:
                self.write_response(200, 'text/plain', file.read())
        if self.path == '/':
            self.path = '/add.html'
            return SimpleHTTPRequestHandler.do_GET(self)

    def do_POST(self):
        if self.headers.get('content-type') == None:
            self.write_response(400, 'text/plain', 'missing content-type\r\n')
            return
        if self.path == '/ip/add':
            ctype, pdict = cgi.parse_header(self.headers.get('content-type'))
            pdict['boundary'] = bytes(pdict['boundary'], 'utf-8')
            pdict['CONTENT-LENGTH'] = int(self.headers['content-length'])
            if ctype == 'multipart/form-data':
                parsedfields = cgi.parse_multipart(self.rfile, pdict)
                if not {'ip', 'reason'} <= parsedfields.keys():
                    logger.info(f'bad ip from {self.client_address}')
                    self.write_response(400, 'text/plain', 'missing ip or reason\r\n')
                    return
                ipaddressSet = ip_check(parsedfields.get('ip'))
                if ipaddressSet:
                    ipaddressesadded = add_ip_to_file(ipaddressSet)
                    if ipaddressesadded:
                        web_hook(ipaddressesadded, parsedfields.get('reason'))
                        self.write_response(200, 'text/plain', 'IPs added\r\n')
                    else:
                        self.write_response(200, 'text/plain', 'IPs already in list\r\n')

                else:
                    logger.info(f'bad ip from {self.client_address}')
                    self.write_response(400, 'text/plain')
            else:
                logger.info(f'bad request from {self.client_address}')
                self.write_response(400, 'text/plain')

        elif self.path == '/ip/delete':
            ctype, pdict = cgi.parse_header(self.headers.get('content-type'))
            pdict['boundary'] = bytes(pdict['boundary'], 'utf-8')
            pdict['CONTENT-LENGTH'] = int(self.headers['content-length'])
            if ctype == 'multipart/form-data':
                parsedfields = cgi.parse_multipart(self.rfile, pdict)
                if not {'ip', 'reason'} <= parsedfields.keys():
                    logger.info(f'bad ip from {self.client_address}')
                    self.write_response(400, 'text/plain', 'missing ip or reason\r\n')
                    return
                ipaddressSet = ip_check(parsedfields.get('ip'))
                if ipaddressSet:
                    ipaddressesremoved = delete_ip_from_file(ipaddressSet)
                    if ipaddressesremoved:
                        #web_hook(ipaddressesremoved, parsedfields.get('reason'))
                        self.write_response(200, 'text/plain', 'IPs removed\r\n')
                    else:
                        self.write_response(200, 'text/plain', 'IPs not in a list\r\n')
                else:
                    logger.info(f'bad ip from {self.client_address}')
                    self.write_response(400, 'text/plain')
            else:
                logger.info(f'bad request from {self.client_address}')
                self.write_response(400, 'text/plain')
        else:
            self.write_response(400, 'text/plain')

    def write_response(self, status_code, content_type, content='\r\n'):
        response = content.encode('utf-8')
        self.send_response(status_code)
        self.send_header('Content-Type', content_type)
        self.send_header('Content-Length', str(len(response)))
        self.end_headers()
        self.wfile.write(response)

    def version_string(self):
        return 'TINY IP FEEDER'

if __name__ == '__main__':
    if not os.path.exists('ipv4.txt'):
        f = open('ipv4.txt', 'x')
        f.close()
    if not os.path.exists('ipv6.txt'):
        f = open('ipv6.txt', 'x')
        f.close()

    server = HTTPServer((HOST_NAME, PORT),  ip_feeder)
    logger.info(f'Server started http://{HOST_NAME}:{PORT}')
    try:
        server.serve_forever()
    except KeyboardInterrupt:
        server.server_close()
        logger.info('exited successfully')
        sys.exit(0)


from pathlib import Path
from argparse import ArgumentParser, ArgumentTypeError

from packr import ResourcePack
from packr.hosts import Github, Modrinth, CurseForge

class ArgumentError(Exception):
    pass
class MissingAuthError(Exception):
    pass

def is_true(value):
    return value.lower() in ('yes', 'true', 't', 'y', '1')

def strip(value):
    return value.strip().rstrip()

def gen_arguments():

    parser = ArgumentParser()

    parser.add_argument('-f', '--filename', help="", nargs='?')
    parser.add_argument('-p', '--parts', help="", type=strip, nargs='*')
    parser.add_argument('-d', '--output-directory', help="", default='build/')
    parser.add_argument('-of', '--optimize-files', type=is_true, nargs='?', const=True, default=False)

    parser.add_argument('-rn', '--release-name', help="", default="New Release")
    parser.add_argument('-rv', '--release-version', help="", default=None)
    parser.add_argument('-ch', '--changelog', help="", default=None)
    parser.add_argument('-mv', '--minecraft-versions', help="", nargs='*')

    parser.add_argument('-gr', '--github-repo', help="", default=None)
    parser.add_argument('-mi', '--modrinth-id', help="", default=None)
    parser.add_argument('-ci', '--curseforge-id', help="", default=None)

    parser.add_argument('-gt', '--github-token', help="", default=None)
    parser.add_argument('-mt', '--modrinth-token', help="", default=None)
    parser.add_argument('-ct', '--curseforge-token', help="", default=None)

    return parser.parse_args()

def main():

    args = gen_arguments()

    if not args.filename:
        raise ArgumentError('A filename is required to generate the pack.')
    if not args.parts:
        raise ArgumentError('Parts are required to generate the pack.')

    print('Generating Resource Pack...')

    pack = ResourcePack(
        args.filename,
        Path(args.output_directory),
        [Path(p) for p in args.parts],
        args.optimize_files
    )
    pack.gen(True)
    print('Done!\n')

    hosts = []

    if args.github_repo:
        if not args.github_token:
            raise MissingAuthError('A Github authorization token is required to upload a release to Github.')
        hosts.append(Github(pack, args.github_token, args.github_repo))
    if args.modrinth_id:
        if not args.modrinth_token:
            raise MissingAuthError('A Modrinth authorization token is required to upload a release to Modrinth.')
        hosts.append(Modrinth(pack, args.modrinth_token, args.modrinth_id))
    if args.curseforge_id:
        if not args.curseforge_token:
            raise MissingAuthError('A Curseforge authorization token is required to upload a release to Curseforge.')
        hosts.append(CurseForge(pack, args.curseforge_token, args.curseforge_id))

    for host in hosts:
        print(f'Publishing to {host.__class__.__name__}...')
        result = host.publish(args.release_version, args.release_name, args.changelog, args.minecraft_versions)
        if result.status_code in [200, 204]:
            print(f'Done! Uploaded successfully!\n')
        else:
            print(f'Done! But the upload failed... {result.reason}')
            try:
                print(result.json(), '\n')
            except Exception:
                print('')

if __name__ == '__main__':
    main()


from typing import List

class Solution:
    def letterCombinations(self, digits: str) -> List[str]:
        digit_dict = {}
        digit_dict["2"] = "abc"
        digit_dict["3"] = "def"
        digit_dict["4"] = "ghi"
        digit_dict["5"] = "jkl"
        digit_dict["6"] = "mno"
        digit_dict["7"] = "pqrs"
        digit_dict["8"] = "tuv"
        digit_dict["9"] = "wxyz"
        res = []

        def backtrack(cur_str: list, pos: int):
            if pos == len(digits):
                if cur_str:
                    res.append("".join(cur_str))
                    # print("res:", res)
                return

            for letter in digit_dict[digits[pos]]:
                cur_str.append(letter)
                # print("cur_str:", cur_str)
                backtrack(cur_str, pos + 1)
                cur_str.pop()

        cur_str = []
        backtrack(cur_str, 0)
        return res

digits = "23"
digits = ""
digits = "2"
print(Solution().letterCombinations(digits))