require 'zeitwerk'

###
# TODO:
# Apply zeitwerk to the rest of framework
# Namespacing wmap/events (when we're able to make potentially breaking changes)
# Correct namespacing to remove the custom inflector (or reduce it's complexity)
# Correct namespacing to cut down on inflector overrides
# Make the necessary changes to reduce/remove the ignored/collapsed files and folders
#
# I don't know why these are needed in `lib/msf/util/dot_net_deserialization/types.rb`
#   require 'msf/util/dot_net_deserialization/types/primitives'
#   require 'msf/util/dot_net_deserialization/types/general'
#   require 'msf/util/dot_net_deserialization/types/record_values'
###

class MsfAutoload
  include Singleton

  def initialize
    @loader = create_loader
    configure(@loader)
    finalize_loader(@loader)
  end

  class TempInflector < Zeitwerk::Inflector
    def camelize(basename, abspath)
      if basename == 'osx' && abspath.end_with?("#{__dir__}/msf/core/payload/osx", "#{__dir__}/msf/core/payload/osx.rb")
        'Osx'
      elsif basename == 'exe' && abspath.end_with?("#{__dir__}/msf/core/exe", "#{__dir__}/msf/core/exe.rb")
        'Exe'
      elsif basename == 'json' && abspath.end_with?("#{__dir__}/msf/base/serializer/json.rb")
        'Json'
      elsif basename == 'powershell' && abspath.end_with?("#{__dir__}/msf/base/sessions/powershell.rb")
        'PowerShell'
      elsif basename == 'ui' && abspath.end_with?("#{__dir__}/msf/core/module/ui", "#{__dir__}/msf/core/module/ui.rb", "#{__dir__}/rex/post/ui", "#{__dir__}/rex/post/ui.rb", "#{__dir__}/rex/post/meterpreter/extensions/stdapi/ui.rb")
        'UI'
      elsif basename == 'mysql' && abspath.end_with?("#{__dir__}/msf/core/exploit/remote/mysql.rb")
        'MYSQL'
      elsif basename == 'ssh' && abspath.end_with?("#{__dir__}/rex/proto/ssh")
        'Ssh'
      elsif basename == 'ftp' && abspath.end_with?("#{__dir__}/rex/proto/ftp")
        'Ftp'
      elsif basename == 'ftp' && abspath.end_with?("#{__dir__}/msf/core/exploit/remote/ftp.rb")
        'Ftp'
      elsif basename == 'http' && abspath.end_with?("#{__dir__}/rex/proto/http")
        'Http'
      elsif basename == 'rftransceiver' && abspath.end_with?("#{__dir__}/rex/post/hwbridge/ui/console/command_dispatcher/rftransceiver.rb")
        'RFtransceiver'
      else
        super
      end
    end
  end

  private

  def ignore_list
    [
      "#{__dir__}/msf/core/modules/external/go/pkg",
      "#{__dir__}/msf/core/constants.rb",
      "#{__dir__}/msf/core/cert_provider.rb",
      "#{__dir__}/msf/core/rpc/json/",
      "#{__dir__}/msf/core/modules/external/ruby/metasploit.rb",
      "#{__dir__}/msf/core/rpc/v10/constants.rb",
      "#{__dir__}/msf/core.rb",
      "#{__dir__}/msf/base.rb",
      "#{__dir__}/rex/post/",
      "#{__dir__}/rex/post.rb",
      "#{__dir__}/rex/proto/ssh/hrr_rb_ssh.rb",
      "#{__dir__}/rex/proto/ssh/connection.rb",
      "#{__dir__}/rex/proto/kerberos/pac/krb5_pac.rb",
      "#{__dir__}/msf/core/mcp.rb",
      "#{__dir__}/msf/core/mcp/"
    ]
  end

  def collapse_list
    [
      "#{__dir__}/msf/core",
      "#{__dir__}/msf/core/rpc/v10",
      "#{__dir__}/msf/core/payload/osx/x64",
      "#{__dir__}/msf/core/payload/windows/x64",
      # "#{__dir__}/msf/core/payload/linux/x64",
      "#{__dir__}/msf/core/web_services/servlet",
      "#{__dir__}/msf/base",
      "#{__dir__}/rex/parser/fs"
    ]
  end

  def custom_inflections
    {
      'uuid' => 'UUID',
      'db_manager' => 'DBManager',
      'ci' => 'CI',
      'fusion_vm' => 'FusionVM',
      'gpp' => 'GPP',
      'ip360' => 'IP360',
      'aspl' => 'ASPL',
      'ip_list' => 'IPList',
      'mbsa' => 'MBSA',
      'xml' => 'XML',
      'nbe' => 'NBE',
      'open_vas' => 'OpenVAS',
      'ip_address' => 'IPAddress',
      'wmap' => 'WMAP',
      'reflective_dll_loader' => 'ReflectiveDLLLoader',
      'ssl' => 'SSL',
      'reverse_tcp_double_ssl' => 'ReverseTcpDoubleSSL',
      'rpc' => 'RPC',
      'db_import_error' => 'DBImportError',
      'db_export' => 'DBExport',
      'extapi' => 'ExtAPI',
      'nonalpha' => 'NonAlpha',
      'nonupper' => 'NonUpper',
      'natpmp' => 'NATPMP',
      'udp_scanner' => 'UDPScanner',
      'epmp' => 'EPMP',
      'cnpilot' => 'CNPILOT',
      'rservices' => 'RServices',
      'ntp' => 'NTP',
      'mqtt' => 'MQTT',
      'iax2' => 'IAX2',
      'pii' => 'PII',
      'mdns' => 'MDNS',
      'crand' => 'CRand',
      'llmnr' => 'LLMNR',
      'drdos' => 'DRDoS',
      'jsp' => 'JSP',
      'macho' => 'MachO',
      'nodejs' => 'NodeJS',
      'jsobfu' => 'JSObfu',
      'vbsobfuscate' => 'VBSObfuscate',
      'osx' => 'OSX',
      'webrtc' => 'WebRTC',
      'json' => 'JSON',
      'sip' => 'SIP',
      'ntlm' => 'NTLM',
      'mssql_commands' => 'MSSQL_COMMANDS',
      'mssql' => 'MSSQL',
      'pdf' => 'PDF',
      'fileformat' => 'FILEFORMAT',
      'ftp' => 'FTP',
      'http' => 'HTTP',
      'html' => 'HTML',
      'pdf_parse' => 'PDF_Parse',
      'vim_soap' => 'VIMSoap',
      'ndmp' => 'NDMP',
      'ndmp_socket' => 'NDMPSocket',
      'dcerpc' => 'DCERPC',
      'dcerpc_mgmt' => 'DCERPC_MGMT',
      'dcerpc_epm' => 'DCERPC_EPM',
      'dcerpc_lsa' => 'DCERPC_LSA',
      'wdbrpc_client' => 'WDBRPC_Client',
      'sunrpc' => 'SunRPC',
      'mysql' => 'MySQL',
      'ldap' => 'LDAP',
      'sqli' => 'SQLi',
      'dhcp_server' => 'DHCPServer',
      'tns' => 'TNS',
      'oracle' => 'ORACLE',
      'dect_coa' => 'DECT_COA',
      'wdbrpc' => 'WDBRPC',
      'exe' => 'EXE',
      'php_exe' => 'PhpEXE',
      'mssql_sqli' => 'MSSQL_SQLI',
      'snmp_client' => 'SNMPClient',
      'afp' => 'AFP',
      'zeromq' => 'ZeroMQ',
      'tftp_server' => 'TFTPServer',
      'db2' => 'DB2',
      'rdp' => 'RDP',
      'riff' => 'RIFF',
      'dns' => 'DNS',
      'smtp_deliver' => 'SMTPDeliver',
      'send_uuid' => 'SendUUID',
      'exec_x86' => 'Exec_X86',
      'exec_x64' => 'Exec_x64',
      'reflective_dll_injection' => 'ReflectiveDLLInjection',
      'reflective_pe_loader' => 'ReflectivePELoader',
      'pe_inject' => 'PEInject',
      'reverse_tcp_x86' => 'ReverseTcp_x86',
      'reverse_tcp_aarch64' => 'ReverseTcp_Aarch64',
      'ruby_dl' => 'RubyDL',
      'wmic' => 'WMIC',
      'net_api' => 'NetAPI',
      'rpc_base' => 'RPC_Base',
      'rpc_plugin' => 'RPC_Plugin',
      'rpc_db' => 'RPC_Db',
      'rpc_console' => 'RPC_Console',
      'rpc_session' => 'RPC_Session',
      'rpc_auth' => 'RPC_Auth',
      'rpc_job' => 'RPC_Job',
      'rpc_core' => 'RPC_Core',
      'rpc_health' => 'RPC_Health',
      'rpc_module' => 'RPC_Module',
      'cli' => 'CLI',
      'sqlitei' => 'SQLitei',
      'mysqli' => 'MySQLi',
      'postgresql' => 'PostgreSQL',
      'postgresqli' => 'PostgreSQLi',
      'ssh' => 'SSH',
      'winrm' => 'WinRM',
      'smb' => 'SMB',
      'uris' => 'URIs',
      'jboss' => 'JBoss',
      'send_uuid_x64' => 'SendUUID_x64',
      'reverse_tcp_x64' => 'ReverseTcp_x64',
      'reverse_sctp_x64' => 'ReverseSctp_x64',
      'block_api_x64' => 'BlockApi_x64',
      'exitfunk_x64' => 'Exitfunk_x64',
      'reverse_http_x64' => 'ReverseHttp_x64',
      'rc4_x64' => 'Rc4_x64',
      'bind_tcp_x64' => 'BindTcp_x64',
      'reverse_win_http_x64' => 'ReverseWinHttp_x64',
      'reflective_dll_inject_x64' => 'ReflectiveDllInject_x64',
      'reverse_win_https_x64' => 'ReverseWinHttps_x64',
      'reflective_pe_loader_x64' => 'ReflectivePELoader_x64',
      'migrate_http_x64' => 'MigrateHttp_x64',
      'migrate_common_x64' => 'MigrateCommon_x64',
      'migrate_tcp_x64' => 'MigrateTcp_x64',
      'migrate_named_pipe_x64' => 'MigrateNamedPipe_x64',
      'reverse_named_pipe_x64' => 'ReverseNamedPipe_x64',
      'meterpreter_loader_x64' => 'MeterpreterLoader_x64',
      'rftransceiver' => 'RFTransceiver',
      'dtc' => 'DTC',
      'uds' => 'UDS',
      'v1_0' => 'V1_0',
      'php_include' => 'PHPInclude',
      'psexec_ms17_010' => 'Psexec_MS17_010',
      'bind_tcp_rc4_x64' => 'BindTcpRc4_x64',
      'reverse_tcp_rc4_x64' => 'ReverseTcpRc4_x64',
      'reverse_https_x64' => 'ReverseHttps_x64',
      'bind_named_pipe_x64' => 'BindNamedPipe_x64',
      'addr_loader' => 'AddrLoader_x64',
      'db_manager_proxy' => 'DBManagerProxy',
      'wmap_scan_ssl' => 'WmapScanSSL',
      'http_db_manager_service' => 'HttpDBManagerService',
      'vyos' => 'VYOS',
      'windows_constants' => 'Windows_Constants',
      'tty' => 'TTY',
      'meterpreter_java' => 'Meterpreter_Java_Java',
      'meterpreter_android' => 'Meterpreter_Java_Android',
      'meterpreter_zarch_linux' => 'Meterpreter_zarch_Linux',
      'meterpreter_python' => 'Meterpreter_Python_Python',
      'meterpreter_ppce500v2_linux' => 'Meterpreter_ppce500v2_Linux',
      'meterpreter_x86_osx' => 'Meterpreter_x86_OSX',
      'meterpreter_armbe_linux' => 'Meterpreter_armbe_Linux',
      'meterpreter_ppc64le_linux' => 'Meterpreter_ppc64le_Linux',
      'meterpreter_x64_linux' => 'Meterpreter_x64_Linux',
      'meterpreter_armle_linux' => 'Meterpreter_armle_Linux',
      'meterpreter_aarch64_linux' => 'Meterpreter_aarch64_Linux',
      'meterpreter_x86_win' => 'Meterpreter_x86_Win',
      'meterpreter_armle_apple_ios' => 'Meterpreter_armle_Apple_iOS',
      'meterpreter_mipsle_linux' => 'Meterpreter_mipsle_Linux',
      'meterpreter_x86_bsd' => 'Meterpreter_x86_BSD',
      'meterpreter_mips64_linux' => 'Meterpreter_mips64_Linux',
      'meterpreter_x86_linux' => 'Meterpreter_x86_Linux',
      'meterpreter_mipsbe_linux' => 'Meterpreter_mipsbe_Linux',
      'meterpreter_aarch64_apple_ios' => 'Meterpreter_aarch64_Apple_iOS',
      'meterpreter_x64_osx' => 'Meterpreter_x64_OSX',
      'meterpreter_aarch64_osx' => 'Meterpreter_aarch64_OSX',
      'meterpreter_ppc_linux' => 'Meterpreter_ppc_Linux',
      'meterpreter_x64_win' => 'Meterpreter_x64_Win',
      'meterpreter_php' => 'Meterpreter_Php_Php',
      'meterpreter_multi' => 'Meterpreter_Multi',
      'hwbridge' => 'HWBridge',
      'vncinject_options' => 'VncInjectOptions',
      'vncinject' => 'VncInject',
      'json_hash_file' => 'JSONHashFile',
      'jwt' => 'JWT',
      'ndr' => 'NDR',
      'ci_document' => 'CIDocument',
      'fusionvm_document' => 'FusionVMDocument',
      'group_policy_preferences' => 'GPP',
      'ip360_aspl_xml' => 'IP360ASPLXMLStreamParser',
      'ip360_xml' => 'IP360XMLStreamParser',
      'nessus_xml' => 'NessusXMLStreamParser',
      'netsparker_xml' => 'NetSparkerXMLStreamParser',
      'nexpose_xml' => 'NexposeXMLStreamParser',
      'nmap_xml' => 'NmapXMLStreamParser',
      'openvas_document' => 'OpenVASDocument',
      'retina_xml' => 'RetinaXMLStreamParser',
      'graphml' => 'GraphML',
      'apple_backup_manifestdb' => 'AppleBackupManifestDB',
      'winscp' => 'WinSCP',
      'acpp' => 'ACPP',
      'tftp' => 'TFTP',
      'ipmi' => 'IPMI',
      'channel_auth_reply' => 'Channel_Auth_Reply',
      'open_session_reply' => 'Open_Session_Reply',
      'rakp2' => 'RAKP2',
      'pjl' => 'PJL',
      'dhcp' => 'DHCP',
      'addp' => 'ADDP',
      'rfb' => 'RFB',
      'io' => 'IO',
      'ntfs' => 'NTFS',
      'bitlocker' => 'BITLOCKER',
      'adb' => 'ADB',
      'drda' => 'DRDA',
      'tlv' => 'TLV',
      'svcctl' => 'SVCCTL',
      'wdscp' => 'WDSCP',
      'appapi' => 'AppApi',
      'uds_errors' => 'UDSErrors',
      'smb_hash_capture' => 'SMBHashCapture',
      'rex_ntlm' => 'RexNTLM',
      'teamcity' => 'TeamCity',
      'nist_sp_800_38f' => 'NIST_SP_800_38f',
      'nist_sp_800_108' => 'NIST_SP_800_108',
      'sccm' => 'SCCM',
      'pfsense' => 'PfSense',
      'opnsense' => 'OPNSense',
      'pgadmin' => 'PgAdmin',
      'freepbx' => 'FreePBX',
      'complete_pbx' => 'CompletePBX'
    }
  end

  def config_paths
    [
      { path: "#{__dir__}/msf/", namespace: Msf },
      { path: "#{__dir__}/rex/", namespace: Rex }
    ]
  end

  # Enables :prepend to inject  existing loader
  def create_loader
    Zeitwerk::Loader.new
  end

  # Enables :prepend to override the configuration items pass to the loader
  def configure(loader)
    config_paths.each do |entry|
      if entry[:namespace]
        loader.push_dir(entry[:path], namespace: entry[:namespace])
      else
        loader.push_dir(entry[:path])
      end
    end
    loader.ignore(ignore_list)
    loader.collapse(collapse_list)
    loader.inflector = TempInflector.new
    loader.inflector.inflect(custom_inflections)
  end

  # Enables :prepend to suppress the loader finalization
  def finalize_loader(loader)
    loader.setup # ready!
  end
end

# global autoload of common gems
autoload :BCrypt, 'bcrypt'
autoload :Dnsruby, 'dnsruby'
autoload :Faker, 'faker'
autoload :BinData, 'bindata'
autoload :Metasm, 'metasm'
autoload :Recog, 'recog'
autoload :RubySMB, 'ruby_smb'
autoload :MetasploitPayloads, 'metasploit-payloads'
autoload :PacketFu, 'packetfu'
autoload :Zip, 'zip'
autoload :DidYouMean, 'did_you_mean'

require 'rexml/document'
# Load IO#expect moneypatch
require 'expect'

# XXX: Should be removed once the `lib/metasploit` folder is loaded by Zeitwerk
require 'metasploit/framework/hashes'
