/* vim:set ts=4 sw=4:
 *
 * (C) 2008 Willem Hengeveld  itsme@xs4all.nl
 *
 * IdcPerl - a perl scripting plugin for the Interactive Disassembler by hex-rays.
 * see http://www.xs4all.nl/~itsme/projects/idcperl
 *
 * this module contains code that dynamically imports all functions from DYNAMIC_PERL_DLL
 *
 */

#include <string>

#include "perldll.h"
struct PerlFuncDef {
    char* name;
    FARPROC* ptr;
};
// ida includes
#include "pro.h"     // for basic types.
#include "ida.hpp"   // for ida constants and types.
#include "idp.hpp"   // for interface version
#include "netnode.hpp"  // for RootNode
#include "expr.hpp"  // for IDCFuncs


// perl includes
#include "extern.h"
#include "perl.h"
#include "xsub.h"
#undef do_open
#undef do_close

#include "redefperlasdll.h"

#include <shlwapi.h>   // FilePathExists

#ifdef TRACE_DLL
#define tracemsg msg
#else
#define tracemsg(...)
#endif

// empty def, this is the module defining the function ptrs.
#define PERLDEFINE
#include "perldllprocs.h"

static PerlFuncDef perl_funcname_table[] = {
#include "perldllprocnames.inc"
};

bool PerlDll::load_procs()
{
    for (PerlFuncDef *p=perl_funcname_table ; p->ptr ; p++)
    {
        *p->ptr = GetProcAddress(_h, p->name);
        if (*p->ptr==NULL)
        {
            msg("IDAPERL ERROR loading proc %s\n", p->name);
            return false;
        }
    }
    return true;
}
void PerlDll::unload()
{
    tracemsg("PerlDll::unload\n");
    if (_h) {
        PERL_SYS_TERM();
        FreeLibrary(_h);
        _h = NULL;
    }
}

bool search_regkey(HKEY hKey, const std::string& regpath, const std::string& name, std::string& dllname)
{
    HKEY hKeyAP;
    LONG rc;
    rc= RegOpenKeyEx(hKey, regpath.c_str(), 0, KEY_READ, &hKeyAP);
    if (rc==0) {
        DWORD dllnamesize= MAX_PATH;
        dllname.resize(dllnamesize);
        rc= RegQueryValueEx(hKeyAP, 0, 0, 0, (BYTE*)&dllname[0], &dllnamesize);
        RegCloseKey(hKeyAP);
        if (rc==0) {
            dllname.resize(strlen(dllname.c_str()));
			if (dllname[dllname.size()-1]!='\\' && dllname[dllname.size()-1]!='/')
				dllname += "\\";
            dllname += "bin\\";
            dllname += name;
            if (PathFileExists(dllname.c_str()))
                return true;
        }
    }
    return false;
}
bool search_activestate_regkeys(const std::string& name, std::string& dllname)
{
    HKEY hKey;
    LONG rc;
    rc= RegOpenKeyEx(HKEY_LOCAL_MACHINE, "Software\\Activestate\\ActivePerl", 0, KEY_READ, &hKey);
    if (rc)
        return false;
    char apversion[256];
    for (int i=0 ; 1 ; i++) {
        DWORD namelen= 256;
        rc= RegEnumKeyEx(hKey, i, apversion, &namelen, 0, 0, 0, 0);
        if (rc==ERROR_NO_MORE_ITEMS)
            break;
        if (search_regkey(hKey, apversion, name, dllname))
            return true;
    }
    return false;
}
bool search_perl_regkey(const std::string& name, std::string& dllname)
{
    return search_regkey(HKEY_LOCAL_MACHINE, "Software\\Perl", name, dllname);
}
bool search_perl_directories(const std::string& name, std::string& dllname)
{
    // todo - try c:\perl*\bin\<dllname>
    return false;
}
bool searchperldll(const std::string& name, std::string& dllname)
{
    // HKLM\software\activestate\ActivePerl\*
    return search_activestate_regkeys(name, dllname)
    // HKLM\software\perl
            || search_perl_regkey(name, dllname)
    // c:\perl*
            || search_perl_directories(name, dllname);
}
bool PerlDll::load()
{
    tracemsg("PerlDll::load\n");

    std::string dllname;
    if (!searchperldll(DYNAMIC_PERL_DLL, dllname)) {
        // try loading it from the searchpath
        dllname= DYNAMIC_PERL_DLL;
    }
    msg("loading %s\n", dllname.c_str());
    _h= LoadLibrary(dllname.c_str());
    if (_h==NULL || _h==INVALID_HANDLE_VALUE) {
        msg("IDAPERL ERROR: LoadLibrary %s\n", dllname.c_str());
        return false;
    }
    if (!load_procs()) {
        unload();
        return false;
    }

    int argc=0;
    char **argv=NULL;
    char **env=NULL;
    PERL_SYS_INIT3(&argc,&argv,&env);

    tracemsg("PerlDll::load done\n");
    return true;
}