Full Code of O-MG/O.MG-Firmware for AI

stable 18150f0e3404 cached
20 files
586.1 KB
185.3k tokens
375 symbols
1 requests
Download .txt
Showing preview only (618K chars total). Download the full file or copy to clipboard to get everything.
Repository: O-MG/O.MG-Firmware
Branch: stable
Commit: 18150f0e3404
Files: 20
Total size: 586.1 KB

Directory structure:
gitextract_edbq2fj2/

├── .gitignore
├── LICENSE
├── README.md
├── c2server/
│   ├── c2server.py
│   ├── index.html
│   └── requirements.txt
├── firmware/
│   └── page.mpfs
├── flash.py
├── hashes.txt
├── scripts/
│   ├── flashapi.py
│   ├── miniterm.py
│   └── pager.py
└── tools/
    └── HIDX/
        ├── powershell/
        │   ├── minify.ps1
        │   ├── win-hidexfil.ps1
        │   └── win-hidshell.ps1
        ├── python/
        │   ├── hidxshell.py
        │   ├── linux-nativeshell.py
        │   ├── stealthlink-client-universal.py
        │   └── stealthlink-host-universal.py
        └── shell/
            └── linux-hidexfil.sh

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
#  Usually these files are written by a python script from a template
#  before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
target/

# Jupyter Notebook
.ipynb_checkpoints

# IPython
profile_default/
ipython_config.py

# pyenv
.python-version

# pipenv
#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
#   However, in case of collaboration, if having platform-specific dependencies or dependencies
#   having no cross-platform support, pipenv may install dependencies that don't work, or not
#   install all needed dependencies.
#Pipfile.lock

# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/

# Celery stuff
celerybeat-schedule
celerybeat.pid

# SageMath parsed files
*.sage.py

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

# Pyre type checker
.pyre/
.DS_Store
*.pem



================================================
FILE: LICENSE
================================================
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.

GRANT 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.

LICENSE 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.

TITLE, 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.

MAINTENANCE 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.

EXPORT 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.

TERMINATION 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.

DISCLAIMER 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.

LIMITATION 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.

USER 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.

GOVERNING LAW This AGREEMENT shall be governed by and construed in accordance with the laws of the State of California.

ENTIRE 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.

RESERVATION OF RIGHTS All rights not expressly granted under this AGREEMENT are reserved entirely to the OWNER.

HEADINGS 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.

BINDING 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.

INTERPRETATION 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.

ATTORNEY'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.

SEVERABILITY 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.


================================================
FILE: README.md
================================================
This is for all versions of the O.MG Cable, O.MG Adapter, and O.MG Plug



# [Setup Instructions & Latest Firmware](https://github.com/O-MG/O.MG-Firmware/wiki)



<img src="https://o.mg.lol/OMGCable-pkg.jpg" >

================================================
FILE: c2server/c2server.py
================================================
import json
import os
import random as rnd
import socket
import sys
import time
from http.server import BaseHTTPRequestHandler, HTTPServer, SimpleHTTPRequestHandler, ThreadingHTTPServer
import threading
import urllib.request
import re
from urllib.parse import urlparse, parse_qs
from datetime import datetime
import monocypher as mono

provisionFile_ver = 3
SESSION_LIFESPAN = 120
serverPort = 8000
adminPort = 8080

def get_ip_address():
    try:
        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        s.connect(("8.8.8.8", 80))
        ip_address = s.getsockname()[0]
        s.close()
        return ip_address
    except Exception as e:
        print("Error occurred while getting IP address:", e)
        return None


ip_address = get_ip_address()
if ip_address:
    print("The computer's current IP address is:", ip_address)
else:
    print("Could not determine the computer's IP address.")
hostName = ip_address


def write_c2log(alias, direction, msg):
    with open("c2log", "a") as f:
        timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        f.write(f"{alias}, {timestamp}, {direction}, {msg}\n")

class c2server(BaseHTTPRequestHandler):
    def end_headers(self):
        self.send_header('Access-Control-Allow-Origin', '*')
        self.send_header('Access-Control-Allow-Methods', 'GET,POST')
        self.send_header('Access-Control-Allow-Headers', 'Content-Type')
        SimpleHTTPRequestHandler.end_headers(self)

    
    def do_GET(self):
        i = self.path.find("/C2=")
        if i < 0:
            return

        print("------ <GET> ------------------ ", self.path[i:])

        raw_msg = bytearray.fromhex(self.path[i + 4:])
        msg_size = (len(self.path) - 4) / 2
        resp = the_host.process_message(raw_msg, msg_size)

        self.send_response(200)
        if resp is not None:
            self.wfile.write(bytes(resp.hex(), "utf-8"))

    def do_POST(self):
        length = int(self.headers['Content-Length'])
        body = self.rfile.read(length)
        print(f"------ <POST> ------------------   len: {length}")

        resp = the_host.process_message(body, length)

        if resp is not None:
            self.wfile.write(bytes(resp.hex(), "utf-8"))


class c2admin(SimpleHTTPRequestHandler):
    def end_headers(self):
        self.send_header('Access-Control-Allow-Origin', '*')
        self.send_header('Access-Control-Allow-Methods', 'GET,POST')
        self.send_header('Access-Control-Allow-Headers', 'Content-Type')
        SimpleHTTPRequestHandler.end_headers(self)

    def handle_C2admin(self):
        with open("c2config", "r") as f:
            content = f.read()
        self.send_response(200)
        self.send_header("Content-type", "application/json")
        self.end_headers()
        self.wfile.write(content.encode("utf-8"))
        
    def handle_C2log(self, alias=None, abridge=True):
        if os.path.exists("c2log"):
            with open("c2log", "r") as f:
                lines = f.readlines()
        
            if alias is not None:
                lines = [line for line in lines if line.startswith(alias)]
                
            if abridge:
                lines = [line for line in lines if not re.search(r'.*, out, \n', line)]
        
            content = "".join(lines)
        
            self.send_response(200)
            self.send_header("Content-type", "text/csv")
            self.end_headers()
            self.wfile.write(content.encode("utf-8"))
        else:
            log("No c2log file exists.")
            with open("c2log", 'w') as f:
                f.write("")

    def do_GET(self):
                url = urlparse(self.path)
                query_params = parse_qs(url.query)
                alias = query_params.get('alias', [None])[0]
                abridge = query_params.get('abridge', [False])[0]
                        
                if url.path == "/C2admin":
                    self.handle_C2admin()
                elif url.path == "/C2log":
                    self.handle_C2log(alias, abridge)
                elif url.path == "/" or url.path == "/index.html":
                    self.path = "/index.html"
                    super().do_GET()
                else:
                    self.send_response(404)
                    self.end_headers()
    
    def do_POST(self):
        if self.path == "/C2admin":
            content_len = int(self.headers.get("Content-Length"))
            body = self.rfile.read(content_len)
            body_decoded = body.decode("utf-8")
            body_data = json.loads(body.decode("utf-8"))
            alias = body_data.get("alias", "")
            action = body_data.get("action", "")
            data = body_data.get("data", "")
        
            commands = [
                "CI",
                "CV",
                "CWInfo",
                "CNGet",
                "CTList",
                "CFList",
                "CWStatus",
                "CEStatus",
                "CLStatus",
                "CHStatus",
                "C2Status",
                "C2Info"
            ]
        
            client_id = next((device["client_id"] for device in config.devices if device["alias"] == alias), None)
            client = the_host.clients.get(client_id)
            if client and action == "queueAdd":
                if data not in client.cmd_queue:
                    client.cmd_queue.append(data)
                    config.save_provision_files()
                self.send_response(200)
                self.end_headers()
            elif client and action == "queueDelete":
                try:
                    data_int = int(data)
                    log(data_int)
                    client.cmd_queue.pop(data_int)
                    config.save_provision_files()
                    self.send_response(200)
                    self.end_headers()
                except (ValueError, IndexError):
                    self.send_response(400)
                    self.end_headers()
            elif client and action == "queueClear":
                client.cmd_queue = []
                config.save_provision_files()
                self.send_response(200)
                self.end_headers()
            elif client and action == "logClear":
                print(alias);
                if alias == "clearAllLogs":
                    with open("c2log", "w") as f:
                        f.write("")
        
                    for device_client in the_host.clients.values():
                        device_client.cmd_queue = commands + device_client.cmd_queue
                else:
                    with open("c2log", "r") as f:
                        lines = f.readlines()
                    lines = [line for line in lines if not line.startswith(alias)]
                    with open("c2log", "w") as f:
                        f.writelines(lines)
        
                    client.cmd_queue = commands + client.cmd_queue
        
                config.save_provision_files()
                self.send_response(200)
                self.end_headers()
            elif client and action == "logClearAll":
                with open("c2log", "w") as f:
                    f.write("")
                
                for device_client in the_host.clients.values():
                    device_client.cmd_queue = commands + device_client.cmd_queue
                
                config.save_provision_files()
                self.send_response(200)
                self.end_headers()
            elif client and action == "pollInterval":
                alias = body_data.get("alias", "")
                data = body_data.get("data", {})
            
                matching_device = next((device for device in config.devices if device["alias"] == alias), None)
                if matching_device is not None:
                    for key in ["poll_seconds", "fast_seconds", "contact_seconds"]:
                        if key in data:
                            matching_device[key] = data[key]
                            
                config.save_provision_files()
                self.send_response(200)
                self.end_headers()
            else:
                self.send_response(400)
                self.end_headers()
        else:
            super().do_POST()


class Host:
    session_count = 0
    clients = {}
    sessions = {}

    class ClientData:
        def __init__(self, client_id, alias, client_exchange_key, cmd_queue):
            self.client_id = client_id
            self.alias = alias
            self.exchange_key = client_exchange_key
            self.cmd_queue = cmd_queue if cmd_queue else [
                "CI",
                "CV",
                "CWInfo",
                "CNGet",
                "CTList",
                "CFList",
                "CWStatus",
                "CEStatus",
                "CLStatus",
                "CHStatus",
                "C2Status",
                "C2Info"
            ]

    class SessionData:
        def __init__(self, session_id, session_key, client_id, device_id, expires, interface_version):
            self.session_id = session_id
            self.session_key = session_key
            self.client_id = client_id
            self.device_id = device_id
            self.expires = expires
            self.ver = interface_version
            self.msg_count = 0
            self.next_cmd = 0

    def __init__(self, host_private_key, host_public_key):
        self.host_private_key = bytes.fromhex(host_private_key) if host_private_key else None
        self.public_key = bytes.fromhex(host_public_key) if host_public_key else None
        self.session_lifespan = SESSION_LIFESPAN

        config = c2config(self.public_key, self.host_private_key, ip_address, serverPort, adminPort)

        for device in config.devices:
            client_id = device['client_id']
            alias = device['alias']
            client_public_key = bytes.fromhex(device['client_public_key'])
            client_exchange_key = mono.key_exchange(self.host_private_key, client_public_key)
            cmd_queue = device.get('cmd_queue', None)
            client = self.ClientData(client_id, alias, client_exchange_key, cmd_queue)
            self.clients[client_id] = client

    def provision(self, alias, poll_seconds, fast_seconds, contact_seconds):
        global config

        for device in config.devices:
            if device['alias'] == alias:
                print(f"Warning: Existing Device Alias {alias} already exists. Provisioning did not continue.")
                return

        self.session_count += 1
        client_id = rnd.randint(0, 1000000)
        client_secret_key = gen_random(32)
        client_public_key = mono.compute_key_exchange_public_key(client_secret_key)
        client_exchange_key = mono.key_exchange(self.host_private_key, client_public_key)
        client = self.ClientData(client_id, alias, client_exchange_key, None)
        self.clients[client_id] = client

        provision_data = {
            'alias': alias,
            'client_id': client_id,
            'client_public_key': client_public_key.hex(),
            'poll_seconds': poll_seconds,
            'fast_seconds': fast_seconds,
            'contact_seconds': contact_seconds
        }

        config.devices.append(provision_data)
        config.save_provision_files()

        log("Provisioned  [{}]   alias: {}".format(client_id, alias))

        print(f"---------- Provision-file: {alias} ----------")
        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}"
        print(device_config)
        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.')
    def make_err_msg(self, err_code, err_text):
        print("***** Sending Error ", err_code, err_text)
        wrapper = MsgWrapper(109, err_code)
        return wrapper.secure_msg()

    def handle_hello(self, hello_msg, client):
        print("<<<<< Hello Msg >>>>>")
        client_id = client.client_id
        device_id = hello_msg.plain_msg[4:10]
        ver = 1
        if hello_msg.msg_size > 10:
            ver = int.from_bytes(hello_msg.plain_msg[10:11], 'little')
    
        for key in self.sessions:
            session = self.sessions[key]
            if session.client_id == client_id and session.device_id == device_id:
                del (self.sessions[key])
                break
    
        session_key = gen_random(32)
        not_unique = True
        while not_unique:
            session_id = rnd.randint(0, 1000000)
            not_unique = session_id in self.sessions
        expires = int(time.time()) + self.session_lifespan + 5
        self.sessions[session_id] = self.SessionData(session_id, session_key, client_id, device_id, expires, ver)
        print(f"Session id: {session_id}, ver: {ver}, key: {session_key.hex()}")
    
        # Fetch the per-device values from the c2config device entry with the matching client_id
        matching_device = next((device for device in config.devices if device["client_id"] == client.client_id), None)
        if matching_device is not None:
            poll_seconds = matching_device.get("poll_seconds", self.session_lifespan)
            fast_seconds = matching_device.get("fast_seconds", self.session_lifespan)
            contact_seconds = matching_device.get("contact_seconds", poll_seconds)
        else:
            poll_seconds = fast_seconds = contact_seconds = self.session_lifespan
        
        
        plain_resp = HelloResponse(session_id, session_key, self.session_lifespan, poll_seconds, fast_seconds, contact_seconds)
        resp = MsgWrapper(101, plain_resp.text, len(plain_resp.text))
        resp.encrypt(client.exchange_key)
        wrapped_msg = resp.secure_msg()
        print("   secure msg: ", wrapped_msg.hex())
        return wrapped_msg

    def handle_poll(self, poll_msg, session):
        seq_no = int.from_bytes(poll_msg.plain_msg[4:8], 'little')
        script_status = int.from_bytes(poll_msg.plain_msg[8:9], 'little')
        print(f"<<<<< Poll Msg #{seq_no} - SS: {script_status}>>>>>")

        client = self.clients[session.client_id]

        if len(client.cmd_queue) == 0:
            cmd = ""
        else:
            cmd = client.cmd_queue[session.next_cmd % len(client.cmd_queue)]

        cmd_len = len(cmd)
        if cmd_len == 0:
            is_more = 0
        else:
            is_more = 1
        if cmd.startswith("CE") and not cmd.startswith("CEStatus"):
            if session.ver > 1 and script_status != 0:
                cmd = ""
            else:
                session.next_cmd += 1
        else:
            session.next_cmd += 0

        if len(cmd):
            print(f">>>>> Running command {cmd}")
        else:
            print(">>>>> Idle  (No command sent)")

        msg = CommandMsg(seq_no, bytes(cmd, "utf-8"), is_more)
        resp = MsgWrapper(102, msg.text, len(msg.text))
        resp.encrypt(session.session_key)
        wrapped_msg = resp.secure_msg()

        client = self.clients[session.client_id]
        write_c2log(client.alias, "out", cmd)

        return wrapped_msg

    def handle_response(self, command_response, session):
        seq_no = int.from_bytes(command_response.plain_msg[0:4], 'little')
        resp_size = int.from_bytes(command_response.plain_msg[4:6], 'little')
        cmd_offset = 7
        
        try:
            response = command_response.plain_msg[7:].decode('utf-8')
        except UnicodeDecodeError:
            hex_response = command_response.plain_msg[7:].hex()
            split_hex = hex_response.split("09", 1)
            if len(split_hex) > 1:
                try:
                    first_part = bytes.fromhex(split_hex[0]).decode('utf-8')
                    response = first_part + '\t' + split_hex[1]
                except UnicodeDecodeError:
                    response = hex_response
            else:
                response = hex_response
        
        print(f"<<<<< Output for #{seq_no} >>>>>   size: {resp_size} ({len(command_response.plain_msg[7:])})")
        print(response)
        
        client = self.clients[session.client_id]
        write_c2log(client.alias, "in ", response)
        
        cmd = client.cmd_queue.pop(0)  # Remove the cmd from the cmd_queue
        config.save_provision_files()

    def handle_error(self, err_msg, session):
        log("Device error: " + err_msg.plain_text)

    def process_message(self, raw_msg, raw_len):
        if raw_len < 47:
            return self.make_err_msg(101, "Bad message, too short")

        msg = MsgWrapper(raw_msg)

        if msg.msg_type == 1:
            if msg.id not in self.clients:
                return self.make_err_msg(102, "Unknown client-id")
            client = self.clients[msg.id]
            key = client.exchange_key
        else:
            if msg.id not in self.sessions:
                config.load_provision_files()
                return self.make_err_msg(103, "Unknown session-id")
            session = self.sessions[msg.id]
            if session.expires < int(time.time()):
                return self.make_err_msg(104, "Session has expired")
            key = session.session_key

        if not msg.decrypt(key):
            return self.make_err_msg(105, "Invalid encryption")

        if msg.msg_type == 1:
            return self.handle_hello(msg, client)
        if msg.msg_type == 2:
            return self.handle_poll(msg, session)
        elif msg.msg_type == 3:
            return self.handle_response(msg, session)
        elif msg.msg_type == 9:
            return self.handle_error(msg, session)

        return self.make_err_msg(106, "Unknown message-type")


class HelloResponse:
    def __init__(self, session_id, session_key, expiry, poll_seconds, fast_seconds, contact_seconds):
        self.text = bytearray()
        self.text.extend(session_id.to_bytes(4, 'little'))
        self.text.extend(expiry.to_bytes(4, 'little'))
        self.text.extend(session_key)
        self.text.extend(poll_seconds.to_bytes(4, 'little'))
        self.text.extend(fast_seconds.to_bytes(4, 'little'))
        self.text.extend(contact_seconds.to_bytes(4, 'little'))

class CommandMsg:
    def __init__(self, seq, command, is_more):
        cmd_len = len(command)
        self.text = bytearray()
        self.text.extend(seq.to_bytes(4, 'little'))
        self.text.extend(cmd_len.to_bytes(2, 'little'))
        self.text.extend(is_more.to_bytes(1, 'little'))
        self.text.extend(command)
        print("   is_more: ", is_more)


class MsgWrapper:
    def __init__(self, *args):
        if len(args) == 1:
            msg = args[0]
            self.msg_type = msg[0]
            self.id = int.from_bytes(msg[1:5], 'little')
            self.nonce = msg[5:29]
            self.mac = msg[29:45]
            if self.msg_type != 9:
                self.msg_size = int.from_bytes(msg[45:47], 'little')
                self.err_code = 0
            else:
                self.err_code = int.from_bytes(msg[45:47], 'little')
                self.msg_size = 0
            self.encoded = msg[47:47 + self.msg_size]
            self.plain_msg = None
        else:
            self.msg_type = args[0]
            self.id = 0
            self.nonce = None
            self.mac = None
            self.encoded = None
            if len(args) == 2:
                self.err_code = args[1]
                self.msg_size = 0
            else:
                self.plain_msg = args[1]
                self.msg_size = args[2]
                self.err_code = 0

    def encrypt(self, key):
        if self.msg_type == 109:
            return

        self.nonce = gen_random(24)
        self.mac, self.encoded = mono.lock(key, self.nonce, self.plain_msg)

    def decrypt(self, key):
        if self.msg_type == 9:
            return

        self.plain_msg = mono.unlock(key, self.nonce, self.mac, self.encoded)
        if self.plain_msg is None:
            print("   Decrypt failed")
            return False
        return True

    def secure_msg(self):
        msg = bytearray()
        msg.extend(self.msg_type.to_bytes(1, 'little'))
        msg.extend(self.id.to_bytes(4, 'little'))
        if self.msg_type == 9 or self.msg_type == 109:
            msg.extend(bytearray(40))
            msg.extend(self.err_code.to_bytes(2, 'little'))
        else:
            msg.extend(self.nonce)
            msg.extend(self.mac)
            msg.extend(self.msg_size.to_bytes(2, 'little'))
            msg.extend(self.encoded)
        return msg


def log(err_text):
    print("<LOG> ", err_text)


def gen_random(size):
    return bytes(rnd.randint(0, 255) for _ in range(size))


class c2config:
    def __init__(self, host_public_key, host_private_key, host_url, host_port, admin_port):
        self.host_public_key = host_public_key
        self.host_private_key = host_private_key
        self.host_url = host_url
        self.host_port = host_port
        self.admin_port = admin_port
        self.devices = []

        if os.path.exists("c2config"):
            self.load_provision_files()
        else:
            print("No c2config file exists.")
            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')
            self.host_private_key = gen_random(32).hex()  # Generate host private key
            self.host_public_key = mono.compute_key_exchange_public_key(
                bytes.fromhex(self.host_private_key)).hex()  # Compute host public key
            self.save_provision_files()  # Save the generated keys

    def save_provision_files(self):
        devices_with_cmd_queue = []
        for device in self.devices:
            client = the_host.clients[device['client_id']]
            device_with_cmd_queue = device.copy()
            device_with_cmd_queue['cmd_queue'] = client.cmd_queue
            devices_with_cmd_queue.append(device_with_cmd_queue)

        config_data = {
            'host_public_key': self.host_public_key,
            'host_private_key': self.host_private_key,
            'host_url': self.host_url,
            'host_port': self.host_port,
            'admin_port': self.admin_port,
            'devices': devices_with_cmd_queue
        }
        with open("c2config", 'w') as f:
            json.dump(config_data, f, indent=4)

    def load_provision_files(self):
        try:
            with open("c2config", 'r') as f:
                if os.path.getsize("c2config") == 0:
                    self.devices = []
                else:
                    config_data = json.load(f)
                    self.host_public_key = config_data['host_public_key']
                    self.host_private_key = config_data['host_private_key']
                    self.host_url = config_data['host_url']
                    self.host_port = config_data['host_port']
                    self.admin_port = config_data['admin_port']
                    self.devices = config_data['devices']
        except FileNotFoundError:
            self.devices = []
            self.host_private_key = gen_random(32).hex()
            self.host_public_key = mono.compute_key_exchange_public_key(
                bytes.fromhex(self.host_private_key)).hex()
            self.save_provision_files()
        except json.JSONDecodeError:
            self.devices = []

def validate_arguments(argv):
            if (len(argv) - 2) % 4 != 0:
                return False, "\nERROR: Incorrect number of arguments.\nEach device provision requires 4 arguments: (alias, poll_seconds, fast_seconds, contact_seconds).\n"
            try:
                for i in range(2, len(argv), 4):
                    int(argv[i + 1])
                    int(argv[i + 2])
                    int(argv[i + 3])
            except ValueError:
                return False, "ERROR: Expected integer values for poll_seconds, fast_seconds, and contact_seconds.\n"
            return True, ""


rnd.seed()
webServer = None

if __name__ == "__main__":
    config = c2config(None, None, ip_address, serverPort, adminPort)
    the_host = Host(config.host_private_key, config.host_public_key)

    for device in config.devices:
        client_id = device['client_id']
        alias = device['alias']
        client_exchange_key = mono.key_exchange(the_host.host_private_key, bytes.fromhex(device['client_public_key']))
        client = the_host.ClientData(client_id, alias, client_exchange_key, device['cmd_queue'])
        the_host.clients[client_id] = client

    config.load_provision_files()

    if len(sys.argv) > 1 and sys.argv[1].lower() == 'provision':
        valid, error_msg = validate_arguments(sys.argv)
        if not valid:
            print(error_msg)
            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")
        else:
            index = 2
            while index < len(sys.argv):
                try:
                    alias = sys.argv[index]
                    poll_seconds = int(sys.argv[index + 1])
                    fast_seconds = int(sys.argv[index + 2])
                    contact_seconds = int(sys.argv[index + 3])
                    the_host.provision(alias, poll_seconds, fast_seconds, contact_seconds)
                    index += 4
                except (IndexError, ValueError):
                    print("Invalid arguments for provisioning. Expected sets of (alias, poll_seconds, fast_seconds, contact_seconds).")
    else:
        try:
            secondaryServer = ThreadingHTTPServer((config.host_url, config.admin_port), c2admin)
            secondaryThread = threading.Thread(target=secondaryServer.serve_forever)
            secondaryThread.daemon = True
            secondaryThread.start()
            print("AdminUI server started http://%s:%s" % (config.host_url, config.admin_port))
        except OSError as e:
            print("Failed to start the AdminUI server.")
            print(e);
            if 'Address already in use' in str(e):
                print(f"Port {config.admin_port} is already in use. Please choose a different port in your c2config.")
            elif 'Can\'t assign requested address' in str(e):
                print(f"Cannot assign requested address: {config.host_url}. Please check the host URL in your c2config.")
            sys.exit(1)

        try:
            webServer = HTTPServer((config.host_url, config.host_port), c2server)
            print("C2 server started http://%s:%s" % (config.host_url, config.host_port))
            with webServer:
                webServer.serve_forever()
        except KeyboardInterrupt:
            print("Server stopped.")
        except OSError as e:
            print(e);
            print("Failed to start the C2 server.")
            if 'Address already in use' in str(e):
                print(f"Port {config.host_port} is already in use. Please choose a different port in your c2config.")
            elif 'Can\'t assign requested address' in str(e):
                print(f"Cannot assign requested address: {config.host_url}. Please check the host URL in your c2config.")
            sys.exit(1)
        finally:
            if webServer is not None:
                webServer.server_close()
                print("Server stopped.")

================================================
FILE: c2server/index.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
	<title>O.MG</title>
	<meta charset="utf-8">
	<meta name="viewport" content="viewport-fit=auto,width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no">
	<meta name="apple-mobile-web-app-capable" content="yes">
	<meta http-equiv="cache-control" content="no-cache">
	<meta http-equiv="expires" content="0">
	<meta http-equiv="pragma" content="no-cache">
	<link href="favicon.ico" rel="shortcut icon" type="image/x-icon">
	<style>
		:root {
			--bgColor1: hsl(0, 0%, 75%);
			--bgColor2: hsl(0, 0%, 25%);
			--bgColor3: hsl(0, 0%, 10%);
			--watermarkColor: hsla(0, 0%, 15%, .4);
			--navColor: hsl(0, 0%, 100%);
			--fontColor: hsl(0, 0%, 100%);
			--inactiveColor: hsl(0, 0%, 30%);
			--inactiveGradient1: hsl(0, 0%, 25%);
			--inactiveGradient2: hsl(0, 0%, 15%);
			--toggleOnColor: hsl(150, 75%, 30%);
			--toggleOnGradient1: hsl(150, 75%, 40%);
			--toggleOnGradient2: hsl(150, 75%, 50%);
			--sidebarGradient: hsla(0, 0%, 5%, 0.7);
			--accentColor: hsl(0, 0%, 40%);
			--accentGradient1: hsl(0, 0%, 30%);
			--accentGradient2: hsl(0, 0%, 50%)
		}
		
		body,
		input[type=text],
		textarea {
			box-sizing: border-box;
			background-color: var(--bgColor2);
			color: var(--fontColor);
		}
		
		body {
			-webkit-margin-before: 0;
			-webkit-margin-end: 0;
			-webkit-margin-after: 0;
			-webkit-margin-start: 0;
			-webkit-padding-before: 0;
			-webkit-padding-end: 0;
			-webkit-padding-after: 0;
			-webkit-padding-start: 0;
			width: 100%;
			height: calc(100dvh - 200px);
			margin: 0;
			padding: 0;
		}
		
		body {
			flex-direction: column;
			font-family: Arial, Verdana, Tahoma, serif;
			font-size: 1rem;
			font-weight: 400;
			row-gap: 1px;
			scrollbar-width: none;
		}
		
		body::-webkit-scrollbar{
			display: none;
		}
		
		button,
		button:hover {
			background: var(--inactiveColor);
		}
		
		body,
		button {
			text-align: left;
		}
		
		button {
			display: flex;
			flex-grow: 1;
			justify-content: center;
			height: 44px;
			text-transform: uppercase;
			align-items: center;
			font-size: 1em;
			border: 0;
			fill: var(--fontColor);
			padding: 10px;
			background-image: linear-gradient(to bottom, var(--inactiveGradient1), var(--inactiveGradient2));
			border-radius: 5px;
			-webkit-appearance: none;
		}
		
		button:hover,
		.buttonSelectedGrey {
			background-image: linear-gradient(to bottom, var(--inactiveGradient2), var(--inactiveGradient1));
			text-decoration: none;
		}
		
		.buttonActivationDelay {
			position: relative;
			filter: grayscale(100%);
			cursor: not-allowed;
			overflow: hidden;
		}
		
		.buttonActivationDelay .buttonText {
			opacity: 0;
			animation: showButtonText 5s forwards;
		}
		
		@keyframes showButtonText {
			0% {
				opacity: 0;
			}
			99% {
				opacity: 0;
			}
			100% {
				opacity: 1;
			}
		}
		
		.buttonActivationDelay::before {
			content: 'Please wait...';
			position: absolute;
			top: 0;
			left: 0;
			width: 100%;
			height: 100%;
			background: inherit;
			display: flex;
			align-items: center;
			justify-content: center;
			animation: hidePlaceholder 5s forwards;
		}
		
		@keyframes hidePlaceholder {
			0% {
				opacity: 1;
			}
			99% {
				opacity: 1;
			}
			100% {
				opacity: 0;
				display: none;
			}
		}
		
		.buttonActivationDelay {
			animation: activateButton 5s forwards;
		}
		
		@keyframes activateButton {
			0% {
				filter: grayscale(100%);
				cursor: not-allowed;
			}
			99% {
				filter: grayscale(100%);
				cursor: not-allowed;
			}
			100% {
				filter: none;
				cursor: pointer;
			}
		}
		
		.buttonActivationDelay::after {
			content: '';
			position: absolute;
			bottom: 0;
			left: 0;
			height: 4px;
			background-color: #00ff00;
			width: 0%;
			animation: progressBar 5s linear forwards;
		}
		
		@keyframes progressBar {
			from {
				width: 0%;
			}
			to {
				width: 100%;
			}
		}
		
		#contentArea_debug input,
		code {
			background-color: black;
			background-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>");
			background-repeat: no-repeat;
			background-position: top 8px right 8px;
			background-size: 20px 20px;
			border-radius: 12px;
			padding: 8px;
			padding-left: 16px;
			padding-right: 36px;
		}
		
		code {
			font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
			font-size: 77.5%;
			color: var(--accentGradient2);
			word-wrap: break-word;
			word-break: break-all;
			display: block;
			margin-top: 10px;
			white-space: pre-wrap;
		}
		
		code::after {
			content: "\a";
			white-space: pre;
		}
		
		button,
		h1,
		h2 {
			font-weight: 700;
		}
		
		h1 {
			margin-block-start: 0;
			margin-block-end: 0;
			margin-inline-start: 0;
			margin-inline-end: 0;
			font-size: 2rem;
			line-height: 1.3;
		}
		
		h2 {
			text-transform: uppercase;
			align-items: center;
			text-align: left;
			font-size: 1em;
			padding-left: 10px;
		}
		
		.bigLabel {
			text-transform: uppercase;
			align-items: center;
			text-align: left;
			font-size: 1em;
			padding-left: 10px;
			font-weight: 700;
			top: -10px;
			position: relative;
			vertical-align: middle;
		}
		
		input {
			color: var(--fontColor);
		}
		
		input[type=text],
		textarea {
			position: relative;
			display: inline-block;
			font-size: medium;
			width: 200px;
		}
		
		input[type=radio] {
			position: relative;
			display: inline-block;
			top: -3px;
		}
		
		#keylogStorage label,
		#payloadStorage label,
		#payloadRun label,
		label {
			position: relative;
			display: block;
			font-size: small;
			font-weight: bold;
			font-size: 1rem;
		}
		
		#payloadStorage label,
		#keylogStorage label, 
		#payloadRun label {
			font-size: 13px;
			font-weight: normal;
		}
		
		a:active,
		a:hover,
		a:link,
		a:visited,
		button,
		nav,
		nav a {
			color: var(--navColor);
		}
		
		nav {
			display: flex;
			flex-flow: row;
			justify-content: space-between;
			background-color: var(--accentColor);
			box-shadow: inset 20px 20px 30px var(--accentGradient1), inset -20px -20px 30px var(--accentGradient2);
			height: 50px;
			padding-left: .5em;
			margin-right: 10px;
			padding-right: 10px;
			width: calc(100vw - 20px);
		}
		
		nav a {
			text-decoration: none;
			padding-top: 2em;
			padding-bottom: 2em;
			margin-top: -2em;
			margin-bottom: -2em;
			padding-right: 5px;
		}
		
		#navbar-wifiIcon,
		nav,
		nav a,
		nav div,
		svg {
			vertical-align: middle;
		}
		
		nav span {
			height: 50px;
			vertical-align: top;
		}
		
		.left-side,
		.right-side,
		body {
			display: flex;
			justify-content: flex-start;
		}
		
		.left-side>div {
			margin-right: 0;
		}
		
		.right-side>div {
			margin-left: auto;
		}
		
		.statusLightActive {
			margin-left: 10px;
			width: 10px;
			height: 10px;
			background-color: var(--accentColor);
			border-radius: 50%;
			box-shadow: 5 5 5 5 var(--inactiveColor);
			transform: scale(1);
			animation: pulse 2s infinite;
		}
		
		#navbar-wifiIcon {
			position: relative;
			left: 10px;
			top: 0px;
			height: 40px;
			visibility: visible;
			width: 40px;
			padding-top: 0;
		}
		
		#wifiDetails {
			display: flex;
			flex-direction: column;
			margin-left: 6px;
			font-size: 0.7em;
			text-align: center;
			row-gap: 0px;
			position: relative;
			left: -33px;
			top: 34px;
		}
		
		.networkIn {
			fill: grey;
		}
		
		.networkOut {
			fill: grey;
		}
		
		@keyframes networkpulse {
			0% {
				fill: grey;
			}
			50% {
				fill: lightgrey;
			}
			100% {
				fill: grey;
			}
		}
		
		.networkActive {
			animation: networkpulse 0.5s;	
		}
		
		#wifiRTT {
			height: 1em;
		}
		
		svg {
			fill: var(--navColor);
		}
		
		content textarea {
			border-top: 0;
			border-bottom: 0;
			border-right: 0;
		}
		
		#payloadEditorFile {
			position: absolute;
			top: 0;
			right: 0;
			margin-right: 14px;
			background-color: rgba(0, 0, 0, .0);
			color: #fff;
			z-index: 0;
		}
		
		#contentArea_debug {
			color: var(--navColor);
		}
		
		#contentArea_debug input {
			color: var(--fontColor);
		}
		
		#footerKeylog,
		#footerPayload {
			padding-top: 0;
			position: relative;
			top: -5px;
		}
		
		#payloadLoadSelect,
		#payloadSelect {
			background-color: #000;
		}
		
		#helpExamples {
			width: calc(100% - 10px);
		}
		
		#helpExamples h3 {
			padding-bottom: 0;
			line-height: 20px;
			margin-block-end: 0;
			margin-bottom: 5px;
		}
		
		#helpExamples pre {
			color: var(--bgColor1);
			font-family: Arial, Verdana, Tahoma, serif;
			white-space: pre-wrap;
		}
		
		#keylogStorage,
		#payloadStorage,
		#payloadRun {
			position: relative;
			padding-left: 11px;
			padding-top: 0;
			margin-top: -2px;
			border-left-width: 4px;
			border-left-color: var(--accentGradient1);
		}
		
		#keylogStorage label,
		#payloadStorage label,
		#payloadRun label {
			top: 0;
			height: 2px;
			text-align: center;
		}
		
		#payloadStorage {
			padding-top: 7px;
		}
		
		#payloadRun {
			padding-top: 7px;
		}
		
		#keymap_row1,
		#keymap_row2,
		#keymap_row3,
		#keymap_row4,
		#keymap_row5,
		#keymap_row6 {
			display: grid;
			justify-content: space-between;
			height: 60px;
			gap: 5px;
			padding: 5px;
		}
		
		#keymap_row1,
		#keymap_row4 {
			grid-template-columns: repeat(13, 1fr);
		}
		
		#keymap_row2,
		#keymap_row3 {
			grid-template-columns: repeat(14, 1fr);
		}
		
		#keymap_row5 {
			grid-template-columns: repeat(12, 1fr);
		}
		
		#keymap_row6 {
			grid-template-columns: repeat(7, 1fr);
		}
		
		#keymapViewer {
			position: absolute;
			vertical-align: middle;
			float: left;
			top: calc((var(--app-height) - 448px)/2);
			width: 90vw;
			height: 448px;
			border-radius: 5px;
			background-color: var(--bgColor2);
			-webkit-box-shadow: 5px 5px 15px 5px var(--inactiveGradient2);
			box-shadow: 5px 5px 15px 5px var(--inactiveGradient2);
			color: var(--fontColor);
		}
		
		#keymapViewerLabel {
			text-align: left;
			color: var(--navColor);
			padding-left: 5px;
			padding-top: 5px;
		}
		
		#keymapViewerSelect {
			width: 120px;
		}
		
		#partitionEditor,
		#partitionApplied {
			position: absolute;
			vertical-align: middle;
			float: left;
			width: 280px;
			height: auto;
			top: calc((var(--app-height) - 448px)/2);
			border-radius: 5px;
			background-color: var(--bgColor2);
			-webkit-box-shadow: 5px 5px 15px 5px var(--inactiveGradient2);
			box-shadow: 5px 5px 15px 5px var(--inactiveGradient2);
			color: var(--navColor);
			padding-left: 5px;
			padding-top: 5px;
		}
		
		#loadingScreen {
			position: absolute;
			vertical-align: middle;
			text-align: center;
			float: left;
			width: 240px;
			height: 160px;
			top: calc((var(--app-height) - 448px)/2);
			border-radius: 5px;
			background-color: var(--bgColor2);
			-webkit-box-shadow: 5px 5px 15px 5px var(--inactiveGradient2);
			box-shadow: 5px 5px 15px 5px var(--inactiveGradient2);
			color: var(--navColor);
			padding-left: 5px;
			padding-top: 5px;
		}
		
		.loader {
			position: relative;
			align-content: center;
			border: 16px solid var(--bgColor1);
			border-top: 16px solid var(--accentColor);
			border-radius: 50%;
			width: 80px;
			height: 80px;
			left: 70px;
			top: 10px;
			animation: spin 2s linear infinite;
		}
		
		#logo {
			height: 50px;
			padding-top: 0;
			background-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>");
		}
		
		#ico_settings {
			background-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>");
		}
		
		#ico_about {
			background-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>");
		}
		
		#ico_help {
			background-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>");
		}
		
		#ico_syslog {
			background-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>");
		}
		
		#ico_payload {
			background-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>");
		}
		
		#ico_keylog {
			background-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>");
		}
		
		#ico_keylogDownload {
			background-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>");
		}
		
		#ico_payloadLoad {
			background-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>");
		}
		
		#ico_keylogLiveLog {
			background-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>");
		}
		
		#ico_keylogSave,
		#ico_payloadSave {
			margin-right: 8px;
			background-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>");
		}
		
		#ico_payloadBuild {
			background-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>");
		}
		
		#ico_keylogRun,
		#ico_payloadRun {
			background-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>");
		}
		
		#ico_fsnobrick,
		#ico_fsnoeat,
		#ico_fsnofire,
		#ico_fsnowater {
			width: 80px;
			background-repeat: no-repeat;
		}
		
		#fsIcons {
			display: grid;
			grid-template-columns: repeat(2, minmax(0, 250px));
			grid-template-rows: repeat(2, minmax(0, 150px));
			grid-gap: 40px;
			justify-content: center;
			text-align: center;
		}
		
		#fsIcons svg {
			width: 100%;
			height: 100%;
		}
		
		#ico_settings,
		#ico_about,
		#ico_help {
			margin-top: 8px;
			width: 34px;
			height: 34px;
			background-repeat: no-repeat;
		}
		
		#ico_keylog,
		#ico_payload,
		#ico_syslog {
			width: 34px;
			height: 34px;
			background-repeat: no-repeat;
		}
		
		#ico_keylog {
			height: 28px;
		}
		
		#ico_syslog {
			height: 30px;
		}
		
		#ico_keylogDownload,
		#ico_keylogLiveLog,
		#ico_keylogRun,
		#ico_keylogSave,
		#ico_payloadBuild,
		#ico_payloadLoad,
		#ico_payloadRun,
		#ico_payloadSave,
		#ico_wifi {
			width: 32px;
			height: 32px;
			background-repeat: no-repeat;
		}
		
		#ico_wifi {
			width: 35px;
			height: 35px;
		}
		
		#modelText {
			position: absolute;
			left: 45px;
			top: 2px;
			text-transform: uppercase;
		}
		
		#modal {
			top: calc((var(--app-height) - 320px)/2);
			height: 320px;
		}
		
		#modalLabel {
			text-align: left;
			color: var(--navColor);
			padding-left: 5px;
			padding-top: 5px;
		}
		
		#modalMessage {
			color: var(--fontColor);
			padding-left: 5px;
			padding-right: 5px;
			width: 100%;
		}
		
		#modalButtons {
			display: flex;
			flex-grow: auto;
			position: absolute;
			bottom: 5px;
			right: 5px;
			width: calc(100% - 10px);
		}
		
		#navbarAlerts {
			margin-top: 8px;
			top: 0px;
			display: inline-block;
			text-align: center;
			align-content: center;
			max-width: 180px;
			min-width: 10px;
			line-height: normal;
		}
		
		#navbar-Links svg {
			padding-top: 7px;
		}
		
		#payloadLoadMenu,
		#payloadSaveMenu,
		#payloadFileMenu {
			position: fixed;
			height: 240px;
			bottom: 57px;
			left: 10px;
			padding: 10px;
			background-color: var(--inactiveGradient2);
			border-top-left-radius: 10px;
			border-top-right-radius: 10px;
			background-image: -webkit-linear-gradient(top, var(--inactiveGradient1), var(--inactiveGradient2));
			background-image: -moz-linear-gradient(top, var(--inactiveGradient1), var(--inactiveGradient2));
			background-image: -ms-linear-gradient(top, var(--inactiveGradient1), var(--inactiveGradient2));
			background-image: -o-linear-gradient(top, var(--inactiveGradient1), var(--inactiveGradient2));
			background-image: linear-gradient(to bottom, var(--inactiveGradient1), var(--inactiveGradient2));
		
		}
		
		#fileMenuFlex {
			display: flex;
			justify-content: left;
		}
		
		#columnLeft,
		#columnRight {
			display: flex;
			flex-direction: column;
			gap: 10px;
			/* adjust as needed */
		}
		
		#columnLeft,
		#columnLeft input {
			width: 246px;
		}
		
		#columnRight {
			padding-left: 5px;
			width: 100px;
		}
		
		.payloadList {
			padding: 10px;
			font-size: 16px;
			border: 1px solid #ccc;
			border-radius: 4px;
			box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
			width: 100%;
		}
		
		.payloadList:focus {
			outline: 0;
			border-color: #66afe9;
			box-shadow: 0 0 0 3px rgba(102, 175, 233, .6);
		}
		
		select {
			padding: 8px;
			font-size: 16px;
			border: 1px solid #ccc;
			border-radius: 4px;
			box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
			appearance: none;
			-webkit-appearance: none;
			-moz-appearance: none;
			background-color: rgba(0, 0, 0, .075);
			color: white;
			appearance: none;
			-webkit-appearance: none;
			-moz-appearance: none;
		}
		
		select:focus {
			outline: 0;
			border-color: #66afe9;
			box-shadow: 0 0 0 3px rgba(102, 175, 233, .6);
		}
		
		#deviceSelect {
			height: 40px;
			padding-top: 7px;
		}
		
		#sidebar {
			max-height: calc(var(--app-height) - 50px);
			position: absolute;
			top: 50px;
			right: 0;
			width: 320px;
			margin: 0;
			overflow: auto;
			background-color: var(--sidebarGradient);
			-webkit-box-shadow: -6px 0 15px 5px var(--sidebarGradient);
			box-shadow: -6px 0 15px 5px var(--sidebarGradient);
			height: var(--app-height);
		}
		
		#sidebar,
		#sidebar a:active,
		#sidebar a:link,
		#sidebar a:visited {
			color: var(--navColor);
		}
		
		#releaseTeam a,
		#sidebar a:hover,
		.about-twitter a {
			color: var(--accentColor);
		}
		
		#sidebar input {
			color: var(--fontColor);
		}
		
		#sidebar_about,
		#sidebar_help,
		#sidebar_settings {
			padding-left: 10px;
		}
		
		#sidebar_settings_subnav {
			width: calc(100% - 10px);
			padding-left: 0;
		}
		
		#subnav button {
			border-bottom-left-radius: 0;
			border-bottom-right-radius: 0;
			margin-inline: 1px;
		}
		
		.about-role {
			margin-bottom: 20px;
		}
		
		.about-section-credits {
			margin-bottom: 0;
			columns: 2;
		}
		
		.about-team-name {
			font-size: 14px;
			color: var(--bgColor1);
			margin-right: 10px;
		}
		
		.about-role,
		.about-twitter {
			font-size: 12px;
			color: var(--navColor);
		}
		
		.button-container {
			position: relative;
			left: 0;
			top: 0;
			transform: translate(0, 0);
		}
		
		.buttonSelected,
		.buttonSelected:hover,
		.buttonRunning,
		.buttonRunning:hover {
			background: var(--accentColor);
			background-image: -webkit-linear-gradient(top, var(--accentGradient1), var(--accentGradient2));
			background-image: -moz-linear-gradient(top, var(--accentGradient1), var(--accentGradient2));
			background-image: -ms-linear-gradient(top, var(--accentGradient1), var(--accentGradient2));
			background-image: -o-linear-gradient(top, var(--accentGradient1), var(--accentGradient2));
			background-image: linear-gradient(to bottom, var(--accentGradient1), var(--accentGradient2));
		}
		
		.buttonSelected:hover,
		.buttonRunning:hover {
			background-image: -webkit-linear-gradient(top, var(--accentGradient1), var(--accentGradient2));
			background-image: -moz-linear-gradient(top, var(--accentGradient1), var(--accentGradient2));
			background-image: -ms-linear-gradient(top, var(--accentGradient1), var(--accentGradient2));
			background-image: -o-linear-gradient(top, var(--accentGradient1), var(--accentGradient2));
			background-image: linear-gradient(to bottom, var(--accentGradient1), var(--accentGradient2));
		}
		
		.close,
		.closeModal {
			color: var(--fontColor);
			float: right;
			font-size: 2rem;
			font-weight: 700;
			line-height: 1;
			text-shadow: 0 1px 0 var(--navColor);
			background-color: var(--bgColor3);
			border-color: var(--bgColor3);
			position: fixed;
			right: 0;
			top: 50px;
		}
		
		.closeModal {
			right: 0px;
			top: 0;
			position: absolute;
		}
		
		.contentFullscreen {
			width: calc(100%);
		}
		
		.contentSidebar {
			width: calc(100% - 325px);
		}
		
		.footerSidebar {
			width: calc(100% - 365px);
		}
		
		.footerFullscreen {
			width: calc(100% - 40px);
		}
		
		.editor-wrapper {
			display: flex;
			position: relative;
		}
		
		.editor div {
			position: relative;
			white-space: pre-wrap;
		}
		
		.debug-info {
			position: absolute;
			top: 0;
			right: 0;
			margin-right: 14px;
			background-color: rgba(0, 0, 0, .5);
			color: #fff;
			font-size: 12px;
			z-index: 10;
		}
		
		.editor {
			background-color: var(--bgColor3);
			border-color: var(--bgColor2);
			color: var(--fontColor);
			border-left-width: 4px;
			font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
			font-size: 16px;
			font-weight: 400;
			line-height: 1.3;
			outline: 0;
			overflow-y: auto;
			margin-left: 11px;
			height: calc(100dvh - 195px);
			padding-bottom: 0;
			border-bottom: 0;
		}
		
		.editor div {
			display: block;
		}
		
		.editorKeylog {
			padding-bottom: 0;
			border-bottom: 0;
			height: calc(100dvh - 195px);
			border-left-color: var(--accentGradient1);
		}
		
		.editorKeylog,
		.editorSyslog,
		.editorc2log,
		.editorPayload {
			background-color: var(--bgColor3);
			border-color: var(--bgColor2);
			color: var(--fontColor);
			display: inline-flex;
			margin-left: 11px;
			width: calc(100% - 22px);
			border-left-width: 0px;
			font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
			font-size: 16px;
			font-weight: 400;
			line-height: 1.3;
		}
		
		.editorSyslog,
		.editorc2log {
			height: calc(100dvh - 127px);
			border-left-color: var(--inactiveGradient1);
		}
		
		.fccNotice {
			font-size: 12px;
		}
		
		.footer {
			width: calc(100% - 22px);
			padding-top: 0;
		}
		
		.headingModal {
			height: 40px;
		}
		
		.helpHeader {
			background: var(--inactiveColor);
			background-image: -webkit-linear-gradient(top, var(--inactiveGradient1), var(--inactiveGradient2));
			background-image: -moz-linear-gradient(top, var(--inactiveGradient1), var(--inactiveGradient2));
			background-image: -ms-linear-gradient(top, var(--inactiveGradient1), var(--inactiveGradient2));
			background-image: -o-linear-gradient(top, var(--inactiveGradient1), var(--inactiveGradient2));
			background-image: linear-gradient(to bottom, var(--inactiveGradient1), var(--inactiveGradient2));
			-webkit-border-radius: 5;
			-moz-border-radius: 5;
			border-radius: 5px;
			color: var(--navColor);
			width: 100%;
		}
		
		.hidden {
			display: none;
		}
		
		.key {
			background-color: var(--bgColor3);
			display: grid;
			grid-template-columns: 50% 50%;
			grid-row: auto auto;
		}
		
		.navLabel {
			vertical-align: middle;
		}
		
		.partitionEditorFieldWrapper {
			display: flex;
		}
		
		.partitionEditorFieldLabel {
			width: 140px;
		}
		
		.progressBar {
			color: var(--fontColor);
			background-color: var(--bgColor3);
			font-size: 10px;
		}
		
		.progressFilled,
		.progressFilled:after,
		.progressFilled:before {
			padding: .01em;
			color: var(--fontColor);
			background-color: var(--accentGradient1);
			text-align: center;
			margin-bottom: 4px;
		}
		
		.submitButton {
			position: relative;
			display: grid;
			font-size: large;
			color: var(--navColor);
			background-color: inherit;
			border-color: var(--bgColor2);
		}
		
		.subnav {
			display: flex;
			flex-grow: auto;
			padding-left: 10px;
			padding-top: 10px;
			margin-bottom: 0;
			padding-bottom: 0;
		}
		
		.footer button {
			border-top-left-radius: 0;
			border-top-right-radius: 0;
			margin-inline: 1px;
		}
		
		.subnav svg {
			padding-right: 10px;
		}
		
		.subnav {
			width: calc(100% - 20px);
		}
		
		.subnavSidebar {
			width: calc(100% - 325px - 20px);
		}
		
		.toast {
			-webkit-animation: slide .5s forwards;
			-webkit-animation-delay: 4s;
			animation: slide .5s forwards;
			animation-delay: 4s;
			background: var(--bgColor3);
			border-radius: 10px;
			box-shadow: inset 20px 20px 30px var(--inactiveGradient1), inset -20px -20px 30px var(--inactiveGradient2);
			color: var(--navColor);
			display: inline-block;
			float: left;
			font-size: 1rem;
			font-weight: 700;
			right: 350px;
			max-height: 120px;
			min-height: 48px;
			overflow: hidden;
			padding-left: 5px;
			position: absolute;
			text-overflow: clip;
			text-transform: uppercase;
			top: 1px;
			white-space: wrap;
			width: 320px;
			z-index: 1;
		}
		
		.toggle-button {
			background: var(--bgColor1);
			width: 80px;
			height: 20px;
			border-radius: 30px;
			padding: 5px;
			cursor: pointer;
			transition: all 300ms ease-in-out;
		}
		
		.toggle-button>.inner-circle {
			background: var(--navColor);
			width: 20px;
			height: 20px;
			border-radius: 50%;
			transition: all 300ms ease-in-out;
		}
		
		.toggle-button.active {
			background: var(--toggleOnColor);
		}
		
		.toggle-button.active>.inner-circle {
			margin-left: 60px;
		}
		
		.updateButton,
		.updateButtonLarge {
			position: relative;
			display: inline-block;
			color: var(--navColor);
			background-color: inherit;
			border-color: var(--bgColor2);
			height: 24px;
			top: 0;
			left: -6px;
			padding-top: 3px;
		}
		
		.updateButtonLarge {
			height: 40px;
			top: -4px;
			left: -4px;
			margin-left: 0;
			align-items: center;
			justify-content: center;
			text-align: center;
		}
		
		.buttonMergeLeft {
			border-top-left-radius: 0;
			border-bottom-left-radius: 0;
		}
		
		.buttonMergeRight {
			border-top-right-radius: 0;
			border-bottom-right-radius: 0;
		}
		
		.visible {
			visibility: visible;
		}
		
		.wifiSignalOn {
			fill: var(--navColor);
		}
		
		dialog {
			width: 60%;
			height: fit-content;
			background: #111;
			color: var(--navColor);
			border-radius: 10px;
			border-color: var(--bgColor2);
		}
		
		.close-button {
			position: absolute;
			top: 10px;
			right: 10px;
			border: none;
			background: transparent;
			font-size: 20px;
			cursor: pointer;
		}
		
		#color-container {
			position: relative;
			width: 100%;
			height: 40px;
		}
		
		#color-slider {
			position: absolute;
			top: 12px;
			width: 200px;
			margin: 0;
			z-index: 1;
		}
		
		#color-box {
			position: absolute;
			top: 10px;
			width: 200px;
			height: 20px;
			background: linear-gradient(to right, hsl(0, 70%, 50%), hsl(120, 70%, 50%), hsl(240, 70%, 50%), hsl(359, 70%, 50%));
			border-radius: 10px;
		}
		
		#apply-color {
			position: absolute;
			top: 0px;
			right: 20px;
		}
		
		#firstTimeSetupAgree, #firstTimeSetupAgreePage2, #firstTimeSetupAgreePage3 {
			bottom: 20px;
			left: 20px;
			width: calc(100% - 40px);
		}
		
		#firstTimeSetup, ##firstTimeSetupPage2, #firstTimeSetupPage3 {
			position: fixed;
			vertical-align: middle;
			top: 50px;
			width: 250px;
			height: 500px;
			border-radius: 5px;
			background-color: var(--bgColor2);
			-webkit-box-shadow: 5px 5px 15px 5px var(--inactiveGradient2);
			box-shadow: 5px 5px 15px 5px var(--inactiveGradient2);
			color: var(--navColor);
			padding-left: 5px;
			padding-top: 5px;
			z-index: 1000;
			
			&::backdrop {
				background-color: rgba(0, 0, 0, 0.5);
				backdrop-filter: blur(5px);
				-webkit-backdrop-filter: blur(5px);
			}
			
			h2 {
				text-align: center;
			}
		}
		
		@media (max-width:700px) {
			#navbarAlerts {
				display: none;
			}
			.toast {
				width: 320px;
				right: 150px;
			}
		}
		
		@media (max-width:550px) {
			.toast {
				width: 320px;
				right: 10px;
			}
		
			#firstTimeSetup, #firstTimeSetupPage2, #firstTimeSetupPage3 {
				position: absolute;
				vertical-align: middle;
				float: left;
				top: 50px;
				width: 90%;
				height: 90dvh;
				border-radius: 5px;
				background-color: var(--bgColor2);
				-webkit-box-shadow: 5px 5px 15px 5px var(--inactiveGradient2);
				box-shadow: 5px 5px 15px 5px var(--inactiveGradient2);
				color: var(--navColor);
				padding-left: 5px;
				padding-top: 5px;
			}
		}
		
		@media (max-width:530px) {
			body {
				height: var(--app-height)
			}
		
			.navLabel {
				display: none
			}
		
			.navIcon {
				display: none
			}
		
			.contentFullscreen {
				width: calc(100%);
			}
		
			.contentFooter {
				width: calc(100% - 40px);
			}
		
			.contentSidebar {
				display: none
			}
		
			.subnavFullscreen {
				width: calc(100% - 20px)
			}
		
			.subnavSidebar {
				display: none
			}
		
			#sidebar {
				width: 100%
			}
		
			#navbarAlerts {
				display: none;
			}
		}
		
		@media (max-width:400px) {
			.toast {
				width: 100%;
				right: 0px;
			}
		}
		
		@media (max-width:1px) {
			nav {
				display: none
			}
		
			#content,
			#subnav,
			.editor,
			.footer {
				padding: 0;
				margin: 0;
				width: 100vw;
			}
		
			.editor {
				height: calc(100dvh - 200px);
			}
		}
		
		@keyframes spin {
			0% {
				transform: rotate(0deg);
			}
		
			to {
				transform: rotate(360deg);
			}
		}
		
		@keyframes pulse {
			0% {
				transform: scale(0.95);
				box-shadow: 0 0 0 0 var(--inactiveColor);
			}
		
			70% {
				transform: scale(0.80);
				box-shadow: 0 0 0 3px var(--inactiveGradient2);
			}
		
			100% {
				transform: scale(0.95);
				box-shadow: 0 0 0 0 var(--inactiveColor);
			}
		}
		
		.tooltip,
		.input-wrapper {
			position: relative;
		}
		
		.tooltiptext {
			position: absolute;
			visibility: hidden;
			background-color: red;
			color: #fff;
			font-size: 0.8em;
			text-align: center;
			border-radius: 6px;
			padding: 5px 0;
			z-index: 1;
			top: 120%;
			left: 0;
			border-width: 5px;
			border-style: solid;
			border-color: transparent transparent red transparent;
		}
		
		.tooltiptext::after {
			content: "";
			position: absolute;
			bottom: 100%;
			margin-left: -240px;
			border-width: 15px;
			border-style: solid;
			border-color: transparent transparent red transparent;
		}
		
		.arrowTop {
			top: -10px;
		}
		
		.arrowTop::after {
			top: 100%;
			border-color: red transparent transparent transparent;
		}
		
		.editor-wrapper {
			background-color: var(--bgColor3);
			width: calc(100% - 22px);
			left: 11px;
			margin-left: 0px;
			padding-left: 0px;
			margin-bottom: -8px;
			padding-bottom: 20px;
			overflow: scroll;
			-ms-overflow-style: none;  /* IE and Edge */
			scrollbar-width: none;  /* Firefox */
		}
		
		.editor-wrapper::-webkit-scrollbar {
		  display: none;
		}
		
		#lineNumbers {
			width: 30px;
			left: 0px;
			background-color: var(--inactiveGradient2);
			padding: 5px;
			text-align: right;
			line-height: 1.5em;
			line-height-step: 1.5em;
			font-family: monospace;
			position: absolute;
			height: calc(100% - 30px);
			overflow: scroll;
			-ms-overflow-style: none;  /* IE and Edge */
			scrollbar-width: none;  /* Firefox */
		}
		
		#lineNumbers::-webkit-scrollbar {
		  display: none;
		}
		
		#payload {
			padding: 5px;
			left: 45px;
			margin-left: 0px;
			border: none;
			outline: none;
			line-height: 1.5em;
			line-height-step: 1.46em;
			font-family: monospace;
			white-space: pre;
			background: transparent;
			position: relative;
			width: calc(100% - 63px);
			overflow: scroll;
			-ms-overflow-style: none;  /* IE and Edge */
			scrollbar-width: none;  /* Firefox */
		}
		
		#payload::-webkit-scrollbar {
		  display: none;
		}
		
		#labipaddress {
			color: black;
		}
		
		.line-red {
			background-color: red;
			width: 30px;
			padding-right: calc(100vw - 60px);
		}
		
		.line-orange {
			background-color: orange;
			width: 30px;
			padding-right: calc(100vw - 55px);
		}
		
		.line-yellow {
			background-color: yellow;
			width: 30px;
			padding-right: calc(100vw - 55px);
		}
		
		.line-green {
			background-color: green;
			width: 30px;
			padding-right: calc(100vw - 55px);
		}
		
		.line-blue {
			background-color: blue;
			width: 30px;
			padding-right: calc(100vw - 55px);
		}
		
		.line-purple {
			background-color: purple;
			width: 30px;
			padding-right: calc(100vw - 55px);
		}
		
		
		.apply-effect {
			animation: glowing 1s;
			-webkit-animation: glowing 1s;
			box-shadow: 0 0 0px red, 0 0 0px red, 0 0 0px red, 0 0 0px red;
			-webkit-box-shadow: 0 0 0px red, 0 0 0px red, 0 0 0px red, 0 0 0px red;
		}
		
		.glowing-effect {
			animation: glowing 1s infinite;
			-webkit-animation: glowing 1s infinite;
			box-shadow: 0 0 1px red, 0 0 3px red, 0 0 5px red, 0 0 7px red;
			-webkit-box-shadow: 0 0 1px red, 0 0 3px red, 0 0 5px red, 0 0 7px red;
		}
		
		@keyframes glowing {
			0% {
				box-shadow: 0 0 4px red, 0 0 6px red, 0 0 8px red, 0 0 10px red;
			}
		
			50% {
				box-shadow: 0 0 8px red, 0 0 10px red, 0 0 12px red, 0 0 14px red;
			}
		
			100% {
				box-shadow: 0 0 4px red, 0 0 6px red, 0 0 8px red, 0 0 10px red;
			}
		}
		
		@-webkit-keyframes glowing {
			0% {
				-webkit-box-shadow: 0 0 4px red, 0 0 6px red, 0 0 8px red, 0 0 10px red;
			}
		
			50% {
				-webkit-box-shadow: 0 0 8px red, 0 0 10px red, 0 0 12px red, 0 0 14px red;
			}
		
			100% {
				-webkit-box-shadow: 0 0 4px red, 0 0 6px red, 0 0 8px red, 0 0 10px red;
			}
		}
	</style>
</head>
<body>
	<nav>
		<div class="left-side">
			<div id="logoBar">
				<svg id="logo" xmlns="http://www.w3.org/2000/svg" xml:space="preserve" class="iconColor" viewBox="110 25 82 50"></svg>
				<span id="modelText" class="visible"></span>
			</div>
			<div id="navbar-wifiIcon">
				<svg id="ico_wifi" xml:space="preserve" viewBox="0 0 24.38 30">
					<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"/>
					<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"/>
				</svg>
			</div>
			<div id="wifiDetails">
				<span id="wifiRTT"></span>
			</div>
			<div id="deviceSelect"></div>
			<div id="navbarAlerts"></div>
		</div>
		<div class="right-side">
			<div id="navbar-Links">
				<a id="navbar-Links-settings" data-target="#modal-settings" href="#" onclick="sidebarAreaToggle('settings')">
					<svg id="ico_settings" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40"></svg>
					<span class="navLabel">SETTINGS</span>
				</a>
				<a id="navbar-Links-about" data-target="#modal-about" href="#" onclick="sidebarAreaToggle('about')">
					<svg id="ico_about" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40"></svg>
					<span class="navLabel">ABOUT</span>
				</a>
				<a id="navbar-Links-help" data-target="#modal-help" href="#" onclick="sidebarAreaToggle('help')">
					<svg id="ico_help" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40"></svg>
					<span class="navLabel">HELP</span>
				</a>
			</div>
		</div>
	</nav>
	<div id="subnav" class="subnav">
		<button id="contentSubNav_syslog" onclick="contentAreaToggle('syslog')">
			<svg id="ico_syslog" xmlns="http://www.w3.org/2000/svg" height="24" width="24" viewBox="0 0 40 40" class="iconColor navIcon"></svg>
			<span>log</span>
			<span id="syslogStatusLight">&nbsp;</span>
		</button>
		<button id="contentSubNav_c2log" onclick="contentAreaToggle('c2log')" class="hidden">
			<svg id="ico_syslog" xmlns="http://www.w3.org/2000/svg" height="24" width="24" viewBox="0 0 40 40" class="iconColor navIcon"></svg>
			<span>c2log</span>
			<span id="c2logStatusLight">&nbsp;</span>
		</button>
		<button id="contentSubNav_keylog" class="keylog hidden" onclick="contentAreaToggle('keylog')">
			<svg id="ico_keylog" xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 40 40" class="iconColor navIcon"></svg>
			<span>Keylog</span>
			<span id="keylogStatusLight">&nbsp;</span>
		</button>
		<button id="contentSubNav_payload" onclick="contentAreaToggle('payload')" class="buttonSelected hidden">
			<svg id="ico_payload" xmlns="http://www.w3.org/2000/svg" data-keyloggingOn height="24" viewBox="0 0 40 40" class="iconColor navIcon"></svg>
			<span>Payload</span>
			<span id="payloadStatusLight">&nbsp;</span>
		</button>
		<button id="contentSubNav_debug" class="hidden" onclick="contentAreaToggle('debug')">Debug</button>
	</div>
	<div id="sidebar" class="hidden">
		<div id="sidebar_settings" class="hidden">
			<button class="close" type="button" onclick="sidebarAreaToggle('close')">
				<span>&times;</span>
			</button>
			<h1>Settings</h1>
			<div id="sidebar_settings_subnav" class="subnav">
				<button id="sidebarNav_queue" onclick="sidebarSettingsAreaToggle('queue')" class="hidden">Queue</button>
				<button id="sidebarNav_net" onclick="sidebarSettingsAreaToggle('net')" class="buttonSelected">Net</button>
				<button id="sidebarNav_config" onclick="sidebarSettingsAreaToggle('config')">USB</button>
				<button id="sidebarNav_debug" onclick="sidebarSettingsAreaToggle('debug')">GENERAL</button>
			</div>
			<div id="sidebar_settings_queue" class="hidden">
				<label for="data">Add CMD to Queue:</label>
				<input type="text" id="data" name="data">
				<button id="modifyQueueC2" onclick="applyEffect(this);">Submit</button>
				<br>
				<label for="queueContactRate">Contact Rate:</label>
				<input type="text" id="queueContactRate" name="queueContactRate">
				<label for="queuePollRate">Poll Rate:</label>
				<input type="text" id="queuePollRate" name="queuePollRate">
				<label for="queueFastRate">Fast Rate:</label>
				<input type="text" id="queueFastRate" name="queueFastRate">
				<button id="queuePollUpdate" onclick="applyEffect(this); changePollRate();">Update Queue Polling Rates</button>
				<table id="cmdQueueTable">
					<thead>
						<tr>
							<th>Command</th>
							<th>Action</th>
						</tr>
					</thead>
					<tbody></tbody>
				</table>
				<br>
				<button id="deleteAllButton" onclick="applyEffect(this);">Delete All in Queue</button>
				<br>
				<button id="queryDeviceStatus" onclick="applyEffect(this);">Query Device Status</button>
				<br>
				<div class="button-container">
					<label for="c2logAlwaysShow">Always Show Last Check-In</label>
					<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`); };">
						<div class="inner-circle"></div>
					</div>
				</div>
				<div class="button-container">
					<label for="c2logAbridge">C2 Log Abridge</label>
					<div id="c2logAbridge" class="toggle-button" onclick="document.getElementById(`c2logAbridge`).classList.toggle('active'); if(this.classList.contains('active')) { c2logAbridge = '1' } else { c2logAbridge = '0' };">
						<div class="inner-circle"></div>
					</div>
				</div>
				<br>
				<button id="clearLogsButton" onclick="applyEffect(this);">
					Clear &nbsp;
					<data id="clearLogsAlias"></data>
					&nbsp; Logs
				</button>
				<button id="clearAllLogsButton" onclick="applyEffect(this);">Clear All Logs</button>
			</div>
			<div id="sidebar_settings_net">
				<div id="wifi-Settings">
					<h3>WiFi Settings</h3>
					<label for="device-WIFIMode">WiFi Mode</label>
					<data id="device-WIFIMode" class="hidden"></data>
					<input type="radio" id="CW0" name="WIFIMode" value="AP">
					<data>AP (Broadcast a new AP)</data>
					<br>
					<input type="radio" id="CW1" name="WIFIMode" value="Station">
					<data>Station (Connect to Infrastructure)</data>
					<div class="input-wrapper">
						<label for="device-WIFISSID">SSID</label>
						<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')">
						<span class="tooltiptext" id="tooltipwifissid"></span>
					</div>
					<div class="input-wrapper">
						<label for="device-WIFIPassword">Password (8-63 Characters, 0 = Open)</label>
						<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')">
						<span class="tooltiptext" id="tooltipwifipass"></span>
					</div>
					<label for="device-WIFIChannel">Channel</label>
					<input type="text" id="device-WIFIChannel" name="device-WIFIChannel" spellcheck="false">
					<label for="device-CustomMACAddress">
						Custom MAC (Default:
						<data id="device-HardwareMACAddress"></data>
						)
					</label>
					<input type="text" id="device-CustomMACAddress" name="device-CustomMACAddress" spellcheck="false">
					<label for="device-IPAddress">Device IP Address</label>
					<span type="text" id="device-IPAddress"></span>
					<label for="device-name">Device Name</label>
					<input type="text" id="device-name" name="device-name" spellcheck="false">
					<button id="update-WIFISettings" class="submitButton" onclick="applyEffect(this); updateWifiSettings();">Change Settings</button>
				</div>
				<div id="c2-Settings" class="c2">
					<h3>C2 Settings</h3>
					<p>
						C2 is currently in beta. Click 
						<a href="https://github.com/O-MG/O.MG-Firmware/wiki/C2">https://github.com/O-MG/O.MG-Firmware/wiki/C2</a>
						 to learn more.
					</p>
					<label for="c2config">
						C2 Config - 
						<span id="c2configApplied"></span>
					</label>
					<input type="text" id="c2config" name="c2config" spellcheck="false">
					<button id="update-C2Settings" class="submitButton" onclick="applyEffect(this); updateC2Settings()">Change Settings</button>
					<div class="button-container">
						<label for="c2status">C2</label>
						<div id="c2status" class="toggle-button" onclick="document.getElementById(`c2status`).classList.toggle('active'); if(this.classList.contains('active')) { sendMessage(`C2Start`) } else { sendMessage(`C2Stop`); };">
							<div class="inner-circle"></div>
						</div>
					</div>
					<button id="button-C2Wipe" class="submitButton hidden" onclick="applyEffect(this); sendMessage('C2Wipe')">Erase C2 Config</button>
				</div>
			</div>
			<div id="sidebar_settings_config" class="hidden">
				<h3>USB ID Defaults</h3>
				<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>
				<br>
				<label for="device-USBVID">USB Vendor ID</label>
				<input type="text" id="device-USBVID" name="device-USBVID">
				<label for="device-USBPID">USB Product ID</label>
				<input type="text" id="device-USBPID" name="device-USBPID">
				<label for="device-USBMAN">USB Manufacturer</label>
				<input type="text" id="device-USBMAN" name="device-USBMAN">
				<label for="device-USBPRO">USB Product</label>
				<input type="text" id="device-USBPRO" name="device-USBPRO">
				<label for="device-USBSER">USB Serial Number</label>
				<input type="text" id="device-USBSER" name="device-USBSER">
				<button class="submitButton" id="update-USBIDs" onclick="applyEffect(this); updateUSBIDs();">Update All USB IDs at once</button>
				<div class="hidx">
					<h4>USB Overclock</h4>
					<select name="experimentalSpeed" id="experimentalSpeed" onchange="sendMessage(`CTSet\tusbinterval\t${this.value}`);sendMessage(`CTList`);">
						<option value="10">100%</option>
						<option value="1">1000%</option>
					</select>
					<button class="submitButton" id="experimentalSpeed" onclick="applyEffect(this); sendMessage(`CR1`)">Apply + Reboot</button>
				</div>
				<div id="keylog-Settings" class="keylog">
					<h3>Keylog Settings</h3>
					<input class="keylyg" type="text" placeholder="US" data-keylogger-map-region id="keylogKeymapSelect" list="keymap-list">
					<button id="update-keylogLocale" class="updateButton" onclick="applyEffect(this); generateKeymap(document.getElementById('keylogKeymapSelect').value)">Update</button>
					<div class="button-container hidden">
						<label for="keylogDisplayUnmappedValues">Display Unmapped Values</label>
						<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`); };">
							<div class="inner-circle"></div>
						</div>
					</div>
				</div>
				<div id="hidx-Settings" class="hidx">
					<h3>HIDX StealthLink Settings</h3>
					<div class="button-container">
						<label for="hidxstatusfile">HIDX File</label>
						<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`); };">
							<div class="inner-circle"></div>
						</div>
					</div>
					<div class="button-container">
						<label for="hidxstatustcp">HIDX TCP</label>
						<div id="hidxstatustcp" class="toggle-button" onclick="document.getElementById(`hidxstatustcp`).classList.toggle('active'); if(this.classList.contains('active')) { sendMessage(`CHStart\tT`) } else { sendMessage(`CHStop`); };">
							<div class="inner-circle"></div>
						</div>
					</div>
					<label for="hidxhost">Listener Address</label>
					<input type="text" id="hidxhost" name="hidxhost" spellcheck="false">
					<label for="hidxport">Listener Port</label>
					<input type="text" id="hidxport" name="hidxport" spellcheck="false">
					<label for="hidxboot">AutoStart on Boot</label>
					<div class="button-container">
						<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`); };">
							<div class="inner-circle"></div>
						</div>
					</div>
					<button id="update-HIDXSettings" class="submitButton" onclick="applyEffect(this); updateHIDXSettings(); sendMessage(`CR1`);">Change Settings</button>
				</div>
				<h3>Toggles</h3>
				<label for="toggle-BootScript">BootScript</label>
				<div class="button-container">
					<div id="toggle-BootScript" class="toggle-button" onclick="if(this.classList.contains('active')) { payloadSetBootSlot('off') } else { payloadSetBootSlot('on'); };">
						<div class="inner-circle"></div>
					</div>
				</div>
				<label for="toggle-CU">USB</label>
				<div class="button-container">
					<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`); }">
						<div class="inner-circle"></div>
					</div>
				</div>
				<label for="toggle-CJ">Mouse Jiggler</label>
				<div class="button-container">
					<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`) }">
						<div class="inner-circle"></div>
					</div>
				</div>
			</div>
			<div id="sidebar_settings_keylog" class="hidden">
				<h3>Keylog Settings</h3>
				<button class="closeModal" type="button" onclick="keylogOptionsMenu('close')">&times;</button>
			</div>
			<div id="sidebar_settings_debug" class="hidden">
				<div>
					<h3>General</h3>
					<label for="sidebar_settings_debug_partitionEditor">Partition Editor</label>
					<button class="plus" id="sidebar_settings_debug_partitionEditor" onclick="partitionEditorCurrent(); generatePartitionEditor(); viewDialog('partitionEditor', 'open')">Partition Editor</button>
					<label for="uiAccentColor">Theme</label>
					<select name="uiAccentColor" id="uiAccentColor" onchange="sendMessage(`CTSet\tuicolor\t${this.value}`);sendMessage(`CTList`);setAccentColors(this.value)">
						<option value="red">Red</option>
						<option value="orange">Orange</option>
						<option value="yellow">Yellow</option>
						<option value="green">Green</option>
						<option value="cyan">Cyan</option>
						<option value="teal">Teal</option>
						<option value="blue">Blue</option>
						<option value="purple">Purple</option>
						<option value="pink">Pink</option>
						<option value="fuchsia">Fuchsia</option>
						<option value="maroon">Maroon</option>
						<option value="brown">Brown</option>
						<option value="grey">Grey</option>
						<option id="uicolorCustom" value="custom">Custom</option>
					</select>
					<label for="uiAccentColor">Custom Color</label>
					<div id="color-container">
						<span>
							<input type="range" id="color-slider" min="0" max="360" value="0">
							<div id="color-box"></div>
						</span>
						<span>
							<button id="apply-color" onclick="applyEffect(this);">Save</button>
						</span>
					</div>
					<h3>Download Frontend Log</h3>
					<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>
					<button class="submitButton" id="sidebar_settings_debug_feLog" onclick="applyEffect(this); downloadFrontendlog();">Download Frontend Log</button>
					<h3>Reboot</h3>
					<button class="submitButton" id="sidebar_settings_config_reboot" onclick="applyEffect(this); sendMessage(`CR1`)">Reboot</button>
					<div class="selfDestruct">
						<h3>Self Destruct</h3>
						<button class="submitButton" id="sidebar_settings_config_selfDestructFull" onclick="applyEffect(this); sendMessage(`CD1`)">Self Destruct (Full)</button>
						<aside>Completely erase all data and disconnect data lines to make device behave "broken". You will need to reflash the firmware to recover.</aside>
						<br>
						<button class="submitButton" id="sidebar_settings_config_selfDestructPartial" onclick="applyEffect(this); sendMessage(`CD2`)">Self Destruct (Partial)</button>
						<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>
					</div>
					<div class="keylog">
						<h3>Erase Keylog</h3>
						<button class="submitButton" id="sidebar_settings_debug_cldelete" onclick="applyEffect(this); sendMessage(`CLDelete`);">Erase Keylog</button>
						<br>
					</div>
					<h3>Custom Command</h3>
					<label for="customCommandInput">Input</label>
					<input type="text" id="customCommandInput" name="customCommandInput" spellcheck="false">
					<button id="customCommandRun" class="updateButton" onclick="applyEffect(this); issueCustomCommand();">Run</button>
					<label for="customCommandOutput">Output</label>
					<textarea id="customCommandOutput" spellcheck="false"></textarea>
					<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>
				</div>
			</div>
		</div>
		<div id="sidebar_about" class="hidden">
			<button class="close" type="button" onclick="sidebarAreaToggle('close')">
				<span>&times;</span>
			</button>
			<h1>About</h1>
			<div>
				<h3>Details</h3>
				<div>
					Backend Version: 
					<span id="device-VersionNumber"></span>
				</div>
				<div>
					Frontend Version: 
					<span id="frontend-VersionNumber"></span>
				</div>
				<div>
					Model: 
					<span id="device-TypeDecoded"></span>
				</div>
				<span id="device-Model" class="hidden"></span>
				<span id="device-Type" class="hidden"></span>
			</div>
			<h3>Release Team</h3>
			<div id="releaseTeam" class="about-section-credits"></div>
			<div class="about-team-name">More Information found at:</div>
			<div class="about-twitter">
				<a href="https://o.mg.lol/team/">https://o.mg.lol/team/</a>
			</div>
			<h2 class="visible">Regulatory</h2>
			<button class="visible" id="sidebar_settings_about_regulatoryButton" onclick="applyEffect(this); createRegulatory();">Regulatory Info</button>
		</div>
		<div id="sidebar_help" class="hidden">
			<button class="close" type="button" onclick="sidebarAreaToggle('close')">
				<span>&times;</span>
			</button>
			<h1>Help</h1>
			See 
			<a href="https://github.com/O-MG/O.MG-Firmware/wiki">Product Wiki</a>
			 for additional info about specs, feature use, & support.
			<h3>Search</h3>
			<input type="text" id="search" placeholder="Search Help...">
			<h3>Keymap Viewer</h3>
			<button class="submitButton helpHeader" id="sidebar_settings_help_keymapViewer" onclick="viewDialog('keymapViewer', 'open'); sidebarAreaToggle('close');">Show Keymap Viewer</button>
			<h3>O.MG DuckyScript Syntax Guide</h3>
			<div id="helpExamples">
				<button id="btn_helppayloadGithub" class="helpHeader" onclick="document.getElementById('helppayloadGithub').classList.toggle('hidden');">Hak5 Community Payloads</button>
				<div id="helppayloadGithub" class="hidden">
					<p>
						Community created payloads can be found at: 
						<a href="http://github.com/hak5/omg-payloads" target="_blank">http://github.com/hak5/omg-payloads</a>
					</p>
					<p>If you have internet access, you can also search below.</p>
					<input id="searchGithubPayloads" type="text" placeholder="Search Github...">
					<button class="updateButton buttonMergeLeft" onclick="applyEffect(this); searchGithubPayloads()">Search</button>
					<br>
					<div id="githubPayloadExamples"></div>
					<br>
				</div>
				<button id="btn_helppayloadExample" class="helpHeader" onclick="document.getElementById('helppayloadExample').classList.toggle('hidden');">Example Payloads</button>
				<div id="helppayloadExample" class="hidden"></div>
				<button id="btn_helppayload" class="helpHeader" onclick="document.getElementById('helppayload').classList.toggle('hidden');">General</button>
				<div id="helppayload" class="hidden"></div>
				<button id="btn_helpkeylog" class="helpHeader keylog" onclick="document.getElementById('helpkeylog').classList.toggle('hidden');">Keylogger</button>
				<div id="helpkeylog" class="keylog hidden"></div>
				<button id="btn_helpselfDestruct" class="helpHeader selfDestruct" onclick="document.getElementById('helpselfDestruct').classList.toggle('hidden');">Self-Destruct</button>
				<div id="helpselfDestruct" class="selfDestruct hidden"></div>
				<button id="btn_helppayloadGeofencing" class="helpHeader" onclick="document.getElementById('helppayloadGeofencing').classList.toggle('hidden');">Geofencing</button>
				<div id="helppayloadGeofencing" class="hidden"></div>
				<button id="btn_helppayloadUSBIDs" class="helpHeader" onclick="document.getElementById('helppayloadUSBIDs').classList.toggle('hidden');">USB Device ID</button>
				<div id="helppayloadUSBIDs" class="hidden"></div>
				<button id="btn_helpglobalKeys" class="helpHeader" onclick="document.getElementById('helpglobalKeys').classList.toggle('hidden');">Global Keys</button>
				<div id="helpglobalKeys" class="hidden"></div>
			</div>
		</div>
	</div>
	<div id="content" class="contentFullscreen">
		<content id="contentArea_keylog" class="hidden keylog">
			<label for="keylog" class="hidden">Keylog Area</label>
			<textarea id="keylog" spellcheck="false" class="editorKeylog" readonly="readonly" placeholder="Keylogger output"></textarea>
			<div id="keylogStorage" class="footer">
				<label for="keylogStorage">
					Storage Used:
					<data id="keylogStoragePercent">&nbsp;</data>
				</label>
				<div id="keylogStorageUsed" class="progressBar">
					<div id="keylogStorageUsedBar" class="progressFilled" style="width:0.2%">&nbsp;</div>
				</div>
			</div>
			<div id="footerKeylog" class="subnav footer">
				<button id="footerNavKeylogDownload" onclick="applyEffect(this); downloadKeylog('1');">
					<svg id="ico_keylogDownload" xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 40 40" class="iconColor navIcon"></svg>
					<span>Refresh Log</span>
				</button>
				<button id="footerNavKeylogSave" onclick="applyEffect(this); saveKeylog();">
					<svg id="ico_keylogSave" xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 40 40" class="iconColor navIcon"></svg>
					<span>Export View</span>
				</button>
				<button id="footerNavKeylogLiveStatus" onclick="liveLog();">
					<svg id="ico_keylogLiveLog" xml:space="preserve" class="iconColor navIcon" viewBox="0 0 40 40"></svg>
					<span>LiveView</span>
				</button>
				<button id="footerNavKeylogStatus" onclick="keylogStartStop();">
					<svg id="ico_keylogRun" xml:space="preserve" class="iconColor navIcon" viewBox="0 0 40 40"></svg>
					<span id="keylogStartButton">Start</span>
				</button>
			</div>
		</content>
		<content id="contentArea_payload" class="visible">
			<div class="editor-wrapper">
				<div id="lineNumbers"></div>
				<textarea id="payload" class="editor" spellcheck="false">DUCKY_LANG US
				DELAY 2000
				</textarea>
				<textarea id="payloadoutput" readonly="readonly" class="hidden"></textarea>
				<div id="debug-info" class="debug-info hidden"></div>
				<div id="payloadEditorFile">
					<span>File: </span>
					<span id="payloadEditorFilename">unsaved</span>
					<span id="payloadEditorBootable"></span>
					 (
					<span id="payloadEditorSaved">unsaved</span>
					)
				</div>
			</div>
			<div id="payloadStorage" class="footer">
				<label for="payloadStorageUsed">
					Storage Used:
					<data id="payloadStoragePercent">0%</data>
				</label>
				<div id="payloadStorageUsed" class="progressBar">
					<div id="payloadStorageUsedBar" class="progressFilled" style="width:0.2%">&nbsp;</div>
				</div>
			</div>
			<div id="payloadRun" class="footer hidden">
				<label for="payloadRunPercentDiv">
					Execution:
					<data id="payloadRunPercent">0%</data>
				</label>
				<div id="payloadRunPercentDiv" class="progressBar">
					<div id="payloadRunPercentBar" class="progressFilled" style="width:0%">&nbsp;</div>
				</div>
			</div>
			<label for="payload" class="hidden">Payload Area</label>
			<div id="footerPayload" class="subnav footer">
				<button id="footerNavPayloadMenu" onclick="this.classList.toggle('buttonSelectedGrey'); toggleModule('payloadFileMenu');">
					<svg id="ico_payloadLoad" xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 40 40" class="iconColor navIcon"></svg>
					<span>File Menu</span>
				</button>
				<button id="footerNavPayloadEvalByLine" class="hidden" onclick="applyEffect(this); compileDuckyScriptByLine();">
					<svg id="ico_payloadBuild" xml:space="preserve" viewBox="0 0 40 40" class="iconColor navIcon"></svg>
					<span>Build By Line</span>
				</button>
				<button id="footerNavPayloadRunByLine" class="hidden" onclick="applyEffect(this); runDuckyScriptByLine();">
					<svg id="ico_payloadRun" xml:space="preserve" viewBox="0 0 40 40" class="iconColor navIcon"></svg>
					<span>Run By Line</span>
				</button>
				<button id="footerNavPayloadEval" onclick="applyEffect(this); compileDuckyScript();">
					<svg id="ico_payloadBuild" xml:space="preserve" viewBox="0 0 40 40" class="iconColor navIcon"></svg>
					<span>Build</span>
				</button>
				<button id="footerNavPayloadRun" onclick="document.getElementById(`footerNavPayloadRun`).classList.add(`buttonRunning`); payloadRun();">
					<svg id="ico_payloadRun" xml:space="preserve" viewBox="0 0 40 40" class="iconColor navIcon"></svg>
					<div>Build + Run</div>
				</button>
			</div>
			<div id="payloadFileMenu" class="hidden footerFullscreen">
				<button class="closeModal" type="button" onclick="toggleModule('payloadFileMenu', 'close');">&times;</button>
				<h2>File Menu</h2>
				<label>Select Payload</label>
				<div id="fileMenuFlex">
					<div id="columnLeft">
						<select name="payloadSelect" id="payloadSelect"></select>
						<datalist id="payload-list"></datalist>
						<span class="tooltiptext arrowTop" id="tooltipPayloadSelect"></span>
					</div>
					<div id="columnRight">
						<button id="payload-loadButton" class="updateButtonLarge buttonMergeLeft" onclick="applyEffect(this); payloadSlotLoad(document.getElementById('payloadSelect').value)">Load</button>
						<button id="payload-saveButton" class="updateButtonLarge buttonMergeLeft buttonMergeRight" onclick="applyEffect(this); payloadSlotSave(document.getElementById('payloadSelect').value)">Save</button>
					</div>
				</div>
				<button id="payload-saveBootscriptButton" class="updateButtonLarge buttonMergeLeft buttonMergeRight" onclick="applyEffect(this); payloadSlotSave('bootscript')">Save Bootscript</button>
				<span>Run on Boot:</span>
				<span class="button-container" style="display: inline-block;">
					<div id="toggle-BootScriptFileMenu" class="toggle-button" onclick="if(this.classList.contains('active')) { payloadSetBootSlot('off') } else { payloadSetBootSlot('on'); };">
						<div class="inner-circle"></div>
					</div>
				</span>
				<br>
				<button id="payload-saveBootscriptButton" class="updateButtonLarge buttonMergeLeft buttonMergeRight" onclick="applyEffect(this); sendMessage(`CERun\tbootscript`)">Run Bootscript</button>
			</div>
		</content>
		<content id="contentArea_syslog" class="hidden">
			<label for="syslog" class="hidden">Syslog Area</label>
			<textarea id="syslog" spellcheck="false" class="editorSyslog" readonly="readonly"></textarea>
			<textarea id="debuglog" class="hidden"></textarea>
		</content>
		<content id="contentArea_c2log" class="hidden">
			<label for="c2log" class="hidden">c2log Area</label>
			<textarea id="c2log" spellcheck="false" class="editorc2log" readonly="readonly"></textarea>
		</content>
		<content id="contentArea_debug" class="hidden">
			<div>
				<label for="debugLevel">Log Level</label>
				<select name="debugLevel" id="debugLevel">
					<option value="NONE">NONE</option>
					<option value="INFO">INFO</option>
					<option value="WARN">WARN</option>
					<option value="CRIT">CRIT</option>
					<option value="DIAG">DIAG</option>
					<option value="DEBUG">DEBUG</option>
				</select>
				<label for="device-CalibrationStatus">Calibration Status</label>
				<input id="device-CalibrationStatus" readonly="readonly">
				<div>
					Calibration Status: 
					<span id="device-CalibrationStatusDecoded" spellcheck="false"></span>
				</div>
				<label for="device-FlashSize">Device Flash Size</label>
				<input id="device-FlashSize" readonly="readonly">
				<label for="device-Uptime">Device Uptime</label>
				<input id="device-Uptime" readonly="readonly">
				<label for="partitionEditorAvailable">Partition Editor Usable Space</label>
				<input id="partitionEditorAvailable" readonly="readonly">
				<label for="bootscriptPair">Bootscript to Payload Pair</label>
				<input id="bootscriptPair" readonly="readonly">
			</div>
			<div id="keyloggerStatus" class="keylog"></div>
			<div id="CFListResults" class="visible"></div>
			<div id="CTListResults" class="visible"></div>
		</content>
	</div>
	<div id="modal" class="hidden">
		<button class="closeModal" type="button" onclick="modal('modal', 'close')">&times;</button>
		<h1 id="modalLabel"></h1>
		<div id="modalMessage"></div>
		<div id="modalLoader" class="loader hidden"></div>
		<div id="modalButtons">
			<button id="modalSave" class="submitButton" onclick="applyEffect(this); downloadModal();">Save Message</button>
			<button id="modalClose" class="submitButton" onclick="modal(`modal`, 'close')">Close</button>
		</div>
	</div>
	<dialog id="loadingScreen" open>
		<h1>Loading WebUI</h1>
		<div class="loader"></div>
	</dialog>
	<dialog id="keymapViewer">
		<button class="close-button" type="button" onclick="viewDialog('keymapViewer', 'close');">&times;</button>
		<span id="keymapViewerLabel">Keymap Viewer</span>
		<span class="keymapViewerHeader">
			<input class="keymapping" type="text" placeholder="US" data-keylogger-map-region id="keymapViewerSelect" list="keymap-list">
			<datalist id="keymap-list"></datalist>
			<button id="update-keymapViewerLocale" class="updateButton" onclick="applyEffect(this); generateKeymap(document.getElementById('keymapViewerSelect').value)">Update</button>
			<button id="find-keymapViewerLocale" class="updateButton" onclick="applyEffect(this); findKeymap()">Find</button>
		</span>
		<div id="keymap_row1"></div>
		<div id="keymap_row2"></div>
		<div id="keymap_row3"></div>
		<div id="keymap_row4"></div>
		<div id="keymap_row5"></div>
		<div id="keymap_row6"></div>
		<div id="keymapFindList"></div>
	</dialog>
	<dialog id="partitionEditor">
		<button class="close-button" type="button" onclick="viewDialog('partitionEditor', 'close')">&times;</button>
		<h1 id="partitionEditorLabel">Partition Editor</h1>
		<div id="partitionPayloadConfig">
			<h2>Payloads</h2>
			<div class="partitionEditorFieldWrapper">
				<span class="partitionEditorFieldLabel">Type</span>
				<select id="payloadSlotType" onchange="generatePartitionEditor();">
					<option value="1" selected="selected">Editable</option>
					<option value="2">Executable</option>
					<option value="3">Both</option>
				</select>
			</div>
			<div class="partitionEditorFieldWrapper">
				<span class="partitionEditorFieldLabel">Count</span>
				<select id="payloadSlotCount" onchange="generatePartitionEditor();"></select>
			</div>
			<div class="partitionEditorFieldWrapper">
				<span class="partitionEditorFieldLabel">Size</span>
				<select id="payloadSlotSize" onchange="generatePartitionEditor();">
					<option value="1">4 KB</option>
					<option value="2">8 KB</option>
					<option value="4" selected="selected">16 KB</option>
					<option value="8">32 KB</option>
					<option value="16">64 KB</option>
					<option value="32">128 KB</option>
					<option value="64">256 KB</option>
				</select>
			</div>
			<div class="partitionEditorFieldWrapper">
				<span class="partitionEditorFieldLabel">Payload Cache</span>
				<select id="plcacheSlotSize" onchange="generatePartitionEditor();">
					<option value="10">40 KB</option>
					<option value="20">80 KB</option>
					<option value="40">160 KB</option>
					<option value="80" selected="selected">320 KB</option>
					<option value="120">480 KB</option>
				</select>
			</div>
			<div class="partitionEditorFieldWrapper">
				<span class="partitionEditorFieldLabel">Bootscript</span>
				<select id="bootscriptSlotSize" onchange="generatePartitionEditor();">
					<option value="1">4 KB</option>
					<option value="2">8 KB</option>
					<option value="4" selected="selected">16 KB</option>
					<option value="8">32 KB</option>
					<option value="16">64 KB</option>
					<option value="32">128 KB</option>
					<option value="64">256 KB</option>
				</select>
			</div>
		</div>
		<br>
		<h2>Exfiltration</h2>
		<div id="partitonHidxConfig" class="partitionEditorFieldWrapper">
			<span class="partitionEditorFieldLabel">HIDX Exfil to File</span>
			<select id="hidxfileSize" onchange="generatePartitionEditor();">
				<option value="1">4 KB</option>
				<option value="2">8 KB</option>
				<option value="4">16 KB</option>
				<option value="8">32 KB</option>
				<option value="16">64 KB</option>
				<option value="32">128 KB</option>
				<option value="64">256 KB</option>
				<option value="128">512 KB</option>
			</select>
		</div>
		<div id="partitionKeylogConfig" class="partitionEditorFieldWrapper">
			<span class="partitionEditorFieldLabel">Keylog</span>
			<span id="keylogSize"></span>
			<span>&nbsp;keys</span>
		</div>
		<br>
		<br>
		<button id="partitionEditorDefault" class="submitButton" onclick="applyEffect(this); partitionEditorApply();">Apply Layout</button>
	</dialog>
	<dialog id="partitionApplied">
		<button class="closeModal" type="button" onclick="viewDialog('partitionApplied', 'close')">&times;</button>
		<h2>Partition Applied</h2>
		<h4>Your device will now reboot.</h4>
	</dialog>
	<dialog id="firstTimeSetup">
		<h2>O.MG Initial Device Setup</h2>
		<div id="fsIcons">
			<div>
				<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 338 338">
					<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"/>
					<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"/>
					<g class="blockerIcon">
						<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"/>
						<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"/>
						<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"/>
					</g>
					<g class="cableIcon">
						<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"/>
						<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"/>
						<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"/>
					</g>
					<g class="plugIcon" style="transform: scale(2) translate(-135px, -100px);">
						<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"/>
						<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"/>
					</g>
					<path d="M275 62 L65 273" fill="none" stroke="red" stroke-width="15" stroke-miterlimit="10"/>
				</svg>
				<br>
				<span>Do not expose to extreme heat.</span>
			</div>
			<div>
				<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 338 338">
					<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"/>
					<path d="M0 1h338v338H0V1Z" fill="none"/>
					<g class="blockerIcon">
						<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"/>
						<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"/>
						<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"/>
					</g>
					<g class="cableIcon">
						<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"/>
						<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"/>
						<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"/>
					</g>
					<g class="plugIcon" style="transform: scale(2) translate(-135px, -100px);">
						<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"/>
						<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"/>
					</g>
					<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"/>
					<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"/>
					<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"/>
					<path d="M275 62 L65 273" fill="none" stroke="red" stroke-width="15" stroke-miterlimit="10"/>
				</svg>
				<br>
				<span>Do not expose to &gt;5V.</span>
			</div>
			<div>
				<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 338 338">
					<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"/>
					<path d="M0 1h338v338H0V1Z" fill="none"/>
					<g fill="none" stroke-miterlimit="10">
						<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"/>
						<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"/>
						<path d="M267 90c0 14-12 25-28 25H97c-14 0-26-10-26-23" stroke="black" stroke-width="10"/>
						<path d="M80 250c0-40 26-72 59-72s49 7 49 16" stroke="black" stroke-width="10"/>
						<path d="M167 114V84 M222 112V83 M113 112V83" stroke="black" stroke-width="5"/>
					</g>
					<g class="blockerIcon">
						<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"/>
						<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"/>
						<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"/>
					</g>
					<g class="cableIcon">
						<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"/>
						<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"/>
						<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"/>
					</g>
					<g class="plugIcon" style="transform: scale(2) translate(-135px, -115px);">
						<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"/>
						<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"/>
					</g>
					<path d="M275 62 L65 273" fill="none" stroke="red" stroke-width="15" stroke-miterlimit="10"/>
				</svg>
				<br>
				<span>Do not eat.</span>
			</div>
			<div>
				<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 338 338">
					<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"/>
					<path d="M67.5,81.5v167M272.5,81.5v167M62.5,243.5h214" fill="none" stroke-miterlimit="10" stroke="black" stroke-width="10"/>
					<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"/>
					<g class="blockerIcon">
						<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"/>
						<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"/>
						<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"/>
					</g>
					<g class="cableIcon">
						<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"/>
						<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"/>
						<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"/>
					</g>
					<g class="plugIcon" style="transform: scale(2) translate(-135px, -100px);">
						<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"/>
						<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"/>
					</g>
					<path d="M275 62 L65 273" fill="none" stroke="red" stroke-width="15" stroke-miterlimit="10"/>
				</svg>
				<br>
				<span>Do not submerge in water.</span>
			</div>
		</div>
		<br>
		<br>
		<br>
		<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>
	</dialog>
	<dialog id="firstTimeSetupPage2">
		<h2>O.MG Initial Device Setup</h2>
		<p>
			This device is currently running factory-loaded firmware.
			<br>
			Firmware build date: 
			<span id="firmwareVersionNumberHumanReadableString"></span>
		</p>
		<p>
			An 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. (
			<a href="https://o.mg.lol/setup">https://o.mg.lol/setup</a>
			)
		</p>
		<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>
		<br>
		<br>
		<br>
		<button id="firstTimeSetupAgreePage2" class="submitButton buttonActivationDelay" onclick="applyEffect(this); viewDialog('firstTimeSetupPage2', 'close'); viewDialog('firstTimeSetupPage3', 'open');">Agree and Activate</button>
	</dialog>
	<dialog id="firstTimeSetupPage3">
		<h2>O.MG Initial Device Setup</h2>
		<h2>Agreement</h2>
		<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>
		<h2>Privacy Policy</h2>
		<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>
		<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>
		<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>
		<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>
		<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>
		<h2>Terms of Use</h2>
		<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>
		<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>
		<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>
		<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>
		<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>
		<p>
			As 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
			<br>
			Tor Ekeland Law, PLLC
			<br>
			30 Wall St., 8th Floor
			<br>
			New York, NY 10005
			<br>
			info@torekeland.com
		</p>
		<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>
		<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>
		<br>
		<br>
		<br>
		<button id="firstTimeSetupAgreePage3" class="submitButton buttonActivationDelay" onclick="applyEffect(this); sendMessage(`FSSet\t1`); sendMessage(`CU0`); viewDialog('firstTimeSetupPage3', 'close');">Agree and Activate</button>
	</dialog>
	<script>
		// Global Variables
		frontendType = "release"; // Options: release, factory
		frontendMajorVersion = "3.0";
		frontendMinorVersion = "250514";
		if (frontendType == "factory") {
			frontendVersionNumber = `${frontendMajorVersion}-${frontendMinorVersion}-${frontendType}`;
		} else {
			frontendVersionNumber = `${frontendMajorVersion}-${frontendMinorVersion}`;
		}
		let debugLevel = "INFO";
		let liveLogStatus = "off";
		let keylogStatus = "0";
		let slotmode = "1";
		// Global Variables - Connection Info
		var enabledFeatures = [];
		const defaultFeatures = ['global', 'syslog', 'debug'];
		let featureFlags = [];
		let lastMessage = 0;
		var connectionAttempts = 0;
		let webSocketErrorCount = 0;
		let previousUptime = Infinity;
		let previousMAC = null;
		let uicolor;
		let zIndexCounter = 1;
		
		function getAddressParameter() {
			const queryString = window.location.search;
			const urlParams = new URLSearchParams(queryString);
			return urlParams.has('backend') ? urlParams.get('backend') : null;
		}
		
		const backendParam = getAddressParameter();
		const hostnameMap = {
		//  "localhost": connectCustomBackend(),
		};
		let hostname = backendParam || hostnameMap[window.location.hostname] || window.location.hostname;
		if(hostname == "localhost"){
			hostname = "192.168.5.140";
		}
		
		function connectCustomBackend() {
			createDialog({
				id: 'ipAddress',
				title: 'O.MG Backend IP Address',
				description: `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>`,
				closeEnabled: false,
			});
		}
		
		// Global Variables - Defaults
		const readSize = 1024;
		const blockSize = 4096;
		let splitPayload = [];
		let splitPayloadCounter = 0;
		let keymapList = [];
		let keymapListPretty = "";
		keymapViewerKeys = [];
		var keymapinverted = "";
		var bootpair;
		var refreshRate = 1;
		var logsIntervalId;
		var adminIntervalId;
		var adminData;
		var c2logAbridge = 0;
		var alwaysDisplayLastPoll = 0;
		var lastDateTime;
		var fccID = "Unknown";
		var crc16 = "";
		var previousLocale = "";
		var activeEndLabel = "O.MG Device";
		
		// Feature Flags
		const features = {
			'global': {
				'CFError': 'Filesystem Error',
				'CFGet': 'Filesystem Get',
				'CFList': 'Filesystem List',
				'CI': 'Firmware Version',
				'CNGet': 'USB Descriptors Get',
				'CNSet': 'USB Descriptors Set',
				'CR': 'Reboot',
				'CS': 'WiFi Scan',
				'CTErase': 'Setting Erase',
				'CTError': 'Setting Error',
				'CTGet': 'Setting Get',
				'CTList': 'Setting List',
				'CTSet': 'Setting Set',
				'CV': 'System Version',
				'CW': 'WiFi Configure',
				'CWInfo': 'WiFi Show Config',
				'CWStatus': 'WiFi Show Status',
				'FE': 'Flash Erase',
				'FR': 'Flash Read',
				'FW': 'Flash Write',
				'FX': 'Flash Write as Hex',
				'FSGet': 'First Time Setup Wizard',
				'FSSet': 'First Time Setup Wizard',
			},
			'syslog': {},
			'payload': {
				'CE': 'Payload Execute',
				'CEError': 'Payload Error',
				'CEStatus': 'Payload Status',
				'CJ': 'Mouse Jiggler Enable/Disable'
			},
			'debug': {
				'CO': 'Calibration Apply',
				'CPGet': 'Calibration Get Request',
				'CPStart': 'Calibration Generate Request',
				'CZ': 'USB Diagnostic Log',
				'E': 'Echo',
			},
			'keylog': {
				'CLDelete': 'Keylog Delete',
				'CLRead': 'Keylog Read',
				'CLSetCursor': 'Keylog Set Cursor',
				'CLStart': 'Keylog Start',
				'CLStatus': 'Keylog Status',
				'CLStop': 'Keylog Stop',
			},
			'hidx': {
				'CHStart': 'HIDX Start',
				'CHStop': 'HIDX Stop',
				'CHStatus': 'HIDX Status',
				'SFErase': 'Sequential File Erase',
				'SFInfo': 'Sequential File Info',
				'SFRead': 'Sequential File Read',
			},
			'c2': {
				'C2Config': 'C2 Config',
				'C2Status': 'C2 Status',
			},
			'standalone': {},
			'stealth': {
				'CD': 'Self Destruct',
				'CU': 'USB Enumeration Enable/Disable',
			}
		};
		
		const typeMap = {
			'a': ['* to USB-A Cable', 'red'],
			'b': ['Unblocker', 'red'],
			'c': ['* to USB-C Cable', 'red'],
			'd': ['USB-C Directional Cable', 'red'],
			'p': ['Plug', 'blue'],
			't': ['A to C Adapter', 'red'],
			'default': ['O.MG Device', 'grey'],
		};
		
		const modelMap = {
			'c': ['Basic', 'BASIC'],
			'k': ['Plus', 'PLUS'],
			'd': ['Elite', 'ELITE'],
			'e': ['Elite', 'ELITE'],
			'default': ['O.MG Device', 'ERROR'],
		};
		
		const featureFlagMap = {
			'pc': ['global', 'syslog', 'debug', 'payload'],
			'bd': ['global', 'syslog', 'debug', 'payload', 'c2', 'hidx', 'stealth'],
			'pd': ['global', 'syslog', 'debug', 'payload', 'c2', 'hidx', 'stealth'],
			'xc': ['global', 'syslog', 'debug', 'payload', 'c2', 'hidx', 'stealth'],
			'c': ['global', 'syslog', 'debug', 'payload', 'stealth'],
			'k': ['global', 'syslog', 'debug', 'payload', 'keylog', 'stealth'],
			'e': ['global', 'syslog', 'debug', 'payload', 'keylog', 'c2', 'hidx', 'stealth'],
		};
		
		function convertVersionToDate(versionString) {
			const year = "20" + versionString.substring(0, 2);
			const month = parseInt(versionString.substring(2, 4)) - 1;
			const day = parseInt(versionString.substring(4, 6));
			const date = new Date(year, month, day);
			return date.toLocaleDateString('en-US', {
				year: 'numeric',
				month: 'long',
				day: 'numeric'
			});
		}
		
		function generateModifiers() {
			const groups = {
				0x01: ['CONTROL', 'CTRL'],
				0x02: ['SHIFT'],
				0x04: ['ALT', 'OPT', 'OPTION'],
				0x08: ['CMD', 'COMMAND', 'GUI', 'META', 'SUPER', 'WIN', 'WINDOWS']
			};
			const leftPrefixes = ['', 'L', 'LEFT', 'LEFT-'];
			const rightPrefixes = ['', 'R', 'RIGHT', 'RIGHT-'];
			const modifiers = {};
		
			Object.entries(groups).forEach(([value, names]) => {
				const leftValue = '0x0' + parseInt(value, 16).toString(16).toUpperCase();
				const rightValue = '0x' + (parseInt(value, 16) << 4).toString(16).toUpperCase();
		
				names.forEach(name => {
					leftPrefixes.forEach(prefix => {
						modifiers[prefix + name] = leftValue;
					});
		
					rightPrefixes.forEach(prefix => {
						modifiers[prefix + name] = rightValue;
					});
				});
			});
		
			return modifiers;
		}
		const modifiers = generateModifiers();
		
		var modifiersInverted = {
			0x01: 'LEFT-CTRL',
			0x02: 'LEFT-SHIFT',
			0x04: 'LEFT-ALT',
			0x08: 'LEFT-GUI',
			0x10: 'RIGHT-CTRL',
			0x20: 'RIGHT-SHIFT',
			0x40: 'RIGHT-ALT',
			0x80: 'RIGHT-GUI',
		};
		
		// On-Load Config
		document.addEventListener("DOMContentLoaded", async function(event) {
			document.getElementById('debugLevel').value = debugLevel;
			logMessage(`CRIT`, `Frontend Version: ${frontendVersionNumber}`);
			generateKeymapList();
			await toggleFeatureFlags();
			await generateDebugMenu();
			generateHelpMenu();
			generateReleaseTeam();
			generateKeymapViewerMenu();
			generateKeymap('US');
			document.querySelectorAll('code').forEach(element => {
				element.addEventListener('click', handleHelpToPayload);
			});
			window.scrollTo(0, 1);
			const appHeight = () => {
				const doc = document.documentElement
				if (window.visualViewport.height != undefined) {
					doc.style.setProperty('--app-height', `${window.visualViewport.height}px`);
				} else if (window.innerHeight != undefined) {
					doc.style.setProperty('--app-height', `${window.innerHeight}px`);
				} else {
					doc.style.setProperty('--app-height', `100dvh`);
				}
			}
			window.addEventListener('resize', appHeight);
			appHeight();
		
			const payloadSelect = document.getElementById('payloadSelect');
			const saveBootableButton = document.getElementById('payload-bootableToggle');
			document.getElementById('searchGithubPayloads').addEventListener('keydown', searchGithubPayloads);
		
			payloadSelect.addEventListener('input', function() {
				if (payloadSelect.value === bootpair) {
					saveBootableButton.classList.add('active');
				} else {
					saveBootableButton.classList.remove('active');
				}
			});
		
			var payloadEditor = document.getElementById('payload');
			var payloadEditorChangeTimeout = null;
			payloadEditor.addEventListener('input', function() {
				if (payloadEditorChangeTimeout) {
					clearTimeout(payloadEditorChangeTimeout);
				}
		
				payloadEditorChangeTimeout = setTimeout(function() {
					payloadEditorSavedElement = document.getElementById('payloadEditorSaved');
					payloadEditorSavedElement.innerHTML = "Edited";
					compileDuckyScript();
				}, 2000);
			});
		
			const colorSlider = document.querySelector("#color-slider");
			const colorBox = document.querySelector("#color-box");
			const applyButton = document.querySelector("#apply-color");
		
			colorSlider.addEventListener("input", function() {
				const hue = colorSlider.value;
				colorBox.style.backgroundColor = `hsl(${hue}, 100%, 50%)`;
				setAccentColors(hue);
			});
		
			applyButton.addEventListener("click", function() {
				sendMessage(`CTSet\tuicolor\t${colorSlider.value}`);
			});
		
		});
		document.onvisibilitychange = function() {
			logMessage(`WARN`, `Browser Visibility Change Detected: [visibilityState]:[${document.visibilityState}]`);
			if (document.visibilityState === 'hidden') {
				logMessage(`INFO`, `Browser Went to Sleep.`);
			} else if (document.visibilityState === 'visible') {
				logMessage(`INFO`, `Browser Resumed from Sleep.`);
				checkWebSocketStatus();
			}
		};
		
		// Data Transform
		const toHex = (() => {
			let cache = {};
		
			const memoizedFunction = (number, size = 2) => {
				const key = number + ',' + size;
				if (cache[key]) {
					return cache[key];
				}
				number = parseInt(number);
				let b1 = number.toString(16);
				if (b1.length === 1) {
					b1 = '0' + b1;
				}
				while (b1.length < size) b1 = '0' + b1;
				const result = b1.toUpperCase();
		
				logMessage(`DEBUG`, `toHex: [number]:[${number}] [b1]:[${b1}] [size]:[${size}]`);
		
				if (result === undefined || result === null) {
					console.log("No matching value for key: ", key);
					return;
				}
		
				cache[key] = result;
				return result;
			};
		
			memoizedFunction.invalidateCache = () => {
				cache = {};
			};
		
			return memoizedFunction;
		})();
		
		function padHex(num) {
			return num.toString(16).padStart(2, '0');
		}
		
		function padInput(value, length) {
			value = String(value);
			let len = parseInt(length);
		
			if (isNaN(len) || len <= value.length) {
				return value;
			}
		
			return value.padStart(len, '0');
		}
		
		function asciiToHex(str) {
			var arr1 = [];
			for (var n = 0, l = str.length; n < l; n++) {
				var hex = Number(str.charCodeAt(n)).toString(16);
				arr1.push(hex);
			}
			return arr1.join('');
		}
		
		function unescapeHtml(html) {
			const entitiesMap = {
				'&lt;': '<',
				'&gt;': '>',
				'&amp;': '&',
				'&quot;': '"',
				'&apos;': "'",
				'&nbsp;': ' '
			};
		
			return html.replace(/&lt;|&gt;|&amp;|&quot;|&apos;|&nbsp;/g, (matched) => {
				return entitiesMap[matched];
			});
		}
		
		function crc16augccitt(str) {
			let crc = 0x1D0F;
			let polynomial = 0x1021;
		
			for (let i = 0; i < str.length; i++) {
				let byte = str.charCodeAt(i);
				for (let j = 0; j < 8; j++) {
					let bit = ((byte >> (7 - j) & 1) === 1),
						c15 = ((crc >> 15 & 1) === 1);
					crc <<= 1;
					if (c15 ^ bit) crc ^= polynomial;
				}
			}
		
			crc &= 0xffff;
			return crc.toString(16).toUpperCase();
		}
		
		
		function bigEndianToLittleEndian(hexString) {
			if (hexString.length % 2 !== 0) {
				console.error('Invalid hex string length. It must be a multiple of 2.');
				return null;
			}
		
			let littleEndian = '';
			for (let i = 0; i < hexString.length; i += 2) {
				littleEndian = hexString.substring(i, i + 2) + littleEndian;
			}
		
			return littleEndian;
		}
		
		function extractArg(args, pattern) {
			const match = args.match(pattern);
			return match ? match[1] : null;
		}
		
		function sleep(ms) {
			return new Promise(resolve => setTimeout(resolve, ms));
		}
		
		function toggleClassBasedOnStatus(elementId, className, status, desiredStatus) {
			let element = document.getElementById(elementId);
		
			if (status === desiredStatus && !element.classList.contains(className)) {
				element.classList.add(className);
			} else if (status !== desiredStatus && element.classList.contains(className)) {
				element.classList.remove(className);
			}
		}
		
		function toggleModule(moduleName, openClose) {
			const moduleElement = document.getElementById(moduleName);
			const isVisible = moduleElement.classList.contains('visible');
			const shouldOpen = openClose === "open" ? true : (openClose === "close" ? false : !isVisible);
		
			toggleVisibility(moduleElement, shouldOpen);
		}
		
		function toggleVisibility(element, isVisible) {
			element.classList.toggle('visible', isVisible);
			element.classList.toggle('hidden', !isVisible);
		}
		
		function toggleClass(id, class1, class2) {
			const element = document.getElementById(id);
			if (element) {
				element.classList.remove(class1);
				element.classList.add(class2);
			}
		}
		
		function toggleClassVisibility(id, visibleClassName, isVisible) {
			const element = document.getElementById(id);
			if (element) {
				element.classList.toggle(visibleClassName, isVisible);
				element.classList.toggle('hidden', !isVisible);
			}
		}
		
		function setElementInnerHTML(id, html) {
			const element = document.getElementById(id);
			if (element) {
				element.innerHTML = html;
			}
		}
		
		function setElementValue(id, value, prop = 'value') {
			const element = document.getElementById(id);
			if (element) {
				element[prop] = value;
			}
		}
		
		function modal(moduleName, mode) {
			const targetModule = moduleName === "modal" ? moduleName : `${moduleName}Modal`;
			toggleModule(targetModule, mode);
		}
		
		function modalCreate(label, message) {
			setElementInnerHTML("modalLabel", label);
			setElementInnerHTML("modalMessage", message);
			toggleModalVisibility(true);
			toggleModule("modal", "open");
		}
		
		function toggleModalVisibility(visible) {
			const modal = document.getElementById('modal');
			if (modal) {
				modal.classList.toggle('hidden', !visible);
				modal.classList.toggle('visible', visible);
			}
		}
		
		function colorNameToHue(colorName) {
			const colors = {
				red: 5,
				orange: 25,
				yellow: 45,
				green: 120,
				cyan: 190,
				teal: 210,
				blue: 230,
				purple: 260,
				pink: 300,
				fuchsia: 320,
				maroon: 340,
				brown: 20,
				grey: 0,
			};
		
			return colors[colorName.toLowerCase()] || 0;
		}
		
		function setAccentColors(hue) {
			const uiAccentColorMenuSelect = document.getElementById('uiAccentColor');
			const r = document.querySelector(':root');
		
			if (!isNaN(hue) && hue >= 0 && hue <= 360) {
				hslHue = hue;
				customColor = true;
			} else {
				hslHue = colorNameToHue(hue);
				customColor = false;
			}
		
			if (hue == "grey") {
				hslSat = "0";
			} else if (hue == "brown") {
				hslSat = "50";
			} else {
				hslSat = "75";
			}
		
			r.style.setProperty('--accentColor', `hsl(${hslHue}, ${hslSat}%, 32%)`);
			r.style.setProperty('--accentGradient1', `hsl(${hslHue}, ${hslSat}%, 23%)`);
			r.style.setProperty('--accentGradient2', `hsl(${hslHue}, ${hslSat}%, 41%)`);
		
			if (uiAccentColorMenuSelect && customColor == false) {
				uiAccentColorMenuSelect.value = hue;
			} else {
				customColor = document.getElementById('uicolorCustom');
				customColor.value = hue;
				uiAccentColorMenuSelect.value = hue;
			}
		}
		
		function hideElementsByClass(...classNames) {
			classNames.forEach((className) => {
				const elements = document.querySelectorAll('.' + className);
				for (const element of elements) {
					element.classList.add('hidden');
				}
			});
		}
		
		function updateElementValue(id, value, prop = 'value') {
			const element = document.getElementById(id);
			if (element) element[prop] = value;
		}
		
		function setRadioChecked(id) {
			const element = document.getElementById(id);
			if (element) {
				element.checked = true;
			}
		}
		
		function processType(type) {
			return typeMap[type] || typeMap['default'];
		}
		
		function processModel(model) {
			return modelMap[model] || modelMap['default'];
		}
		
		function contentAreaToggle(moduleName) {
			const modules = ["keylog", "payload", "syslog", "c2log", "debug"];
		
			if (moduleName === "keylog") {
				downloadKeylog('1');
			}
		
			modules.forEach(module => {
				document.getElementById(`contentArea_${module}`).classList.remove(`visible`);
				document.getElementById(`contentArea_${module}`).classList.add(`hidden`);
				document.getElementById(`contentSubNav_${module}`).classList.remove(`buttonSelected`);
			});
		
			document.getElementById(`contentArea_${moduleName}`).classList.replace(`hidden`, `visible`);
			document.getElementById(`contentSubNav_${moduleName}`).classList.add(`buttonSelected`);
		}
		
		function sidebarSettingsAreaToggle(moduleName) {
			document.getElementById(`sidebar_settings_queue`).classList.remove(`visible`);
			document.getElementById(`sidebar_settings_net`).classList.remove(`visible`);
			document.getElementById(`sidebar_settings_config`).classList.remove(`visible`);
			document.getElementById(`sidebar_settings_debug`).classList.remove(`visible`);
			document.getElementById(`sidebar_settings_queue`).classList.add(`hidden`);
			document.getElementById(`sidebar_settings_net`).classList.add(`hidden`);
			document.getElementById(`sidebar_settings_config`).classList.add(`hidden`);
			document.getElementById(`sidebar_settings_debug`).classList.add(`hidden`);
			document.getElementById(`sidebarNav_queue`).classList.remove(`buttonSelected`);
			document.getElementById(`sidebarNav_net`).classList.remove(`buttonSelected`);
			document.getElementById(`sidebarNav_config`).classList.remove(`buttonSelected`);
			document.getElementById(`sidebarNav_debug`).classList.remove(`buttonSelected`);
			document.getElementById(`sidebar_settings_${moduleName}`).classList.remove(`hidden`);
			document.getElementById(`sidebar_settings_${moduleName}`).classList.add(`visible`);
			document.getElementById(`sidebarNav_${moduleName}`).classList.remove(`buttonSelected`);
			document.getElementById(`sidebarNav_${moduleName}`).classList.add(`buttonSelected`);
		}
		
		function sidebarAreaToggle(moduleName) {
			if (moduleName != "close") {
				if (document.getElementById(`sidebar_${moduleName}`).classList.contains(`visible`)) {
					moduleName = "close";
				}
			}
			document.getElementById(`sidebar`).classList.remove(`visible`);
			document.getElementById(`sidebar`).classList.add(`hidden`);
			document.getElementById(`sidebar_settings`).classList.remove(`visible`);
			document.getElementById(`sidebar_settings`).classList.add(`hidden`);
			document.getElementById(`sidebar_about`).classList.remove(`visible`);
			document.getElementById(`sidebar_about`).classList.add(`hidden`);
			document.getElementById(`sidebar_help`).classList.remove(`visible`);
			document.getElementById(`sidebar_help`).classList.add(`hidden`);
			if (moduleName != "close") {
				document.getElementById(`content`).classList.remove(`contentFullscreen`);
				document.getElementById(`content`).classList.add(`contentSidebar`);
				document.getElementById(`payloadFileMenu`).classList.remove(`footerFullscreen`);
				document.getElementById(`payloadFileMenu`).classList.add(`footerSidebar`);
				document.getElementById(`subnav`).classList.remove(`subnavFullscreen`);
				document.getElementById(`subnav`).classList.add(`subnavSidebar`);
				document.getElementById(`sidebar_${moduleName}`).classList.remove(`hidden`);
				document.getElementById(`sidebar_${moduleName}`).classList.add(`visible`);
				document.getElementById(`sidebar`).classList.remove(`hidden`);
				document.getElementById(`sidebar`).classList.add(`visible`);
			} else {
				document.getElementById(`content`).classList.remove(`contentSidebar`);
				document.getElementById(`content`).classList.add(`contentFullscreen`);
				document.getElementById(`payloadFileMenu`).classList.remove(`footerSidebar`);
				document.getElementById(`payloadFileMenu`).classList.add(`footerFullscreen`);
				document.getElementById(`subnav`).classList.remove(`subnavSidebar`);
				document.getElementById(`subnav`).classList.add(`subnavFullscreen`);
			}
		}
		
		function toggleSignalBars(elements, state) {
			elements.forEach(element => {
				const el = document.getElementById(element);
				el.classList.remove(state === 'on' ? 'wifiSignalOff' : 'wifiSignalOn');
				el.classList.add(state);
			});
		}
		
		function downloadFile(elementId, fileName, redactPasswords = true) {
			logMessage(`CRIT`, `${fileName}`);
			let text = document.getElementById(elementId).value;
		
			if (redactPasswords) {
				const wifipass = document.getElementById('device-WIFIPassword').value;
				const re = new RegExp(wifipass.replace(/[.*+\-?^${}()|[\]\\]/g, '\\$&'), 'g');
				text = text.replace(re, '********');
			}
		
			const textFileAsBlob = new Blob([text], { type: 'text/plain' });
			const downloadLink = document.createElement("a");
			downloadLink.download = `${fileName}.txt`;
			downloadLink.innerHTML = "Download File";
		
			const createObjectURL = window.webkitURL ? window.webkitURL.createObjectURL : window.URL.createObjectURL;
			downloadLink.href = createObjectURL(textFileAsBlob);
		
			if (!window.webkitURL) {
				downloadLink.onclick = destroyClickedElement;
				downloadLink.style.display = "none";
				document.body.appendChild(downloadLink);
			}
		
			downloadLink.click();
		}
		
		function downloadModal() {
			const modalName = document.getElementById('modalLabel').innerHTML;
			downloadFile('modalMessage', `${modalName}-log`);
		}
		
		function saveKeylog() {
			downloadFile('keylog', 'keylog');
		}
		
		async function toggleFeatureFlags() {
			enabledFeatures = [];
			if (!featureFlags) {
				featureFlags = defaultFeatures;
			}
			for (let i = 0; i < featureFlags.length; i++) {
				if (features[featureFlags[i]]) {
					for (let key in features[featureFlags[i]]) {
						enabledFeatures.push(key);
					}
				}
			}
			logMessage(`CRIT`, `Supported Features: ${enabledFeatures}`);
			await generateDebugMenu();
		}
		
		function applyFeatureFlags(type, model) {
			const key = (type === 'p' || type === 'b') ? type + model : model;
			featureFlags = featureFlagMap[key] || [];
			
			if (type === 'p' && model === 'c') { // Plug Basic
				hideElementsByClass('selfDestruct', 'keylog', 'c2', 'hidx');
			}
			if (type === 'b' && model === 'd') { // Unblocker Elite
				hideElementsByClass('keylog');
			}
			if (type === 'p' && model === 'd') { // Plug Elite
				hideElementsByClass('keylog');
			}
			if (model === 'c') { // Basic Cable
				hideElementsByClass('keylog', 'c2', 'hidx');
			}
			if (model === 'k') { // Plus Cable
				hideElementsByClass('c2', 'hidx');
			}
			if (type === 'a' || type === 'c' || type === 'd') {
				hideElementsByClass('plugIcon', 'blockerIcon'); // Cable
			}
			if (type === 'b' || type === 't') {
				hideElementsByClass('plugIcon', 'cableIcon'); // Unblocker / Adapter
			}
			if (type === 'p') {
				hideElementsByClass('cableIcon', 'blockerIcon'); // Plug
			}
		}
		
		const parseData = (data) => {
			response = data;
			parameters = response.split("\t");
			return { response, parameters };
		};
		
		const updateElements = (elementMap) => {
			for (const [elementId, value] of Object.entries(elementMap)) {
				updateElement(elementId, value);
			}
		};
		
		function setElementValues(elementValues, domElement = null, activeClassElements = [], toggleClassElements = []) {
			const valueElements = ['INPUT', 'SELECT', 'OPTION', 'TEXTAREA'];
			const innerHtmlElements = ['DIV', 'SPAN', 'P', 'LI', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'DATA'];
		
			for (const [elementId, item] of Object.entries(elementValues)) {
				const element = document.getElementById(elementId);
		
				if (!element) {
					console.warn(`Element with ID: [${elementId}] not found in the DOM.`);
					continue;
				}
		
				let value;
				let updateMethod;
		
				if (typeof item === 'object' && item !== null && 'value' in item && 'method' in item) {
					value = item.value;
					updateMethod = item.method;
					element[updateMethod] = value;
				} else {
					value = item;
		
					if (domElement) {
						updateMethod = domElement;
					} else {
						if (valueElements.includes(element.tagName)) {
							updateMethod = 'value';
						} else if (innerHtmlElements.includes(element.tagName)) {
							updateMethod = 'innerHTML';
						}
					}
		
					if (updateMethod) {
						element[updateMethod] = value;
					} else {
						console.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.`);
					}
				}
		
				if (activeClassElements.includes(elementId)) {
					element.classList[value === "1" ? 'add' : 'remove']('active');
				}
		
				if (toggleClassElements.hasOwnProperty(elementId)) {
					element.classList.toggle(toggleClassElements[elementId]);
				}
			}
		}
		
		function setElementChecked(id, isChecked) {
			const element = document.getElementById(id);
			if (element) {
				element.checked = isChecked;
			}
		}
		
		const sendCommands = (commands) => {
			commands.forEach(command => sendMessage(command));
		};
		
		const handleKey = (key, value, action) => {
			const elementId = key.split('"')[1];
			const elementValue = value.split(':')[1].replace(/"/g, '').trim();
			if (action === 'active') {
				document.getElementById(elementId).classList[elementValue === "1" ? 'add' : 'remove']('active');
			} else if (action === 'value') {
				updateElement(elementId, elementValue);
			}
		};
		
		function viewDialog(id, action) {
			var dialog = document.getElementById(id);
		
			if (!dialog) {
				if (action !== "remove") {
					console.error("No dialog found with id:", id);
				}
				return;
			}
		
			if (action === "blockingOpen") {
				dialog.showModal();
			} else if (action === "open") {
				dialog.setAttribute("open", "");
				dialog.removeAttribute("close");
			} else if (action === "close") {
				dialog.removeAttribute("open");
				dialog.setAttribute("close", "");
			} else if (action === "remove") {
				dialog.remove();
			} else {
				console.error("Invalid action:", action);
			}
		}
		
		function createDialog({ id, title = '', description = '', action = null, closeEnabled = true }) {
			let dialog = document.getElementById(id);
		
			if (!dialog) {
				dialog = document.createElement('dialog');
				dialog.id = id;
				dialog.classList.add('general-dialog');
		
				if (closeEnabled) {
					const closeButton = document.createElement('button');
					closeButton.textContent = 'X';
					closeButton.classList.add('close-button');
					closeButton.addEventListener('click', () => {
						dialog.close();
					});
					dialog.appendChild(closeButton);
				}
		
				if (title) {
					const dialogTitle = document.createElement('h2');
					dialogTitle.textContent = title;
					dialog.appendChild(dialogTitle);
				}
		
				if (description) {
					const dialogDescription = document.createElement('div');
					dialogDescription.innerHTML = description;
					dialog.appendChild(dialogDescription);
				}
		
				if (action) {
					const dialogAction = document.createElement('button');
					dialogAction.textContent = action.text || 'OK';
					dialogAction.addEventListener('click', action.callback || (() => {}));
					dialog.appendChild(dialogAction);
				}
		
				document.body.appendChild(dialog);
			}
		
			if (!dialog.open) {
				dialog.showModal();
			}
		
			return dialog;
		}
		
		// Process Message Responses
		function processOnMessage(responseText, responseHex) {
			lastMessage = Date.now();
			responseTextParams = responseText.split("\t");
		
			if (responseTextParams[0] != "CTList") {
				logMessage("DEBUG", `WebSocket Message Received: [response]:[${responseText}]`);
			}
		
			switch (responseTextParams[0]) {
				case responseTextParams[0].match(/\[custom\]/)?.input:
					apiResponseCustom(responseText);
					break;
				case responseTextParams[0].match(/\[CHStatus\]/)?.input:
					apiResponseCHStatus(responseText);
					break;
				case "CB":
				case "CC":
				case "CD":
				case "CEError":
				case "CFError":
				case "CFGet":
				case "CH":
				case "CK":
				case "CM":
				case "CNSet":
				case "CPStart":
				case "CQ":
				case "CR":
				case "CTErase":
				case "CTError":
				case "CTSet":
				case "CW":
				case "E":
				case "FB":
				case "FE":
				case "FM":
				case "FR":
				case "FW":
				case "FX":
				case "G":
				case "CLDelete":
				case "CLSetCursor":
					apiResponse(responseText);
					break;
				case responseTextParams[0].match(/CH Mode/)?.input:
					apiResponseCHStatus(responseText);
					break;
				case "C2Config":
					apiResponseC2Config(responseText);
					break;
				case "C2Info":
					apiResponseC2Info(responseText);
					break;
				case "C2Status":
					apiResponseC2Status(responseText);
					break;
				case "C2Wipe":
					apiResponseC2Wipe(responseText);
					break;
				case "CEStatus":
					apiResponseCEStatus(responseText);
					break;
				case responseTextParams[0].match(/CEStore/)?.input:
					apiResponseCEStore(responseText);
					break;
				case responseTextParams[0].match(/CE/)?.input:
				case responseTextParams[0].match(/ce/)?.input:
					apiResponseCE(responseText);
					break;
				case "CFList":
					apiResponseCFList(responseText);
					break;
				case 'CI':
					apiResponseCI(responseText);
					break;
				case responseTextParams[0].match(/CJ/)?.input:
					apiResponseCJ(responseText);
					break;
				case responseTextParams[0].match(/\[dKAll\]/)?.input:
					downloadKeylog(responseText, responseHex);
					break;
				case responseTextParams[0].match(/CLRead/)?.input:
					apiRequestCLRead(responseText);
					break;
				case "CLStart":
					apiResponseCLStart(responseText);
					break;
				case "CLStatus":
					apiResponseCLStatus(responseText);
					break;
				case "CLStop":
					apiResponseCLStop(responseText);
					break;
				case "CNGet":
					apiResponseCNGet(responseText);
					break;
				case "CO":
					apiResponseCO(responseText);
					break;
				case "CPGet":
					apiResponseCPGet(responseText);
					break;
				case responseTextParams[0].match(/CS/)?.input:
					apiResponseCS(responseText);
					break;
				case "CTGet":
					apiResponseCTGet(responseText);
					break;
				case "CTList":
					apiResponseCTList(responseText);
					break;
				case responseTextParams[0].match(/CU/)?.input:
					apiResponseCU(responseText);
					break;
				case "CV":
					apiResponseCV(responseText);
					break;
				case responseTextParams[0].match(/CZ/)?.input:
					apiResponseCZ(responseTextParams[2]);
					break;
				case responseTextParams[0].match(/\[pL.*\]/)?.input:
					apiResponsePayloadLoad(responseTextParams[2]);
					break;
				case responseTextParams[0].match(/\[pS.*\]/)?.input:
					apiResponsePayloadSave(responseText);
					break;
				case responseTextParams[0].match(/\[sB.*\]/)?.input:
					apiResponseStatusBootSlot(responseTextParams[2]);
					break;
				case "CWInfo":
					apiResponseCWInfo(responseText);
					break;
				case "CWStatus":
					apiResponseCWStatus(responseText);
					break;
				case "E":
					break;
				case "FSGet":
					apiResponseFSGet(responseText);
					break;
				case "SFRead":
					apiResponseSFRead(responseText);
					break;
				case "SVStatus":
					apiResponseSVStatus(responseText);
					break;
				case responseTextParams[0].match(/\[.*\]/)?.input:
					logMessage(`CRIT`, `WebSocket MessageID: Unknown ID Handler. [response]:[${responseText}]`)
					break;
				default:
					logMessage(`CRIT`, `WebSocket Message Error: Could not be routed. [response]:[${responseText}]`)
					break;
			}
		}
		
		function issueCustomCommand() {
			const cmd = document.getElementById('customCommandInput').value;
			logMessage('INFO', `issueCustomCommand: [cmd]:[${cmd}]`);
			sendMessage(`[custom]${cmd}`);
		}
		
		function apiResponseCustom(data) {
			data = data.replace("[custom]", "");
			const { response, parameters } = parseData(data);
		
			logMessage('CRIT', `issueCustomCommand: [response]:[${response}] [parameters]:[${parameters}]`);
			updateElementValue('customCommandOutput', parameters, 'value');
		}
		
		let prevDataCTList = null;
		const apiResponseCTList = (data) => {
			if (data === prevDataCTList) {
				let uicolorExists = false;
				let splitData = data.split('\t');
				let listName = splitData[0];
				let jsonData = splitData[1];
				let jsonObject = JSON.parse(jsonData);
				for (let key in jsonObject) {
					if (key == "uicolor") {
						customColor = document.getElementById('uicolorCustom');
						customColor.value = jsonObject[key];
						customColorSlider = document.getElementById('color-slider');
						customColorSlider.value = jsonObject[key];
						uicolor = jsonObject[key];
						setAccentColors(jsonObject[key]);
						uicolorExists = true;
					}
				}
				return;
			}
		
			prevDataCTList = data;
			let uicolorExists = false;
			let splitData = data.split('\t');
			let listName = splitData[0];
			let jsonData = splitData[1];
			let jsonObject = JSON.parse(jsonData);
		
			for (let key in jsonObject) {
				if (key == "bootpair") {
					bootpair = jsonObject[key];
					document.getElementById('bootscriptPair').value = bootpair;
				}
		
				if (key == "bootscript" && jsonObject[key] !== '0') {
					document.getElementById(`toggle-BootScript`).classList.add(`active`);
					document.getElementById(`toggle-BootScriptFileMenu`).classList.add(`active`);
				}
		
				if (key == "bootscript" && jsonObject[key] == '0') {
					document.getElementById(`toggle-BootScript`).classList.remove(`active`);
					document.getElementById(`toggle-BootScriptFileMenu`).classList.remove(`active`);
				}
		
				if (key == "wifimac") {
					wifimac = document.getElementById('device-CustomMACAddress');
					wifimac.value = jsonObject[key];
				}
		
				if (key == "devicename") {
					devicename = document.getElementById('device-name');
					devicename.value = jsonObject[key];
				}
				
				if (key == "hidxhost") {
					hidxhost = document.getElementById('hidxhost');
					hidxhost.value = jsonObject[key];
				}
		
				if (key == "hidxport") {
					hidxport = document.getElementById('hidxport');
					hidxport.value = jsonObject[key];
				}
		
				if (key == "hidxboot") {
					document.getElementById(`hidxboot`).classList.add(`active`);
				}
		
				if (key == "showmsgtime") {
					alwaysDisplayLastPoll = elementValue;
					const toggleElems = ['c2logAlwaysShow'];
					toggleElems.forEach((elemId) => {
						const toggleElem = document.getElementById(elemId);
						if (toggleElem) {
							if (elementValue !== '0') {
								toggleElem.classList.add('active');
							} else {
								toggleElem.classList.remove('active');
							}
						}
					});
				}
		
				if (key == "slotmode") {
					slotmode = jsonObject[key];
				}
		
				if (key == "uicolor") {
					customColor = document.getElementById('uicolorCustom');
					customColor.value = jsonObject[key];
					customColorSlider = document.getElementById('color-slider');
					customColorSlider.value = jsonObject[key];
					setAccentColors(jsonObject[key]);
					uicolorExists = true;
				}
		
				if (key == "usbinterval") {
					usbinterval = document.getElementById('experimentalSpeed');
					usbinterval.value = jsonObject[key];
				}
				
				if (key == "keylogall") {
					if (jsonObject[key] == "1") {
						document.getElementById(`keylogDisplayUnmappedValues`).classList.add(`active`);
					}
				}
			}
		
			if (!uicolorExists) {
				const deviceModel = document.getElementById('device-Model').innerHTML;
				const deviceType = document.getElementById('device-Type').innerHTML;
		
				if (deviceModel == "c" && deviceType == "p") {
					sendMessage(`CTSet\tuicolor\tblue`);
					setAccentColors("blue");
				} else {
					sendMessage(`CTSet\tuicolor\tred`);
					setAccentColors("red");
				}
			}
		}
		
		let globalJson = {};
		let resolveFunction = null;
		
		function apiResponseCFList(data) {
			const { response, parameters } = parseData(data);
			logMessage('DEBUG', `apiResponseCFList: [data]:[${response}]`);
			updateElement('response-CFList', response);
			const json = JSON.parse(parameters[1]);
			Object.assign(globalJson, json);
			if (parameters[3] !== "0") {
				sendMessage(`CFList\t${parameters[3]}`);
				return new Promise((resolve, reject) => {
					resolveFunction = resolve;
				});
			} else {
				processCFListData();
				if (resolveFunction) {
					resolveFunction();
					resolveFunction = null;
				}
				return Promise.resolve();
			}
		}
		
		function processCFListData() {
			const CFListResults = document.getElementById("CFListResults");
			CFListResults.innerHTML = "";
			const payloadList = document.getElementById("payload-list");
			payloadList.innerHTML = "";
			const payloadSelect = document.getElementById("payloadSelect");
			payloadSelect.innerHTML = "";
		
		
			Object.entries(globalJson).forEach(([key, value]) => {
				const label = document.createElement("label");
				label.for = key;
				label.innerText = `cflist: ${key}`;
		
				const name = document.createElement("input");
				name.id = key;
				name.name = parseInt(value[0], 10);
				name.value = value[1];
				CFListResults.appendChild(label);
				CFListResults.appendChild(name);
				if (key.startsWith("payload")) {
					const option = document.createElement("option");
					option.value = key;
					option.innerHTML = key;
					payloadList.appendChild(option);
					payloadSelect.appendChild(option);
				}
			});
		
			let payloadSlotAvailable = 0;
			for (let key in globalJson) {
				if (key.startsWith("bootscript") || key.startsWith("payload") || key.startsWith("exec") || key.startsWith("keylog") || key.startsWith("plcache") || key.startsWith("hidxfile") || key.startsWith("<free>")) {
					payloadSlotAvailable += parseInt(globalJson[key][1]);
				}
			}
			const elementIds = {
				'partitionEditorAvailable': { value: payloadSlotAvailable, method: 'value' },
			};
			setElementValues(elementIds);
			
			updateElement('response-CFList', JSON.stringify(globalJson));
			globalJson = {};
		}
		
		let prevDataCI = null;
		
		function apiResponseCI(data) {
			if (data === prevDataCI) {
				return;
			}
			prevDataCI = data;
		
			const { response, parameters } = parseData(data);
		
			if (parseFloat(parameters[6]) < previousUptime || parameters[1] !== previousMAC) {
				viewDialog('ipAddress', 'remove');
				if (parameters[1] !== previousMAC && previousMAC != null) {
					toggleModalVisibility(true);
		
					const modalLabel = "Warning";
					const 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.`;
		
					setElementValues({
						"modalLabel": modalLabel,
						"modalMessage": modalMessage
					});
		
					setTimeout(function() {
						location.reload(true);
					}, 10000);
				}
		
				const elementIds = {
					'response-CI': { value: response, method: 'value' },
					'device-HardwareMACAddress': { value: parameters[1], method: 'innerHTML' },
					'device-FlashSize': { value: parameters[2], method: 'value' },
					'device-Uptime': { value: parameters[6], method: 'value' },
				};
				setElementValues(elementIds);
		
				const restartReasons = {
					'0': 'Normal Startup By Power On',
					'1': `Hardware Watchdog Reset ${parameters[4]} ${parameters[5]} ${parameters[7]} ${parameters[8]}`,
					'2': `Exception Reset ${parameters[4]} ${parameters[5]} ${parameters[7]} ${parameters[8]}`,
					'3': `Software Watchdog Reset ${parameters[4]} ${parameters[5]} ${parameters[7]} ${parameters[8]}`,
					'4': 'Software Restart',
					'5': 'Wake from Deep-Sleep',
					'6': 'External System Reset',
				};
		
				const restartReason = restartReasons[parameters[3]] || 'Unknown Restart Reason';
				logMessage('INFO', `Connection Established: [Uptime]:[${parameters[6]} seconds - ${restartReason}]`, 'Connection Established');
				viewDialog(`partitionApplied`, `close`);
		
				previousUptime = parseFloat(parameters[6]);
				previousMAC = parameters[1];
			}
		}
		
		let prevDataCNGet = null;
		
		function apiResponseCNGet(data) {
			if (data === prevDataCNGet) {
				return;
			}
			prevDataCNGet = data;
		
			const { response, parameters } = parseData(data);
			logMessage('CRIT', `apiResponseCNGet: [data]:[${response}]`);
		
			const elementIds = {
				'response-CNGet': { value: response, method: 'value' },
				'device-USBVID': { value: parameters[1], method: 'value' },
				'device-USBPID': { value: parameters[2], method: 'value' },
				'device-USBMAN': { value: parameters[3], method: 'value' },
				'device-USBPRO': { value: parameters[4], method: 'value' },
				'device-USBSER': { value: parameters[5], method: 'value' },
			};
		
			setElementValues(elementIds);
		}
		
		let intervalIdCPGet = null;
		
		function apiResponseCPGet(data) {
			const { response, parameters } = parseData(data);
			const isCPGet = response === "CPGet\t";
		
			setElementValues({
				'response-CPGet': isCPGet ? '' : response,
				'calibrationStatusViewer': isCPGet ? 'Generating Calibration Data. Please Wait.' : response
			});
		
			if (isCPGet && !intervalIdCPGet) {
				intervalIdCPGet = setInterval(() => sendMessage('CPGet'), 1000);
			} else if (!isCPGet && intervalIdCPGet) {
				clearInterval(intervalIdCPGet);
				intervalIdCPGet = null;
			}
		
			logMessage('CRIT', `apiResponseCPGet: [data]:[${response}]`);
		}
		
		async function readCalibrationText(event) {
			const file = event.target.files.item(0);
			const text = await file.text();
			calibrationData(text);
		}
		
		function calibrationData(data) {
			logMessage(`CRIT`, `CalibrationData: [file]:[${data}]`);
			sendMessage(`CO${data}`);
		}
		
		function apiResponseCO(data) {
			const { response, parameters } = parseData(data);
		
			toggleModalVisibility(true);
		
			const isSuccess = params[1] === "ok";
			const modalLabel = isSuccess ? "Calibration Success" : "Calibration Failed";
			const modalMessage = isSuccess ?
				`Calibration Succeeded. Please unplug and replug your O.MG Device to apply configuration.` :
				`Log Data: <${params}>`;
		
			setElementInnerHTML("modalLabel", modalLabel);
			setElementInnerHTML("modalMessage", modalMessage);
		
			if (isSuccess) {
				logMessage(`INFO`, `Calibration Succeeded. Please unplug and replug your O.MG Cable to apply configuration. Response: ${params}`);
				sendMessage(`CR1`);
			} else {
				logMessage(`INFO`, `Calibration Failed. Response: ${params}`);
			}
		
			parameters = response.split("\t");
			logMessage(`CRIT`, `apiResponseCO: [data]:[${response}]`);
			setElementInnerHTML("response-CO", response);
		}
		
		let prevDataCTGet = null;
		
		function apiResponseCTGet(data) {
			if (data === prevDataCTGet) {
				return;
			}
			prevDataCTGet = data;
		
			const { response, parameters } = parseData(data);
			logMessage('CRIT', `apiResponseCTGet: [data]:[${response}]`);
			setElementValues({ 'response-CTGet': response });
		
			const settingKey = parameters[1];
			const settingValue = parameters[2];
		
			const settingsMap = {
				wifimac: 'device-CustomMACAddress',
				uicolor: setAccentColors
			};
		
			if (settingsMap.hasOwnProperty(settingKey)) {
				const action = settingsMap[settingKey];
				typeof action === 'function' ? action(settingValue) : setElementValues({
					[action]: settingValue
				});
			} else {
				logMessage('CRIT', `CTGet does not have a handler for this setting. [key]:[${settingKey}] [value]:[${settingValue}]`);
			}
		}
		
		const apiResponseCV = (() => {
			previousData = null;
		
			return (data) => {
				if (data !== previousData) {
					const response = data;
					const parameters = response.split("\t");
					logMessage(`DIAG`, `apiResponseCV: [data]:[${response}]`);
		
					viewDialog('loadingScreen', 'close');
		
					const type = parameters[2].charAt(0);
					const model = parameters[2].charAt(1);
					const calibrationStatus = parameters[3].charAt(0);
		
					const typeData = processType(type);
					const modelData = processModel(model);
					
					if(type == "a") {
						activeEndLabel = "the USB-A side of your O.MG Cable";
					} else if (type == "b") {
						activeEndLabel = "the USB-A Plug of your O.MG UnBlocker";
					} else if (type == "d") {
						activeEndLabel = "your O.MG Plug";
					} else if (type == "c" || type == "d") {
						activeEndLabel = "the USB-C side of your O.MG Cable";				
					} else if (type == "t") {
						activeEndLabel = "the USB-C side of your O.MG Adapter";				
					} else {
						activeEndLabel = "O.MG Device";
					}
		
					applyFeatureFlags(type, model);
		
					if (['c', 'k', 'd', 'e'].includes(model)) {
						toggleClass('contentSubNav_payload', 'hidden', 'visible');
					}
		
					if (model === 'k' || model === 'e') {
						toggleClass('contentSubNav_keylog', 'hidden', 'visible');
					}
		
					if (calibrationStatus === '?' || calibrationStatus === '-' || calibrationStatus === 'l' || calibrationStatus === '') {
						updateElementValue('modelText', 'ERROR', 'innerHTML');
						contentAreaToggle('syslog');
						toggleClass('contentSubNav_payload', 'visible', 'hidden');
						updateElementValue('navbarAlerts', '<button onclick="viewDialog(`calibrationerror`, `open`);">Calibration Error</button>', 'innerHTML');
						updateElementValue('device-TypeDecoded', 'O.MG Device', 'innerHTML');
						
						if(calibrationStatus === 'l') {
							updateElementValue('device-CalibrationStatusDecoded', 'Legacy Calibration', 'innerHTML');
							logMessage(`CRIT`, '! ALERT: Device calibration incompatible with new firmware. You can initiate a free calibration upgrade, or downgrade to v1 firmware.');
							calibrationDescriptionText = '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>';
						} else {
							updateElementValue('device-CalibrationStatusDecoded', 'Calibration Error, Contact Support', 'innerHTML');
							logMessage(`CRIT`, '! ALERT: Device calibration failure. Please contact support.');
							calibrationDescriptionText = '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>';
						}
						
						createDialog({
							id: 'calibrationerror',
							title: 'Calibration Error',
							description: `${calibrationDescriptionText}<br /><br />
							<h2>Tools</h2>
							<button class="submitButton" id="sidebar_settings_debug_CPStart" onclick="sendMessage('CPStart'); sendMessage('CPGet'); this.remove();">Generate Hardware Profile</button>
							<div id="calibrationStatusViewer"><br /><button class="submitButton" id="calibrationErrorDownloadButton" onclick="downloadFrontendlog();">Download Frontend Log</button></div>
							<form>
								<h2>Apply Calibration File:</h2>
								<input type="file" id="calibration" name="calibration" accept=".cal" onchange="readCalibrationText(event)">
								<div class="message-body">
									<p id="omg-calibration-text"></p>
									<p id="omg-calibration-log"></p>
								</div>
							</form>`,
							closeEnabled: true,
						});
		
						featureFlags = ['global', 'syslog', 'debug'];
						hideElementsByClass('payload', 'keylog', 'c2', 'hidx');
						toggleFeatureFlags();
					} else {
						const elementIds = {
							'response-CV': { value: response, method: 'value' },
							'frontend-VersionNumber': { value: frontendVersionNumber, method: 'innerHTML' },
							'firmwareVersionNumberHumanReadableString': { value: convertVersionToDate(frontendMinorVersion), method: 'innerHTML' },
							'device-VersionNumber': { value: parameters[1], method: 'innerHTML' },
							'device-Model': { value: parameters[2].charAt(0), method: 'innerHTML' },
							'device-Type': { value: parameters[2].charAt(1), method: 'innerHTML' },
							'device-CalibrationStatus': { value: parameters[3], method: 'value' },
							'device-TypeDecoded': { value: typeData[0] + ' ' + modelData[0], method: 'innerHTML' },
							'modelText': { value: modelData[1], method: 'innerHTML' },
							'device-CalibrationStatusDecoded': { value: 'Good', method: 'innerHTML' },
						};
						setElementValues(elementIds);
						fccID = parameters[4];
					}
					toggleFeatureFlags();
		
					previousData = data;
				}
			};
		})();
		
		function apiResponseFSGet(data) {
			const { response, parameters } = parseData(data);
			if (parameters[1] == "0") {
				logMessage(`INFO`, `First Time Setup Wizard: Not Activated`);
				debugLevel = 'NONE';
				viewDialog('firstTimeSetup', 'blockingOpen');
			}
		}
		
		let prevDataCWInfo = null;
		
		function apiResponseCWInfo(data) {
			if (data === prevDataCWInfo) {
				return;
			}
			prevDataCWInfo = data;
			const { response, parameters } = parseData(data);
			logMessage(`DIAG`, `apiResponseCWInfo: [data]:[${response}]`);
		
			const elementIds = {
				'response-CWInfo': { value: response, method: 'value' },
				'device-WIFIMode': { value: parameters[0], method: 'value' },
				'device-WIFISSID': { value: parameters[2], method: 'value' },
				'device-WIFIPassword': { value: parameters[3], method: 'value' },
				'device-WIFIChannel': { value: parameters[4], method: 'value' },
			};
			setElementValues(elementIds);
		
			if (parameters[1] === '2') {
				setRadioChecked('CW0');
			} else if (parameters[1] === '1') {
				setRadioChecked('CW1');
			} else {
				setRadioChecked('CW0');
			}
		}
		
		let prevDataSVStatus = null;
		function apiResponseSVStatus(data) {
			if (data === prevDataSVStatus) {
				return;
			}
			prevDataSVStatus = data;
			const { response, parameters } = parseData(data);
		
			logMessage(`CRIT`, `apiResponseSVStatus: [data]:[${response}]`);
			
			const jsonChunk = JSON.parse(parameters[1]);
		
			if (jsonChunk.hasOwnProperty('usb')) {
				const usbStatus = jsonChunk.usb.state;
				switch (usbStatus) {
					case 'Detached':
						document.getElementById(`toggle-CU`).classList.remove(`active`);
						document.getElementById(`toggle-CJ`).classList.remove(`active`);
						break
					case 'NoHost':
						document.getElementById(`toggle-CU`).classList.add(`active`);
						createDialog({
							id: 'usbenumeration',
							title: 'Host not detected',
							description: `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>`,
							closeEnabled: true,
						});
						break
					case 'Enumerated':
						document.getElementById(`toggle-CU`).classList.add(`active`);
						break
					case 'Err-Default':
						document.getElementById(`toggle-CU`).classList.add(`active`);
						break
					case 'Err-Address':
						document.getElementById(`toggle-CU`).classList.add(`active`);
						break
					case 'Suspended':
						document.getElementById(`toggle-CU`).classList.add(`active`);
						break
					default:
						logMessage(`INFO`, `! ERROR: USB Enumeration Status Unexpected.`);
						break
				}
			}
			if (jsonChunk.hasOwnProperty('jiggler')) {
				const jigglerStatus = jsonChunk.jiggler.state;
				switch (jigglerStatus) {
					case 'disabled':
						document.getElementById(`toggle-CJ`).classList.remove(`active`);
						break
					case 'enabled':
						document.getElementById(`toggle-CJ`).classList.add(`active`);
						break
					default:
						logMessage(`INFO`, `! ERROR: Jiggler Status Unexpected.`);
						break
				}
			}
			if (jsonChunk.hasOwnProperty('kl')) {
				const klStatus = jsonChunk.kl.state;
				const footerNavKeylogStatus = document.getElementById('footerNavKeylogStatus');
				const klButtonText = document.getElementById('keylogStartButton');
		
				switch (klStatus) {
					case 'active':
						footerNavKeylogStatus.classList.add('buttonSelected');
						klButtonText.innerHTML = 'Stop';
						break
					case 'disabled':
						footerNavKeylogStatus.classList.remove('buttonSelected');
						klButtonText.innerHTML = 'Start';
						break
					default:
						logMessage(`INFO`, `! ERROR: Keylog Status Unexpected.`);
						break
				}
			}
		}
		
		let prevDataCWStatus = null;
		
		function apiResponseCWStatus(data) {
			if (data === prevDataCWStatus) {
				return;
			}
			prevDataCWStatus = data;
			const { response, parameters } = parseData(data);
		
			ipAddressSplit = parameters[2].match(/.{1,2}/g);
			ipAddressDecimal = ipAddressSplit.reverse().map(hex => parseInt(hex, 16)).join('.');
		
			logMessage(`DEBUG`, `apiResponseCWStatus: [data]:[${response}]`);
		
			const elementIds = {
				'response-CWStatus': { value: response, method: 'value' },
				'device-IPAddress': { value: ipAddressDecimal, method: 'innerHTML' },
			};
			setElementValues(elementIds);
		}
		
		function updateUSBIDs() {
			const usbCommands = [
				`CTSet\tusbvid\t${document.getElementById("device-USBVID").value}`,
				`CTSet\tusbpid\t${document.getElementById("device-USBPID").value}`,
				`CTSet\tusbman\t${document.getElementById("device-USBMAN").value}`,
				`CTSet\tusbpro\t${document.getElementById("device-USBPRO").value}`,
				`CTSet\tusbser\t${document.getElementById("device-USBSER").value}`
			];
		
			sendCommands(usbCommands);
		
			logMessage(`INFO`, `! USB Device IDs Changed, Device will reboot.`);
			logMessage(`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}]`);
		
			sendMessage(`CR1`);
		}
		
		function updateWifiSettings() {
			let mode = document.getElementById("CW1").checked ? 1 : 2;
			const wifiCommands = [
				`CTSet\twifimode\t${mode}`,
				`CTSet\twifissid\t${document.getElementById("device-WIFISSID").value}`,
				`CTSet\twifikey\t${document.getElementById("device-WIFIPassword").value}`,
				`CTSet\twifimac\t${document.getElementById("device-CustomMACAddress").value}`,
				`CTSet\tdevicename\t${document.getElementById("device-name").value}`,
				`CR1`
			];
		
			sendCommands(wifiCommands);
		
			logMessage(`CRIT`, `ChangeWifiConfig: [cmd]:[${wifiCommands[0]}]:[${wifiCommands[1]}]`);
			logMessage(`INFO`, `! WiFi Config Changed, Device will now reboot!`);
		}
		
		function createLabelAndInput(labelText, commandName, idPrefix, parentId) {
			return new Promise((resolve) => {
				const parentElement = document.getElementById(parentId);
		
				if (!document.getElementById(`${idPrefix}${commandName}`)) {
					const labelName = document.createElement("label");
					labelName.htmlFor = `${idPrefix}${commandName}`;
					labelName.innerHTML = labelText;
					parentElement.appendChild(labelName);
		
					const inputName = document.createElement("input");
					inputName.id = `${idPrefix}${commandName}`;
					parentElement.appendChild(inputName);
				}
				resolve();
			});
		}
		
		async function generateDebugMenu() {
			const keylogCmdHandlerList = ['LiveLogStatus', 'Status', 'CaptureMode', 'SpaceUsed', 'SpaceFree', 'DataWaiting', 'DataLost', 'LastReportID', 'NextReportID', 'USBPkts', 'USBLost', 'USBHolds', 'USBBursts'];
			
			const defaultCmdHandlerList = enabledFeatures;
		
			const mandatoryFields = ['CI', 'CV', 'CWInfo', 'CNGet', 'CWStatus', 'C2Config', 'C2Info', 'C2Status', 'C2Wipe'];
			for (let field of mandatoryFields) {
				await createLabelAndInput(field, field, 'response-', 'contentArea_debug');
			}
		
			const defaultCmdPromises = defaultCmdHandlerList.map((commandName) => {
				if (!mandatoryFields.includes(commandName)) {
					return createLabelAndInput(commandName, commandName, 'response-', 'contentArea_debug');
				}
			});
		
			const keylogCmdPromises = keylogCmdHandlerList.map((commandName) => {
				return createLabelAndInput(`Keylogger ${commandName}`, commandName, 'keylogger-', 'keyloggerStatus');
			});
		
			await Promise.all([...defaultCmdPromises, ...keylogCmdPromises]);
		}
		
		// Logger Module
		let currentToast = null; // Keep track of the current toast
		function logMessage(level, msg, toastOnly) {
			const debugLevels = {
				'NONE': 0,
				'INFO': 1,
				'WARN': 2,
				'CRIT': 3,
				'DIAG': 4,
				'DEBUG': 5
			};
		
			const now = new Date();
			const datetime = `${now.toLocaleDateString()} ${now.toLocaleTimeString()} ${now.getMilliseconds().toString().padStart(3, '0')}ms`;
			const debugLevel = document.getElementById('debugLevel').value;
		
			if (level !== "DEBUG" || level !== "NONE") {
				document.getElementById("debuglog").innerHTML += `LOG ${level} (${datetime}): ${msg}\n`;
			}
		
			if (debugLevels[level] <= debugLevels[debugLevel] && level !== "NONE") {
				document.getElementById("syslog").innerHTML += `[${level}]\t${msg}\n`;
		
				if (level === "INFO" && !msg.includes("onMessage")) {
					const displayMessage = toastOnly || msg;
		
					if (currentToast && document.body.contains(currentToast)) {
						// Update existing toast
						currentToast.innerHTML = displayMessage;
						
						// Refresh the toast's z-index to bring it to front
						currentToast.style.zIndex = zIndexCounter++;
						
						// Reset the timeout
						clearTimeout(currentToast.timeoutId);
						currentToast.timeoutId = setTimeout(() => {
							if (document.body.contains(currentToast)) {
								document.body.removeChild(currentToast);
								currentToast = null;
							}
						}, 3000);
					} else {
						// Create new toast
						const toast = document.createElement('div');
						toast.className = 'toast';
						toast.innerHTML = displayMessage;
						toast.style.zIndex = zIndexCounter++;
		
						const removeToast = () => {
							if (document.body.contains(toast)) {
								document.body.removeChild(toast);
								currentToast = null;
							}
						};
		
						const timeoutId = setTimeout(removeToast, 3000);
						toast.timeoutId = timeoutId; // Store the timeout ID on the toast element
						
						toast.onclick = () => {
							clearTimeout(timeoutId);
							removeToast();
						};
		
						document.body.appendChild(toast);
						currentToast = toast;
					}
				}
			}
		}
		
		// Payload Module
		function apiResponseCE(data) {}
		previousData = '';
		var iterations;
		split_payloads = "";
		compiledBootSlot = "";
		cePayload = "";
		var compiledBootSlot = "";
		var defaultDelay = "0";
		var defaultDelayJitter = "0";
		var defaultCharDelay = "0";
		var defaultCharDelayJitter = "0";
		var compileFailed = "0";
		
		function getPayload() {
			payloadElement = document.getElementById("payload");
			unescapedPayloadElement = unescapeHtml(payloadElement.value);
			randomDelay = Math.floor(Math.random() * 100) + 1;
			unescapedPayloadElement += `\nDELAY ${randomDelay}`;
			return unescapedPayloadElement;
		}
		
		function processPayload(payload) {
			updateLineNumbers();
			let originalPayload = payload;
			let payloadLines = payload.split('\n');
			try {
				payload = sortLines(payload);
				payload = replaceBlocksWithLines(payload);
				payload = replaceFunctions(payload);
				payload = removeComments(payload);
				payload = replaceRepeat(payload);
				payload = replaceDefines(payload);
				payload = applyDelays(payload);
				payload = normalizeCommands(payload);
				logMessage(`CRIT`, `Compiling Payload - Intermediate Parsing: [script]:[${payload}]`);
				return payload;
			} catch (err) {
				let payloadLinesAfter = payload.split('\n');
				for (let i = 0; i < payloadLines.length; i++) {
					if (payloadLines[i] !== payloadLinesAfter[i]) {
						console.error(`Error on line ${i + 1}: ${payloadLines[i]}`);
						break;
					}
				}
				console.error(err);
				throw err;
			}
		}
		
		const 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"]);
		
		function normalizeCommands(payload) {
			return payload.split('\n').map(line => {
				let [command, ...rest] = line.split(' ');
				if (!SPECIAL_COMMANDS.has(command.toUpperCase())) {
					rest = rest.map(item => item.toUpperCase());
				}
				return [command.toUpperCase(), ...rest].join(' ');
			}).join('\n');
		}
		
		function runDuckyScriptByLine() {
			payloadCompiled = compileDuckyScriptByLine();
		}
		
		function compileDuckyScriptByLine() {
			let payloadScript = getPayload();
			let payloadLines = payloadScript.split('\n');
			let payloadCompiled = "";
			console.log(payloadScript);
		
			for (let i = 0; i < payloadLines.length; i++) {
				let originalLine = payloadLines[i];
				try {
					let processedLine = processPayload(originalLine);
					let compiledLine = duckyscriptToBytecodeConverter(cleanInvisibles(processedLine));
					if (compiledLine !== undefined) {
						payloadCompiled += compiledLine + '\n';
					}
		
					console.log(`Compile Line ${i+1}: [input][${originalLine}] [output][${compiledLine}]`);
				} catch (err) {
					console.error(`Error on line ${i + 1}: ${originalLine}`);
					console.error(err);
					throw err;
				}
			}
		
			payloadCompiled = payloadCompiled.replace(/\s/g, '');
			payloadCompiled = payloadCompiled.toUpperCase();
		
			let payload1Size = document.getElementById("payload1") ? document.getElementById("payload1").value : '1';
			let calculatedPercent = (((payloadCompiled.length / 6) / (payload1Size * 4096)) * 100).toFixed(0);
			let payloadStoragePercent = Math.min(calculatedPercent, 100) + '%';
			document.getElementById('payloadStorageUsedBar').style.width = payloadStoragePercent;
			document.getElementById('payloadStoragePercent').innerHTML = payloadStoragePercent;
			payloadCompiled = split_process(payloadCompiled);
		
			console.log(payloadCompiled);
			return payloadCompiled;
		}
		
		function compileDuckyScript() {
			payloadIntermediate = "";
			payloadScript = "";
			payloadLines = "";
			payloadCompiled = "";
			line = "";
		
			payloadScript = getPayload();
			const originalPayloadLines = payloadScript.split('\n');
			payloadScript = processPayload(payloadScript);
		
			payloadLines = payloadScript.split('\n');
			payloadLines = payloadLines.map(cleanInvisibles);
		
			try {
				payloadCompiled = payloadLines.map((line, index) => {
					try {
						return duckyscriptToBytecodeConverter(line);
					} catch (error) {
						console.error(`Error on processed line ${index + 1}: ${error.message}`);
						const originalLineIndex = originalPayloadLines.findIndex(originalLine =>
							originalLine.toLowerCase().includes(line.toLowerCase())
						);
						if (originalLineIndex !== -1) {
							logMessage(`INFO`, `Payload Compiler Error: Line ${originalLineIndex + 1} - ${originalPayloadLines[originalLineIndex]}. [${error.message}]`);
							setEditorLineColors(`${originalLineIndex + 1}`, `red`);
						} else {
							console.error(`Could not find original line for processed line ${index + 1}`);
						}
						throw error;
					}
				}).join('\n');
			} catch (error) {
				throw error;
			}
		
			payloadCompiled = payloadCompiled.replace(/\s/g, '');
			payloadCompiled = payloadCompiled.toUpperCase();
			crc16 = crc16augccitt(payloadCompiled);
		
			let payload1Size = document.getElementById("payload1") ? document.getElementById("payload1").value : '1';
			let calculatedPercent = (((payloadCompiled.length / 6) / (payload1Size * 4096)) * 100).toFixed(0);
			let payloadStoragePercent = Math.min(calculatedPercent, 100) + '%';
			document.getElementById('payloadStorageUsedBar').style.width = payloadStoragePercent;
			documen
Download .txt
gitextract_edbq2fj2/

├── .gitignore
├── LICENSE
├── README.md
├── c2server/
│   ├── c2server.py
│   ├── index.html
│   └── requirements.txt
├── firmware/
│   └── page.mpfs
├── flash.py
├── hashes.txt
├── scripts/
│   ├── flashapi.py
│   ├── miniterm.py
│   └── pager.py
└── tools/
    └── HIDX/
        ├── powershell/
        │   ├── minify.ps1
        │   ├── win-hidexfil.ps1
        │   └── win-hidshell.ps1
        ├── python/
        │   ├── hidxshell.py
        │   ├── linux-nativeshell.py
        │   ├── stealthlink-client-universal.py
        │   └── stealthlink-host-universal.py
        └── shell/
            └── linux-hidexfil.sh
Download .txt
SYMBOL INDEX (375 symbols across 9 files)

FILE: c2server/c2server.py
  function get_ip_address (line 20) | def get_ip_address():
  function write_c2log (line 40) | def write_c2log(alias, direction, msg):
  class c2server (line 45) | class c2server(BaseHTTPRequestHandler):
    method end_headers (line 46) | def end_headers(self):
    method do_GET (line 53) | def do_GET(self):
    method do_POST (line 68) | def do_POST(self):
  class c2admin (line 79) | class c2admin(SimpleHTTPRequestHandler):
    method end_headers (line 80) | def end_headers(self):
    method handle_C2admin (line 86) | def handle_C2admin(self):
    method handle_C2log (line 94) | def handle_C2log(self, alias=None, abridge=True):
    method do_GET (line 116) | def do_GET(self):
    method do_POST (line 133) | def do_POST(self):
  class Host (line 232) | class Host:
    class ClientData (line 237) | class ClientData:
      method __init__ (line 238) | def __init__(self, client_id, alias, client_exchange_key, cmd_queue):
    class SessionData (line 257) | class SessionData:
      method __init__ (line 258) | def __init__(self, session_id, session_key, client_id, device_id, ex...
    method __init__ (line 268) | def __init__(self, host_private_key, host_public_key):
    method provision (line 284) | def provision(self, alias, poll_seconds, fast_seconds, contact_seconds):
    method make_err_msg (line 318) | def make_err_msg(self, err_code, err_text):
    method handle_hello (line 323) | def handle_hello(self, hello_msg, client):
    method handle_poll (line 363) | def handle_poll(self, poll_msg, session):
    method handle_response (line 403) | def handle_response(self, command_response, session):
    method handle_error (line 431) | def handle_error(self, err_msg, session):
    method process_message (line 434) | def process_message(self, raw_msg, raw_len):
  class HelloResponse (line 469) | class HelloResponse:
    method __init__ (line 470) | def __init__(self, session_id, session_key, expiry, poll_seconds, fast...
  class CommandMsg (line 479) | class CommandMsg:
    method __init__ (line 480) | def __init__(self, seq, command, is_more):
  class MsgWrapper (line 490) | class MsgWrapper:
    method __init__ (line 491) | def __init__(self, *args):
    method encrypt (line 520) | def encrypt(self, key):
    method decrypt (line 527) | def decrypt(self, key):
    method secure_msg (line 537) | def secure_msg(self):
  function log (line 552) | def log(err_text):
  function gen_random (line 556) | def gen_random(size):
  class c2config (line 560) | class c2config:
    method __init__ (line 561) | def __init__(self, host_public_key, host_private_key, host_url, host_p...
    method save_provision_files (line 579) | def save_provision_files(self):
    method load_provision_files (line 598) | def load_provision_files(self):
  function validate_arguments (line 620) | def validate_arguments(argv):

FILE: flash.py
  function omg_tos (line 81) | def omg_tos():
  function omg_dependency_imports (line 234) | def omg_dependency_imports():
  function handler (line 269) | def handler(signal_received, frame):
  class omg_results (line 274) | class omg_results():
    method __init__ (line 275) | def __init__(self):
  function get_dev_info (line 294) | def get_dev_info(dev):
  function ask_for_flasherhwver (line 305) | def ask_for_flasherhwver():
  function ask_for_port (line 322) | def ask_for_port():
  function omg_flash (line 404) | def omg_flash(command,tries=2):
  function complete (line 432) | def complete(statuscode, message="Press Enter to continue..."):
  function make_request (line 436) | def make_request(url):
  function get_resource_file (line 452) | def get_resource_file(url,params=None,data_type='text'):
  function get_release_data (line 475) | def get_release_data():
  function omg_fetch_latest_firmware (line 497) | def omg_fetch_latest_firmware(create_dst_dir=False,dst_dir="./firmware"):
  function omg_locate (line 539) | def omg_locate():
  function omg_probe (line 617) | def omg_probe():
  function omg_reset_settings (line 638) | def omg_reset_settings():
  function omg_patch (line 649) | def omg_patch(_ssid, _pass, _mode, slotsize=4, percent=60):
  function omg_input (line 682) | def omg_input():
  function omg_flashfw (line 785) | def omg_flashfw(mac=None,flash_size=None):
  function omg_runflash (line 807) | def omg_runflash(pre_erase=False,skip_flash=False,skip_input=False,skip_...
  function get_script_path (line 840) | def get_script_path():

FILE: scripts/flashapi.py
  function timeout_per_mb (line 76) | def timeout_per_mb(seconds_per_mb, size_bytes):
  function check_supported_function (line 87) | def check_supported_function(func, check_func):
  function stub_function_only (line 98) | def stub_function_only(func):
  function stub_and_esp32_function_only (line 102) | def stub_and_esp32_function_only(func):
  function byte (line 109) | def byte(bitstr, index):
  function byte (line 112) | def byte(bitstr, index):
  function _mask_to_shift (line 121) | def _mask_to_shift(mask):
  function esp8266_function_only (line 129) | def esp8266_function_only(func):
  class ESPLoader (line 133) | class ESPLoader(object):
    method __init__ (line 185) | def __init__(self, port=DEFAULT_PORT, baud=ESP_ROM_BAUD, trace_enabled...
    method _set_port_baudrate (line 201) | def _set_port_baudrate(self, baud):
    method detect_chip (line 208) | def detect_chip(port=DEFAULT_PORT, baud=ESP_ROM_BAUD, connect_mode='de...
    method read (line 225) | def read(self):
    method write (line 228) | def write(self, packet):
    method trace (line 235) | def trace(self, message, *format_args):
    method checksum (line 248) | def checksum(data, state=ESP_CHECKSUM_MAGIC):
    method command (line 257) | def command(self, op=None, data=b"", chk=0, wait_response=True, timeou...
    method check_command (line 289) | def check_command(self, op_description, op=None, data=b'', chk=0, time...
    method flush_input (line 304) | def flush_input(self):
    method sync (line 308) | def sync(self):
    method _setDTR (line 314) | def _setDTR(self, state):
    method _setRTS (line 317) | def _setRTS(self, state):
    method _connect_attempt (line 322) | def _connect_attempt(self, mode='default_reset', esp32r0_delay=False):
    method connect (line 364) | def connect(self, mode='default_reset'):
    method read_reg (line 383) | def read_reg(self, addr):
    method write_reg (line 390) | def write_reg(self, addr, value, mask=0xFFFFFFFF, delay_us=0):
    method update_reg (line 394) | def update_reg(self, addr, mask, new_val):
    method mem_begin (line 403) | def mem_begin(self, size, blocks, blocksize, offset):
    method mem_block (line 419) | def mem_block(self, data, seq):
    method mem_finish (line 424) | def mem_finish(self, entrypoint=0):
    method flash_begin (line 436) | def flash_begin(self, size, offset):
    method flash_block (line 452) | def flash_block(self, data, seq, timeout=DEFAULT_TIMEOUT):
    method flash_encrypt_block (line 459) | def flash_encrypt_block(self, data, seq, timeout=DEFAULT_TIMEOUT):
    method flash_finish (line 466) | def flash_finish(self, reboot=False):
    method run (line 471) | def run(self, reboot=False):
    method flash_id (line 476) | def flash_id(self):
    method parse_flash_size_arg (line 480) | def parse_flash_size_arg(self, arg):
    method run_stub (line 487) | def run_stub(self, stub=None):
    method flash_defl_begin (line 514) | def flash_defl_begin(self, size, compsize, offset):
    method flash_defl_block (line 534) | def flash_defl_block(self, data, seq, timeout=DEFAULT_TIMEOUT):
    method flash_defl_finish (line 539) | def flash_defl_finish(self, reboot=False):
    method flash_md5sum (line 547) | def flash_md5sum(self, addr, size):
    method change_baud (line 561) | def change_baud(self, baud):
    method erase_flash (line 572) | def erase_flash(self):
    method erase_region (line 578) | def erase_region(self, offset, size):
    method read_flash (line 587) | def read_flash(self, offset, length, progress_fn=None):
    method flash_spi_attach (line 619) | def flash_spi_attach(self, hspi_arg):
    method flash_set_parameters (line 627) | def flash_set_parameters(self, size):
    method run_spiflash_command (line 637) | def run_spiflash_command(self, spiflash_command, data=b"", read_bits=0):
    method read_status (line 716) | def read_status(self, num_bytes=2):
    method write_status (line 728) | def write_status(self, new_status, num_bytes=2, set_non_volatile=False):
    method get_crystal_freq (line 749) | def get_crystal_freq(self):
    method hard_reset (line 760) | def hard_reset(self):
    method soft_reset (line 765) | def soft_reset(self, stay_in_bootloader):
  class ESP8266ROM (line 785) | class ESP8266ROM(ESPLoader):
    method get_efuses (line 817) | def get_efuses(self):
    method get_chip_description (line 824) | def get_chip_description(self):
    method get_chip_features (line 829) | def get_chip_features(self):
    method flash_spi_attach (line 835) | def flash_spi_attach(self, hspi_arg):
    method flash_set_parameters (line 842) | def flash_set_parameters(self, size):
    method chip_id (line 847) | def chip_id(self):
    method read_mac (line 853) | def read_mac(self):
    method get_erase_size (line 868) | def get_erase_size(self, offset, size):
    method override_vddsdio (line 883) | def override_vddsdio(self, new_voltage):
  class ESP8266StubLoader (line 887) | class ESP8266StubLoader(ESP8266ROM):
    method __init__ (line 891) | def __init__(self, rom_loader):
    method get_erase_size (line 896) | def get_erase_size(self, offset, size):
  class ESP32ROM (line 903) | class ESP32ROM(ESPLoader):
    method is_flash_encryption_key_valid (line 938) | def is_flash_encryption_key_valid(self):
    method get_flash_crypt_config (line 953) | def get_flash_crypt_config(self):
    method get_chip_description (line 965) | def get_chip_description(self):
    method get_chip_features (line 979) | def get_chip_features(self):
    method read_efuse (line 1024) | def read_efuse(self, n):
    method chip_id (line 1028) | def chip_id(self):
    method read_mac (line 1031) | def read_mac(self):
    method get_erase_size (line 1041) | def get_erase_size(self, offset, size):
    method override_vddsdio (line 1044) | def override_vddsdio(self, new_voltage):
  class ESP32StubLoader (line 1067) | class ESP32StubLoader(ESP32ROM):
    method __init__ (line 1072) | def __init__(self, rom_loader):
  class ESPBOOTLOADER (line 1081) | class ESPBOOTLOADER(object):
  function LoadFirmwareImage (line 1087) | def LoadFirmwareImage(chip, filename):
  class ImageSegment (line 1102) | class ImageSegment(object):
    method __init__ (line 1103) | def __init__(self, addr, data, file_offs=None):
    method copy_with_new_addr (line 1111) | def copy_with_new_addr(self, new_addr):
    method split_image (line 1114) | def split_image(self, split_len):
    method __repr__ (line 1123) | def __repr__(self):
    method pad_to_alignment (line 1129) | def pad_to_alignment(self, alignment):
  class ELFSection (line 1133) | class ELFSection(ImageSegment):
    method __init__ (line 1134) | def __init__(self, name, addr, data):
    method __repr__ (line 1138) | def __repr__(self):
  class BaseFirmwareImage (line 1142) | class BaseFirmwareImage(object):
    method __init__ (line 1146) | def __init__(self):
    method load_common_header (line 1152) | def load_common_header(self, load_file, expected_magic):
    method verify (line 1159) | def verify(self):
    method load_segment (line 1163) | def load_segment(self, f, is_irom_segment=False):
    method warn_if_unusual_segment (line 1175) | def warn_if_unusual_segment(self, offset, size, is_irom_segment):
    method maybe_patch_segment_data (line 1180) | def maybe_patch_segment_data(self, f, segment_data):
    method save_segment (line 1202) | def save_segment(self, f, segment, checksum=None):
    method read_checksum (line 1210) | def read_checksum(self, f):
    method calculate_checksum (line 1215) | def calculate_checksum(self):
    method append_checksum (line 1222) | def append_checksum(self, f, checksum):
    method write_common_header (line 1227) | def write_common_header(self, f, segments):
    method is_irom_addr (line 1231) | def is_irom_addr(self, addr):
    method get_irom_segment (line 1234) | def get_irom_segment(self):
    method get_non_irom_segments (line 1242) | def get_non_irom_segments(self):
  class ESP8266ROMFirmwareImage (line 1247) | class ESP8266ROMFirmwareImage(BaseFirmwareImage):
    method __init__ (line 1250) | def __init__(self, load_file=None):
    method default_output_name (line 1265) | def default_output_name(self, input_file):
    method save (line 1269) | def save(self, basename):
  class ESP8266V2FirmwareImage (line 1285) | class ESP8266V2FirmwareImage(BaseFirmwareImage):
    method __init__ (line 1288) | def __init__(self, load_file=None):
    method default_output_name (line 1322) | def default_output_name(self, input_file):
    method save (line 1332) | def save(self, filename):
  function esp8266_crc32 (line 1361) | def esp8266_crc32(data):
  class ESP32FirmwareImage (line 1369) | class ESP32FirmwareImage(BaseFirmwareImage):
    method __init__ (line 1378) | def __init__(self, load_file=None):
    method is_flash_addr (line 1415) | def is_flash_addr(self, addr):
    method default_output_name (line 1419) | def default_output_name(self, input_file):
    method warn_if_unusual_segment (line 1423) | def warn_if_unusual_segment(self, offset, size, is_irom_segment):
    method save (line 1426) | def save(self, filename):
    method save_flash_segment (line 1520) | def save_flash_segment(self, f, segment, checksum=None):
    method load_extended_header (line 1528) | def load_extended_header(self, load_file):
    method save_extended_header (line 1548) | def save_extended_header(self, save_file):
  class ELFFile (line 1565) | class ELFFile(object):
    method __init__ (line 1571) | def __init__(self, name):
    method get_section (line 1577) | def get_section(self, section_name):
    method _read_elf_file (line 1583) | def _read_elf_file(self, f):
    method _read_sections (line 1604) | def _read_sections(self, f, section_header_offs, section_header_count,...
    method sha256 (line 1642) | def sha256(self):
  function slip_reader (line 1650) | def slip_reader(port, trace_function):
  function arg_auto_int (line 1692) | def arg_auto_int(x):
  function div_roundup (line 1696) | def div_roundup(a, b):
  function align_file_position (line 1700) | def align_file_position(f, size):
  function flash_size_bytes (line 1705) | def flash_size_bytes(size):
  function hexify (line 1714) | def hexify(s, uppercase=True):
  class HexFormatter (line 1722) | class HexFormatter(object):
    method __init__ (line 1724) | def __init__(self, binary_string, auto_split=True):
    method __str__ (line 1728) | def __str__(self):
  function pad_to (line 1743) | def pad_to(data, alignment, pad_character=b'\xFF'):
  class FatalError (line 1750) | class FatalError(RuntimeError):
    method __init__ (line 1751) | def __init__(self, message):
    method WithResult (line 1755) | def WithResult(message, result):
  class NotImplementedInROMError (line 1760) | class NotImplementedInROMError(FatalError):
    method __init__ (line 1761) | def __init__(self, bootloader, func):
  class NotSupportedError (line 1765) | class NotSupportedError(FatalError):
    method __init__ (line 1766) | def __init__(self, esp, function_name):
  function load_ram (line 1773) | def load_ram(esp, args):
  function read_mem (line 1794) | def read_mem(esp, args):
  function write_mem (line 1798) | def write_mem(esp, args):
  function dump_mem (line 1803) | def dump_mem(esp, args):
  function detect_flash_size (line 1816) | def detect_flash_size(esp, args):
  function _update_image_flash_params (line 1828) | def _update_image_flash_params(esp, address, args, image):
  function write_flash (line 1854) | def write_flash(esp, args):
  function image_info (line 1985) | def image_info(args):
  function make_image (line 2009) | def make_image(args):
  function elf2image (line 2023) | def elf2image(args):
  function read_mac (line 2053) | def read_mac(esp, args):
  function chip_id (line 2062) | def chip_id(esp, args):
  function erase_flash (line 2071) | def erase_flash(esp, args):
  function erase_region (line 2078) | def erase_region(esp, args):
  function run (line 2085) | def run(esp, args):
  function flash_id (line 2089) | def flash_id(esp, args):
  function read_flash (line 2097) | def read_flash(esp, args):
  function verify_flash (line 2117) | def verify_flash(esp, args):
  function read_flash_status (line 2155) | def read_flash_status(esp, args):
  function write_flash_status (line 2159) | def write_flash_status(esp, args):
  function version (line 2168) | def version(args):
  function main (line 2177) | def main(custom_commandline=None):
  function expand_file_arguments (line 2507) | def expand_file_arguments():
  class FlashSizeAction (line 2523) | class FlashSizeAction(argparse.Action):
    method __init__ (line 2524) | def __init__(self, option_strings, dest, nargs=1, auto_detect=False, *...
    method __call__ (line 2528) | def __call__(self, parser, namespace, values, option_string=None):
  class SpiConnectionAction (line 2555) | class SpiConnectionAction(argparse.Action):
    method __call__ (line 2556) | def __call__(self, parser, namespace, value, option_string=None):
  class AddrFilenamePairAction (line 2580) | class AddrFilenamePairAction(argparse.Action):
    method __init__ (line 2582) | def __init__(self, option_strings, dest, nargs='+', **kwargs):
    method __call__ (line 2585) | def __call__(self, parser, namespace, values, option_string=None):
  function _main (line 2718) | def _main():

FILE: scripts/miniterm.py
  function key_description (line 33) | def key_description(character):
  class ConsoleBase (line 43) | class ConsoleBase(object):
    method __init__ (line 46) | def __init__(self):
    method setup (line 53) | def setup(self):
    method cleanup (line 56) | def cleanup(self):
    method getkey (line 59) | def getkey(self):
    method write_bytes (line 63) | def write_bytes(self, byte_string):
    method write (line 68) | def write(self, text):
    method cancel (line 73) | def cancel(self):
    method __enter__ (line 80) | def __enter__(self):
    method __exit__ (line 84) | def __exit__(self, *args, **kwargs):
  class Out (line 93) | class Out(object):
    method __init__ (line 96) | def __init__(self, fd):
    method flush (line 99) | def flush(self):
    method write (line 102) | def write(self, s):
  class Console (line 105) | class Console(ConsoleBase):
    method __init__ (line 131) | def __init__(self):
    method __del__ (line 158) | def __del__(self):
    method getkey (line 166) | def getkey(self):
    method cancel (line 183) | def cancel(self):
    method __init__ (line 195) | def __init__(self):
    method setup (line 205) | def setup(self):
    method getkey (line 212) | def getkey(self):
    method cancel (line 218) | def cancel(self):
    method cleanup (line 221) | def cleanup(self):
  class Console (line 194) | class Console(ConsoleBase):
    method __init__ (line 131) | def __init__(self):
    method __del__ (line 158) | def __del__(self):
    method getkey (line 166) | def getkey(self):
    method cancel (line 183) | def cancel(self):
    method __init__ (line 195) | def __init__(self):
    method setup (line 205) | def setup(self):
    method getkey (line 212) | def getkey(self):
    method cancel (line 218) | def cancel(self):
    method cleanup (line 221) | def cleanup(self):
  class Transform (line 231) | class Transform(object):
    method rx (line 233) | def rx(self, text):
    method tx (line 237) | def tx(self, text):
    method echo (line 241) | def echo(self, text):
  class CRLF (line 246) | class CRLF(Transform):
    method tx (line 249) | def tx(self, text):
  class CR (line 253) | class CR(Transform):
    method rx (line 256) | def rx(self, text):
    method tx (line 259) | def tx(self, text):
  class LF (line 263) | class LF(Transform):
  class NoTerminal (line 267) | class NoTerminal(Transform):
    method rx (line 277) | def rx(self, text):
  class NoControls (line 283) | class NoControls(NoTerminal):
  class Printable (line 295) | class Printable(Transform):
    method rx (line 298) | def rx(self, text):
  class Colorize (line 313) | class Colorize(Transform):
    method __init__ (line 316) | def __init__(self):
    method rx (line 321) | def rx(self, text):
    method echo (line 324) | def echo(self, text):
  class DebugIO (line 328) | class DebugIO(Transform):
    method rx (line 331) | def rx(self, text):
    method tx (line 336) | def tx(self, text):
  function ask_for_port (line 363) | def ask_for_port():
  class Miniterm (line 388) | class Miniterm(object):
    method __init__ (line 394) | def __init__(self, serial_instance, echo=False, eol='crlf', filters=()):
    method _start_reader (line 412) | def _start_reader(self):
    method _stop_reader (line 420) | def _stop_reader(self):
    method start (line 427) | def start(self):
    method stop (line 437) | def stop(self):
    method join (line 441) | def join(self, transmit_only=False):
    method close (line 449) | def close(self):
    method update_transformations (line 452) | def update_transformations(self):
    method set_rx_encoding (line 459) | def set_rx_encoding(self, encoding, errors='replace'):
    method set_tx_encoding (line 464) | def set_tx_encoding(self, encoding, errors='replace'):
    method dump_port_settings (line 469) | def dump_port_settings(self):
    method reader (line 494) | def reader(self):
    method writer (line 513) | def writer(self):
    method handle_menu_key (line 551) | def handle_menu_key(self, c):
    method upload_file (line 637) | def upload_file(self):
    method change_filter (line 659) | def change_filter(self):
    method change_encoding (line 678) | def change_encoding(self):
    method change_baudrate (line 694) | def change_baudrate(self):
    method change_port (line 708) | def change_port(self):
    method suspend_port (line 738) | def suspend_port(self):
    method get_help_text (line 769) | def get_help_text(self):
  function main (line 813) | def main(default_port=None, default_baudrate=9600, default_rts=None, def...

FILE: scripts/pager.py
  class COORD (line 46) | class COORD(Structure):
  class SMALL_RECT (line 49) | class SMALL_RECT(Structure):
  class CONSOLE_SCREEN_BUFFER_INFO (line 53) | class CONSOLE_SCREEN_BUFFER_INFO(Structure):
  function _windows_get_window_size (line 61) | def _windows_get_window_size():
  function _posix_get_window_size (line 72) | def _posix_get_window_size():
  function getwidth (line 93) | def getwidth():
  function getheight (line 105) | def getheight():
  function dumpkey (line 140) | def dumpkey(key):
  function _getch_windows (line 163) | def _getch_windows(_getall=False):
  function _getch_unix (line 174) | def _getch_unix(_getall=False):
  function getchars (line 240) | def getchars():
  function echo (line 244) | def echo(msg):
  function prompt (line 249) | def prompt(pagenum):
  function page (line 256) | def page(content, pagecallback=prompt):

FILE: tools/HIDX/python/hidxshell.py
  class HIDX (line 30) | class HIDX():
    method __init__ (line 31) | def __init__(self,vid=None,pid=None,debug=False,sbuff=8,rbuff=8):
    method display_dev (line 64) | def display_dev(self,vid=None,pid=None):
    method do_cmd (line 74) | def do_cmd(self,cmd):
    method find_devs (line 86) | def find_devs(self):
    method setup_dev (line 107) | def setup_dev(self,vid,pid):
    method pad_input (line 144) | def pad_input(self,input_str, left_pad=False, right_pad=False, chunk_s...
    method split_output (line 160) | def split_output(self,message,chunk_size=8):
    method write (line 169) | def write(self,msg=None, buffer_limit=8):
    method _read (line 194) | def _read(self, buffer_limit=8,report_size=8,timeout=200):
    method read (line 234) | def read(self, retries=1):
    method setWriteBuffSize (line 266) | def setWriteBuffSize(self,size):
    method setReadBuffSize (line 270) | def setReadBuffSize(self,size):
    method decode_msg (line 274) | def decode_msg(self,data):
    method start (line 282) | def start(self,max_errors=5):
    method send (line 324) | def send(self,message,max_errors=5):
  function clean_id (line 347) | def clean_id(inmsg):
  function read_stdin (line 352) | def read_stdin():

FILE: tools/HIDX/python/linux-nativeshell.py
  function do_cmd (line 20) | def do_cmd(cmd):
  function get_linux_dev (line 26) | def get_linux_dev():
  function debug_linux_dev (line 35) | def debug_linux_dev():
  function debug_linux (line 43) | def debug_linux(fd):
  function chunk_cmd (line 56) | def chunk_cmd(result):

FILE: tools/HIDX/python/stealthlink-client-universal.py
  function non_blocking_input (line 55) | def non_blocking_input(prompt="", timeout=5):
  function pad_input (line 63) | def pad_input(input_str, left_pad=False, right_pad=False, chunk_size = 8):
  function split_output (line 79) | def split_output(message,chunk_size=8):
  function recvlog (line 87) | def recvlog(msg):
  function handle_client (line 91) | def handle_client(client_socket,run,rts):
  function console_input (line 120) | def console_input(client_socket,run,rts, server_socket = None):
  function hidxcli (line 172) | def hidxcli(host, port,reuse=True):

FILE: tools/HIDX/python/stealthlink-host-universal.py
  class HIDX (line 27) | class HIDX():
    method __init__ (line 28) | def __init__(self,vid=None,pid=None,debug=False,sbuff=8,rbuff=8):
    method display_dev (line 61) | def display_dev(self,vid=None,pid=None):
    method do_cmd (line 71) | def do_cmd(self,cmd):
    method find_devs (line 90) | def find_devs(self):
    method setup_dev (line 111) | def setup_dev(self,vid,pid):
    method pad_input (line 148) | def pad_input(self,input_str, left_pad=False, right_pad=False, chunk_s...
    method split_output (line 164) | def split_output(self,message,chunk_size=8):
    method write (line 173) | def write(self,msg=None, buffer_limit=8):
    method _read (line 198) | def _read(self, buffer_limit=8,report_size=8,timeout=200):
    method read (line 239) | def read(self, retries=1):
    method setWriteBuffSize (line 272) | def setWriteBuffSize(self,size):
    method setReadBuffSize (line 276) | def setReadBuffSize(self,size):
    method decode_msg (line 280) | def decode_msg(self,data):
    method start (line 288) | def start(self,max_errors=5):
    method send (line 330) | def send(self,message,max_errors=5):
  function clean_id (line 353) | def clean_id(inmsg):
  function read_stdin (line 358) | def read_stdin():
Condensed preview — 20 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (666K chars).
[
  {
    "path": ".gitignore",
    "chars": 1816,
    "preview": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packagi"
  },
  {
    "path": "LICENSE",
    "chars": 9850,
    "preview": "SOFTWARE LICENSE AGREEMENT IMPORTANT - PLEASE READ CAREFULLY: THIS END-USER LICENSE AGREEMENT (\"EULA\" OR \"AGREEMENT\") IS"
  },
  {
    "path": "README.md",
    "chars": 209,
    "preview": "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](http"
  },
  {
    "path": "c2server/c2server.py",
    "chars": 27740,
    "preview": "import json\nimport os\nimport random as rnd\nimport socket\nimport sys\nimport time\nfrom http.server import BaseHTTPRequestH"
  },
  {
    "path": "c2server/index.html",
    "chars": 306264,
    "preview": "<!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=\"vie"
  },
  {
    "path": "c2server/requirements.txt",
    "chars": 21,
    "preview": "pymonocypher == 3.1.*"
  },
  {
    "path": "flash.py",
    "chars": 39698,
    "preview": "# Copyright 2023 Mischief Gadgets LLC\n\nimport os\nimport sys\nimport json\nimport glob\nimport serial\nimport base64\nimport p"
  },
  {
    "path": "hashes.txt",
    "chars": 563,
    "preview": "f3cc103136423a57975750907ebc1d367e2985ac6338976d4d5a439f50323f4a  firmware/blank-settings.bin\nf47a8ec3e9aff2318d89694228"
  },
  {
    "path": "scripts/flashapi.py",
    "chars": 111768,
    "preview": "#!/usr/bin/env python\n#\n# Copyright (C) Fredrik Ahlberg, Angus Gratton, Espressif Systems (Shanghai) PTE LTD,\n# Mischief"
  },
  {
    "path": "scripts/miniterm.py",
    "chars": 37890,
    "preview": "#!/usr/bin/env python\n#\n# Very simple serial terminal\n#\n# This file is part of pySerial. https://github.com/pyserial/pys"
  },
  {
    "path": "scripts/pager.py",
    "chars": 8847,
    "preview": "#!/usr/bin/env python\n\"\"\"\nPage output and find dimensions of console.\n\nThis module deals with paging on Linux terminals "
  },
  {
    "path": "tools/HIDX/powershell/minify.ps1",
    "chars": 1014,
    "preview": "function Minify-Script {\n    param(\n        [Parameter(Mandatory=$true)]\n        [string]$InputFilePath,\n        [Parame"
  },
  {
    "path": "tools/HIDX/powershell/win-hidexfil.ps1",
    "chars": 4527,
    "preview": "<#\nHIDXExfil.ps1\nAuthor: Ø1 (@01p8or13)\nAcknowledgements: spiceywasabi, rogandawes\nRequired Dependencies: Activated HIDX"
  },
  {
    "path": "tools/HIDX/powershell/win-hidshell.ps1",
    "chars": 9458,
    "preview": "<#\nwin-hidshell.ps1\nAuthors:  Ø1(@01p8or13), Wasabi (@spiceywasabi)\nAcknowledgements: rogandawes (@RoganDawes)\nRequired "
  },
  {
    "path": "tools/HIDX/python/hidxshell.py",
    "chars": 14017,
    "preview": "# NOTE: This is a POC only\n# This has certain limitations on size of packets and writes\n# You may need root access to us"
  },
  {
    "path": "tools/HIDX/python/linux-nativeshell.py",
    "chars": 3628,
    "preview": "# NOTE: This is a POC only\n# This has certain limitations on size of packets and writes\n# You may need root access to us"
  },
  {
    "path": "tools/HIDX/python/stealthlink-client-universal.py",
    "chars": 8050,
    "preview": "#Name: Stealth-client-universal.py\n#Author: Wasabi (@spiceywasabi)\n#Acknowledgments: Ø1(@01p8or13)\n#Required Dependencie"
  },
  {
    "path": "tools/HIDX/python/stealthlink-host-universal.py",
    "chars": 14355,
    "preview": "# NOTE: This is a POC only\n# This has certain limitations on size of packets and writes\n# You may need root access to us"
  },
  {
    "path": "tools/HIDX/shell/linux-hidexfil.sh",
    "chars": 468,
    "preview": "#!/bin/sh\n# Note: This uses base components to implement\n# More advanced techniques are possible \n# but this is a simple"
  }
]

// ... and 1 more files (download for full content)

About this extraction

This page contains the full source code of the O-MG/O.MG-Firmware GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 20 files (586.1 KB), approximately 185.3k tokens, and a symbol index with 375 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!