
// IDA Pro plugin to load function name information from PDB files

// This file was modify from the old PDB plugin.

// I rewrite the sybmol loading code.
// It use native dbghelp.dll now and it should support 64 bit if
// IDA does. Test with windows XP SP1 PDB files.

// Make sure you have the lastest dbghelp.dll in your search
// path. I put the dbghelp.dll in the plugin directory.

// This file refers to cvconst.h and dbghelp.h files from DIA SDK.
// Please download them from the Microsoft site.

// You can define the symbol search path like windbg does.
//                                  - Christopher Li

// Revision history:
//      - Removed static linking to DBGHELP.DLL
//      - Added support for different versions of DBGHELP.DLL
//                                                      Ilfak Guilfanov


/*//////////////////////////////////////////////////////////////////////
                           Necessary Includes
//////////////////////////////////////////////////////////////////////*/

#include <windows.h>

#pragma pack(push, 8)
#include "cvconst.h"
#include "dbghelp.h"
#pragma pack(pop)

#ifdef __BORLANDC__
#if __BORLANDC__ < 0x560
#include "bc5_add.h"
#endif
#endif

#include <map>
#include <ida.hpp>
#include <idp.hpp>
#include <err.h>
#include <auto.hpp>
#include <name.hpp>
#include <loader.hpp>
#include <diskio.hpp>
#include <typeinf.hpp>
#include <demangle.hpp>
#include "../../ldr/pe/pe.h"

static char download_path[QMAXPATH];

typedef std::map<ea_t, qstring> namelist_t;
static namelist_t namelist;

#ifdef _DEBUG
#define debug_print(x) msg x
#else
#define debug_print(x)
#endif

typedef
BOOL IMAGEAPI SymEnumerateSymbols64_t(
    IN HANDLE                       hProcess,
    IN DWORD64                      BaseOfDll,
    IN PSYM_ENUMSYMBOLS_CALLBACK64  EnumSymbolsCallback64,
    IN PVOID                        UserContext
    );

//----------------------------------------------------------------------
typedef DWORD IMAGEAPI SymSetOptions_t(IN DWORD SymOptions);
typedef BOOL IMAGEAPI SymInitialize_t(IN HANDLE hProcess, IN LPCSTR UserSearchPath, IN BOOL fInvadeProcess);
typedef DWORD64 IMAGEAPI SymLoadModule64_t(IN HANDLE hProcess, IN HANDLE hFile, IN PSTR ImageName, IN PSTR ModuleName, IN DWORD64 BaseOfDll, IN DWORD SizeOfDll);
typedef BOOL IMAGEAPI SymEnumSymbols_t(IN HANDLE hProcess, IN ULONG64 BaseOfDll, IN PCSTR Mask, IN PSYM_ENUMERATESYMBOLS_CALLBACK EnumSymbolsCallback, IN PVOID UserContext);
typedef BOOL IMAGEAPI SymUnloadModule64_t(IN HANDLE hProcess, IN DWORD64 BaseOfDll);
typedef BOOL IMAGEAPI SymCleanup_t(IN HANDLE hProcess);

static HINSTANCE dbghelp = NULL;
static SymSetOptions_t     *pSymSetOptions     = NULL;
static SymInitialize_t     *pSymInitialize     = NULL;
static SymLoadModule64_t   *pSymLoadModule64   = NULL;
static SymEnumSymbols_t    *pSymEnumSymbols    = NULL;
static SymUnloadModule64_t *pSymUnloadModule64 = NULL;
static SymCleanup_t        *pSymCleanup        = NULL;
static SymEnumerateSymbols64_t *pSymEnumerateSymbols64 = NULL;

//----------------------------------------------------------------------
// Dynamically load and link to DBGHELP or IMAGEHLP libraries
// Return: success
static bool setup_pointers(void)
{
  char dll[QMAXPATH];
  if ( getsysfile(dll, sizeof(dll), "dbghelp.dll", NULL) == NULL )
    return false;

  dbghelp = LoadLibrary(dll);
  if ( dbghelp == NULL )
  {
    deb(IDA_DEBUG_PLUGIN, "PDB plugin: failed to load DBGHELP.DLL");
  }
  else
  {
    pSymSetOptions     = (SymSetOptions_t    *)GetProcAddress(dbghelp, "SymSetOptions");
    pSymInitialize     = (SymInitialize_t    *)GetProcAddress(dbghelp, "SymInitialize");
    pSymLoadModule64   = (SymLoadModule64_t  *)GetProcAddress(dbghelp, "SymLoadModule64");
    pSymEnumSymbols    = (SymEnumSymbols_t   *)GetProcAddress(dbghelp, "SymEnumSymbols");
    pSymUnloadModule64 = (SymUnloadModule64_t*)GetProcAddress(dbghelp, "SymUnloadModule64");
    pSymCleanup        = (SymCleanup_t       *)GetProcAddress(dbghelp, "SymCleanup");
    pSymEnumerateSymbols64 = (SymEnumerateSymbols64_t*)GetProcAddress(dbghelp, "SymEnumerateSymbols64");

    if ( pSymSetOptions     != NULL
      && pSymInitialize     != NULL
      && pSymLoadModule64   != NULL
      && pSymUnloadModule64 != NULL
      && pSymCleanup        != NULL
      && pSymEnumSymbols    != NULL  // required XP or higher
      && pSymEnumerateSymbols64 != NULL ) // will used it to load 64-bit programs
    {
      return true;
    }
  }
  deb(IDA_DEBUG_PLUGIN, "PDB plugin: Essential DBGHELP.DLL functions are missing\n");
  FreeLibrary(dbghelp);
  dbghelp = NULL;
  return false;
}

//----------------------------------------------------------------------
static bool create_func_if_necessary(ea_t ea, const char *name)
{
  int stype = segtype(ea);
  if ( stype != SEG_NORM && stype != SEG_CODE ) // only for code or normal segments
    return false;

  if ( get_mangled_name_type(name) == MANGLED_DATA )
    return false;

  if ( !ua_ana0(ea) )
    return false;

  if ( !ph.notify(ph.is_sane_insn, 1) )
    return false;

  auto_make_proc(ea);
  return true;
}

//----------------------------------------------------------------------
static size_t print_symflags(ulong flags, char *buf, size_t bufsize)
{
  char *ptr = buf;
  char *end = buf + bufsize;
  if ( flags == 0 )
  {
    APPZERO(ptr, end);
  }
  else
  {
    if ( (flags & SYMFLAG_CLR_TOKEN)    != 0 ) APPEND(ptr, end, " CLR_TOKEN");
    if ( (flags & SYMFLAG_CONSTANT)     != 0 ) APPEND(ptr, end, " CONSTANT");
    if ( (flags & SYMFLAG_EXPORT)       != 0 ) APPEND(ptr, end, " EXPORT");
    if ( (flags & SYMFLAG_FORWARDER)    != 0 ) APPEND(ptr, end, " FORWARDER");
    if ( (flags & SYMFLAG_FRAMEREL)     != 0 ) APPEND(ptr, end, " FRAMEREL");
    if ( (flags & SYMFLAG_FUNCTION)     != 0 ) APPEND(ptr, end, " FUNCTION");
    if ( (flags & SYMFLAG_ILREL)        != 0 ) APPEND(ptr, end, " ILREL");
    if ( (flags & SYMFLAG_LOCAL)        != 0 ) APPEND(ptr, end, " LOCAL");
    if ( (flags & SYMFLAG_METADATA)     != 0 ) APPEND(ptr, end, " METADATA");
    if ( (flags & SYMFLAG_PARAMETER)    != 0 ) APPEND(ptr, end, " PARAMETER");
    if ( (flags & SYMFLAG_REGISTER)     != 0 ) APPEND(ptr, end, " REGISTER");
    if ( (flags & SYMFLAG_REGREL)       != 0 ) APPEND(ptr, end, " REGREL");
    if ( (flags & SYMFLAG_SLOT)         != 0 ) APPEND(ptr, end, " SLOT");
    if ( (flags & SYMFLAG_THUNK)        != 0 ) APPEND(ptr, end, " THUNK");
    if ( (flags & SYMFLAG_TLSREL)       != 0 ) APPEND(ptr, end, " TLSREL");
    if ( (flags & SYMFLAG_VALUEPRESENT) != 0 ) APPEND(ptr, end, " VALUEPRESENT");
    if ( (flags & SYMFLAG_VIRTUAL)      != 0 ) APPEND(ptr, end, " VIRTUAL");
    int allbits = SYMFLAG_CLR_TOKEN
                | SYMFLAG_CONSTANT
                | SYMFLAG_EXPORT
                | SYMFLAG_FORWARDER
                | SYMFLAG_FRAMEREL
                | SYMFLAG_FUNCTION
                | SYMFLAG_ILREL
                | SYMFLAG_LOCAL
                | SYMFLAG_METADATA
                | SYMFLAG_PARAMETER
                | SYMFLAG_REGISTER
                | SYMFLAG_REGREL
                | SYMFLAG_SLOT
                | SYMFLAG_THUNK
                | SYMFLAG_TLSREL
                | SYMFLAG_VALUEPRESENT
                | SYMFLAG_VIRTUAL;
    flags &= ~allbits;
    if ( flags != 0 )
      qsnprintf(ptr, end-ptr, " %08lX", flags);
    memmove(buf, &buf[1], ptr+1-buf);
    ptr--;
  }
  return ptr - buf;
}

//----------------------------------------------------------------------
static const char *print_symtag(ulong tag)
{
  const char *name;
  switch ( tag )
  {
    case SymTagNull:             name = "Null";             break;
    case SymTagExe:              name = "Exe";              break;
    case SymTagCompiland:        name = "Compiland";        break;
    case SymTagCompilandDetails: name = "CompilandDetails"; break;
    case SymTagCompilandEnv:     name = "CompilandEnv";     break;
    case SymTagFunction:         name = "Function";         break;
    case SymTagBlock:            name = "Block";            break;
    case SymTagData:             name = "Data";             break;
    case SymTagAnnotation:       name = "Annotation";       break;
    case SymTagLabel:            name = "Label";            break;
    case SymTagPublicSymbol:     name = "PublicSymbol";     break;
    case SymTagUDT:              name = "UDT";              break;
    case SymTagEnum:             name = "Enum";             break;
    case SymTagFunctionType:     name = "FunctionType";     break;
    case SymTagPointerType:      name = "PointerType";      break;
    case SymTagArrayType:        name = "ArrayType";        break;
    case SymTagBaseType:         name = "BaseType";         break;
    case SymTagTypedef:          name = "Typedef";          break;
    case SymTagBaseClass:        name = "BaseClass";        break;
    case SymTagFriend:           name = "Friend";           break;
    case SymTagFunctionArgType:  name = "FunctionArgType";  break;
    case SymTagFuncDebugStart:   name = "FuncDebugStart";   break;
    case SymTagFuncDebugEnd:     name = "FuncDebugEnd";     break;
    case SymTagUsingNamespace:   name = "UsingNamespace";   break;
    case SymTagVTableShape:      name = "VTableShape";      break;
    case SymTagVTable:           name = "VTable";           break;
    case SymTagCustom:           name = "Custom";           break;
    case SymTagThunk:            name = "Thunk";            break;
    case SymTagCustomType:       name = "CustomType";       break;
    case SymTagManagedType:      name = "ManagedType";      break;
    case SymTagDimension:        name = "Dimension";        break;
    default:                     name = "?";                break;
  }
  return name;
}

//----------------------------------------------------------------------
static bool looks_like_function_name(const char *name)
{
  // this is not quite correct: the presence of an opening brace
  // in the demangled name indicates a function
  // we can have a pointer to a function and there will be a brace
  // but this logic is not applied to data segments
  if ( strchr(name, '(') != NULL )
    return true;

  // check various function keywords
  static const char *const keywords[] =
  {
    "__cdecl ",
    "public: ",
    "virtual ",
    "operator ",
    "__pascal ",
    "__stdcall ",
    "__thiscall ",
  };
  for ( int i=0; i < qnumber(keywords); i++ )
    if ( strstr(name, keywords[i]) != NULL )
      return true;
  return false;
}

//----------------------------------------------------------------------
static bool check_for_ids(ea_t ea, const char *name)
{
  // Seems to be a GUID?
  const char *ptr = name;
  while ( *ptr == '_' )
    ptr++;

  static const char *guids[] = { "IID", "DIID", "GUID", "CLSID", NULL };
  static const char *sids[] = { "SID", NULL };

  struct id_info_t
  {
    const char **names;
    const char *type;
  };
  static id_info_t ids[] =
  {
    { guids, "GUID x;" },
    { sids,  "SID x;" },
  };
  for ( int k=0; k < qnumber(ids); k++ )
  {
    for ( const char **p2=ids[k].names; *p2; p2++ )
    {
      const char *guid = *p2;
      size_t len = strlen(guid);
      if ( strncmp(ptr, guid, len) == 0
        && (ptr[len] == '_' || ptr[len] == ' ') ) // space can be in demangled names
      {
        apply_cdecl2(idati, ea, ids[k].type);
        return true;
      }
    }
  }
  return false;
}

//----------------------------------------------------------------------
// maybe_func: -1:no, 0-maybe, 1-yes
static bool apply_name(ea_t ea, const char *name, int maybe_func)
{
  showAddr(ea); // so the user doesn't get bored

  char buf[MAXSTR];
  buf[0] = '\0';
  if ( maybe_func <= 0 && demangle(buf, sizeof(buf), name, MNG_SHORT_FORM) > 0 )
  {
    // check for meaningless 'string' names
    if ( strcmp(buf, "`string'") == 0 )
    {
      size_t s1 = get_max_ascii_length(ea, ASCSTR_C);
      size_t s2 = get_max_ascii_length(ea, ASCSTR_UNICODE);
      make_ascii_string(ea, 0, s1 >= s2 ? ASCSTR_C : ASCSTR_UNICODE);
      return true;
    }
  }

  // Renaming things immediately right here can lead to bad things.
  // For example, if the name is a well known function name, then
  // ida will immediately try to create a function. This is a bad idea
  // because IDA does not know exact function boundaries and will try
  // to guess them. Since the database has little information yet, there
  // is a big chance that the function will end up to be way too long.
  // That's why we collect names here and will rename them later.
  namelist[ea] = name;

  // check for function telltales
  if ( maybe_func == 0
    && segtype(ea) != SEG_DATA
    && demangle(buf, sizeof(buf), name, MNG_LONG_FORM) > 0
    && looks_like_function_name(buf) )
  {
    auto_make_proc(ea); // fixme: when we will implement lvars, we have to process these request
                        // before handling lvars
    return true;
  }

  if ( check_for_ids(ea, name) )
    return true;
  if ( check_for_ids(ea, buf) )
    return true;

  if ( maybe_func == 0 )
    create_func_if_necessary(ea, name);
  return true;
}

//----------------------------------------------------------------------
// New method: symbol enumeration callback
static BOOL CALLBACK EnumerateSymbolsProc(
        PSYMBOL_INFO psym,
        ULONG /*SymbolSize*/,
        PVOID delta)
{

  ea_t ea = (ea_t)(psym->Address + *(adiff_t*)delta);
  const char *name = psym->Name;

  // for some reason Flags is always zero
#if 0
  int idx = psym->Index;
  int tidx = psym->TypeIndex;
  char buf[MAXSTR];
  print_symflags(psym->Flags, buf, sizeof(buf));
  msg("%5d: %a %5d F=%s Size=%d Reg=%d Scope=%d T=%s %s\n", idx,
        ea,
        tidx,
        buf,
        psym->Size,
        psym->Register,
        psym->Scope,
        print_symtag(psym->Tag),
        name);
#endif

  int maybe_func = 0; // maybe
  switch ( psym->Tag )
  {
    case SymTagFunction:
    case SymTagThunk:
      auto_make_proc(ea); // certainly a func
      maybe_func = 1;
      break;
    case SymTagNull:
    case SymTagExe:
    case SymTagCompiland:
    case SymTagCompilandDetails:
    case SymTagCompilandEnv:
    case SymTagBlock:
    case SymTagData:
    case SymTagAnnotation:
    case SymTagLabel:
    case SymTagUDT:
    case SymTagEnum:
    case SymTagFunctionType:
    case SymTagPointerType:
    case SymTagArrayType:
    case SymTagBaseType:
    case SymTagTypedef:
    case SymTagBaseClass:
    case SymTagFunctionArgType:
    case SymTagFuncDebugStart:
    case SymTagFuncDebugEnd:
    case SymTagUsingNamespace:
    case SymTagVTableShape:
    case SymTagVTable:
    case SymTagCustom:
    case SymTagCustomType:
    case SymTagManagedType:
    case SymTagDimension:
      maybe_func = -1;
      break;
    case SymTagPublicSymbol:
    case SymTagFriend:
    default:
      break;
  }

  return apply_name(ea, name, maybe_func);
}

//----------------------------------------------------------------------
// This method is used to load 64-bit applications into 32-bit IDA
static BOOL CALLBACK EnumSymbolsProc64( PCSTR   szName,
                                        DWORD64 ulAddr,
                                        ULONG   /*ulSize*/,
                                        PVOID   ud  )
{
  adiff_t delta = *(adiff_t *)ud;
  ea_t ea = ulAddr + delta;
  return apply_name(ea, szName, 0);
}

//----------------------------------------------------------------------
// Display a system error message
static void error_msg(char *name)
{
  msg("%s: %s\n", name, winerr(GetLastError()));
}

//--------------------------------------------------------------------------
// callback for parsing config file
static const char *idaapi parse_options(
        const char *keyword,
        int value_type,
        const void *value)
{
  if ( strcmp(keyword, "PDBSYM_DOWNLOAD_PATH") != 0 )
    return IDPOPT_BADKEY;

  if ( value_type != IDPOPT_STR )
    return IDPOPT_BADTYPE;

  qstrncpy(download_path, (const char *)value, sizeof(download_path));

  // empty string used for ida program directory
  if ( download_path[0] != '\0' && !qisdir(download_path) )
    return IDPOPT_BADVALUE;

  return IDPOPT_OK;
}

//----------------------------------------------------------------------
// Main function: do the real job here
// called_from_loader==1: ida decided to call the plugin itself
void idaapi plugin_main(int called_from_loader)
{
  bool ok;
  int counter;
  adiff_t delta;
  char input_path[QMAXPATH], *input = input_path;
  void *fake_proc = (void *) 0xBEEFFEED;
  DWORD64 symbase;
  peheader_t pe;

  if ( !setup_pointers() )
    return; // since we have unloaded the libraries, reinitialize them

  netnode penode("$ PE header");
  ea_t loaded_base = penode.altval(-2);

  // Get the input file name and try to guess the PDB file locaton
  // If failed, ask the user
  get_input_file_path(input_path, sizeof(input_path));
  if ( !qfileexist(input) )
  {
    input = askfile_c(false, input, "Please specify the input file");
    if ( input == NULL )
      return;
  }

  pSymSetOptions(SYMOPT_LOAD_LINES|SYMOPT_FAVOR_COMPRESSED|SYMOPT_NO_PROMPTS);

  // make symbol search path: use symbol server, store in TEMP directory.
  // default place for download is TempDir\ida
  if ( !GetTempPath(sizeof(download_path), download_path) )
    download_path[0] = '\0';
  else
    qstrncat(download_path, "ida", sizeof(download_path));
  read_user_config_file("pdb", parse_options);
  static const char spath_prefix[] = "srv*";
  static const char spath_suffix[] = "*http://msdl.microsoft.com/download/symbols";
  char spath[sizeof(spath_prefix)+sizeof(download_path)+sizeof(spath_suffix)];
  char *ptr = spath, *end = ptr + sizeof(spath);
  APPEND(ptr, end, spath_prefix);
  APPEND(ptr, end, download_path);
  APPEND(ptr, end, spath_suffix);
  if ( !pSymInitialize(fake_proc, spath, FALSE) )
  {
    error_msg("SymInitialize");
    return;
  }

  static const char question[] = "AUTOHIDE REGISTRY\nHIDECANCEL\n"
    "IDA Pro has determined that the input file was linked with debug information.\n"
    "Do you want to look for the corresponding PDB file at the local symbol store\n"
    "and the Microsoft Symbol Server?\n";
  if ( called_from_loader && askyn_c(1, question) <= 0 )
    goto cleanup;

  msg("(Retrieving) and loading the PDB file...\n");
  symbase = pSymLoadModule64(fake_proc, 0, input, NULL, loaded_base, 0);
  if ( symbase == 0 )
    goto cleanup;

  // We managed to load the PDB file.
  // It is very probably that the file comes from VC
  // Load the corresponding type library immediately
  if ( ph.id == PLFM_386
    && penode.valobj(&pe, sizeof(pe)) > 0
    && pe.subsys != PES_NATIVE )
  {
    add_til2(pe.is_pe_plus() ? "vc8amd64" : "vc6win", ADDTIL_INCOMP);
  }

  counter = 0;
  delta = loaded_base - symbase;
#if !defined(__AMD64__) && defined(__EA64__) // trying to load 64bit into 32-bit ida
  if ( inf.is_64bit() && pSymEnumerateSymbols64 != NULL )
    ok = pSymEnumerateSymbols64(fake_proc, symbase, EnumSymbolsProc64, &delta);
  else
#endif
    ok = pSymEnumSymbols(fake_proc, (DWORD) symbase, "", EnumerateSymbolsProc, &delta);
  if ( !ok )
  {
    error_msg("EnumSymbols");
    goto unload;
  }

  // Now all information is loaded into the database (except names)
  // We are ready to use names
  for ( namelist_t::iterator p=namelist.begin(); p != namelist.end(); ++p )
    counter += set_name(p->first, p->second.c_str(), SN_NOWARN);

  msg("PDB: total %d symbol%s loaded\n", counter, counter>1 ? "s" : "");

unload:
  if (!pSymUnloadModule64(fake_proc, symbase))
    error_msg("SymUnloadModule64:");

cleanup:
  if (!pSymCleanup(fake_proc))
    error_msg("SymCleanup");
}



/*//////////////////////////////////////////////////////////////////////
                      IDA PRO INTERFACE START HERE
//////////////////////////////////////////////////////////////////////*/
//--------------------------------------------------------------------------
//      terminate
//      usually this callback is empty
//
//      IDA will call this function when the user asks to exit.
//      This function won't be called in the case of emergency exits.

void idaapi term(void)
{
  if ( dbghelp != NULL )
  {
    FreeLibrary(dbghelp);
    dbghelp = NULL;
  }
  namelist.clear();
}

//--------------------------------------------------------------------------
//
//      initialize plugin
//
//      IDA will call this function only once.
//      If this function returns PLGUIN_SKIP, IDA will never load it again.
//      If this function returns PLUGIN_OK, IDA will unload the plugin but
//      remember that the plugin agreed to work with the database.
//      The plugin will be loaded again if the user invokes it by
//      pressing the hotkey or selecting it from the menu.
//      After the second load the plugin will stay on memory.
//
//      In this example we check the input file format and make the decision.
//      You may or may not check any other conditions to decide what you do:
//      whether you agree to work with the database or not.
//

int idaapi init(void)
{
  const char *opts = get_plugin_options("pdb");
  if ( opts != NULL && strcmp(opts, "off") == 0 )
    return PLUGIN_SKIP;

  if ( inf.filetype != f_PE )
    return PLUGIN_SKIP; // only for PE files

  if ( !setup_pointers() )
    return PLUGIN_SKIP;

  term(); // unload libraries to keep the memory foorprint small

  return PLUGIN_OK;
}

//--------------------------------------------------------------------------
char comment[] = "Load debug information from a PDB file";

char help[] =
"PDB file loader\n"
"\n"
"This module allows you to load debug information about function names\n"
"from a PDB file.\n"
"\n"
"The PDB file should be in the same directory as the input file\n";


//--------------------------------------------------------------------------
// This is the preferred name of the plugin module in the menu system
// The preferred name may be overriden in plugins.cfg file

char wanted_name[] = "Load PDB file (dbghelp 4.1+)";


// This is the preferred hotkey for the plugin module
// The preferred hotkey may be overriden in plugins.cfg file
// Note: IDA won't tell you if the hotkey is not correct
//       It will just disable the hotkey.

char wanted_hotkey[] = ""; // Ctrl-F12 is used to draw function call graph now
                           // No hotkey for PDB files, but since it is not
                           // used very often, it is tolerable

//--------------------------------------------------------------------------
//
//      PLUGIN DESCRIPTION BLOCK
//
//--------------------------------------------------------------------------
plugin_t PLUGIN =
{
  IDP_INTERFACE_VERSION,
  PLUGIN_UNL | PLUGIN_MOD | PLUGIN_HIDE, // plugin flags:
  init,                 // initialize

  term,                 // terminate. this pointer may be NULL.

  plugin_main,          // invoke plugin

  comment,              // long comment about the plugin
                        // it could appear in the status line
                        // or as a hint

  help,                 // multiline help about the plugin

  wanted_name,          // the preferred short name of the plugin
  wanted_hotkey         // the preferred hotkey to run the plugin
};


