/*
 * init.c - DLL startup routines module.
 */

/* Headers
 **********/

#include "project.h"
#pragma hdrstop

#include "shlwapi.h"
#include "shlobj.h"
#include "ieguidp.h"

#include "init.h"
#include "clsfact.h"
#include "inetcpl.h"
#include "refcount.hpp"
#include "shlstock.h"
#include "olestock.h"
#include "ftps.hpp"     /* for CLSID_MIMEFileTypesPropSheetHook */
#include "inetps.hpp"   /* for CLSID_Internet */
#include "shguidp.h"    // for CLSID_URLExecHook
#include "cfmacros.h"   // static class factory macros

#define MLUI_INIT
#include <mluisupp.h>

/****************************** Public Functions *****************************/

/* Declare _main() so we can link with the CRT lib, but not have to
** use the DllMainCRTStartup entry point.
*/
void
_cdecl
main(void)
    {
    }


#pragma warning(disable:4100) /* "unreferenced formal parameter" warning */

#ifdef MAINWIN
BOOL WINAPI DllMain(HINSTANCE hModule, DWORD dwReason, PVOID pvReserved);

extern "C" BOOL url_DllMain(
    IN HINSTANCE DllHandle,
    IN DWORD Reason,
    IN LPVOID Reserved
    )
{
  return DllMain(DllHandle,Reason,Reserved);
}

#endif

/*
** DllMain()
**
**
**
** Arguments:
**
** Returns:
**
** Side Effects:  none
*/
PUBLIC_CODE BOOL WINAPI DllMain(HINSTANCE hModule, DWORD dwReason,
                                PVOID pvReserved)
{
   BOOL bResult;

   DebugEntry(DllMain);

   /* Validate dwReason below. */
   /* pvReserved may be any value. */

   ASSERT(IS_VALID_HANDLE(hModule, MODULE));

   switch (dwReason)
   {
      case DLL_PROCESS_ATTACH:
         MLLoadResources(hModule, TEXT("urllc.dll"));
         bResult = AttachProcess(hModule);
         break;

      case DLL_PROCESS_DETACH:
         MLFreeResources(hModule);
         bResult = DetachProcess(hModule);
         break;

      case DLL_THREAD_ATTACH:
         bResult = AttachThread(hModule);
         break;

      case DLL_THREAD_DETACH:
         bResult = DetachThread(hModule);
         break;

      default:
         ERROR_OUT(("LibMain() called with unrecognized dwReason %lu.",
                    dwReason));
         bResult = FALSE;
         break;
   }

   DebugExitBOOL(DllMain, bResult);

   return(bResult);
}


UINT 
WhichPlatform(void)
{
    HINSTANCE hinst;

    //
    // in retail we cache this info
    // in debug we always re-fetch it (so people can switch)
    //
#ifdef DEBUG
    UINT uInstall = PLATFORM_UNKNOWN;
#else
    static UINT uInstall = PLATFORM_UNKNOWN;

    if (uInstall != PLATFORM_UNKNOWN)
        return uInstall;
#endif

    hinst = GetModuleHandle(TEXT("SHDOCVW.DLL"));
    if (hinst)
    {
        // NOTE: GetProcAddress always takes ANSI strings!
        DLLGETVERSIONPROC pfnGetVersion =
            (DLLGETVERSIONPROC)GetProcAddress(hinst, "DllGetVersion");

        if (pfnGetVersion)
        {
            DLLVERSIONINFO info;

            info.cbSize = sizeof(info);
            if (SUCCEEDED(pfnGetVersion(&info)))
            {
                if (4 <= info.dwMajorVersion &&
                    71 <= info.dwMinorVersion &&
                    429 <= info.dwBuildNumber)
                {
                    uInstall = PLATFORM_INTEGRATED;
                }
                else
                    uInstall = PLATFORM_IE3;
            }
        }

#ifdef DEBUG
        // To allow easier debugging, we can override the platform by 
        // setting InstallPlatform value in ccshell.ini.
        {
            UINT uInstallT = GetPrivateProfileInt(TEXT("urldebugoptions"),
                                                  TEXT("InstallPlatform"),
                                                  PLATFORM_UNKNOWN, 
                                                  TEXT("ohare.ini"));
            if (PLATFORM_UNKNOWN != uInstallT)
            {
                TRACE_OUT(("  ***Overriding real platform installation***\r\n"));
                uInstall = uInstallT;
            }
        }        

        switch (uInstall)
        {
        case PLATFORM_IE3:
            TRACE_OUT(("  ***Assuming IE3***\r\n"));
            break;

        case PLATFORM_INTEGRATED:
            TRACE_OUT(("  ***Assuming Nashville***\r\n"));
            break;
        }
#endif
    }

    return uInstall;
}


#pragma data_seg(DATA_SEG_PER_INSTANCE)

// DLL reference count == number of class factories +
//                        number of URLs +
//                        LockServer() count

PRIVATE_DATA ULONG s_ulcDLLRef   = 0;

#pragma data_seg()


PUBLIC_CODE ULONG DLLAddRef(void)
{
   ULONG ulcRef;

   ASSERT(s_ulcDLLRef < ULONG_MAX);

   ulcRef = ++s_ulcDLLRef;

   TRACE_OUT(("DLLAddRef(): DLL reference count is now %lu.",
              ulcRef));

   return(ulcRef);
}


PUBLIC_CODE ULONG DLLRelease(void)
{
   ULONG ulcRef;

   if (EVAL(s_ulcDLLRef > 0))
      s_ulcDLLRef--;

   ulcRef = s_ulcDLLRef;

   TRACE_OUT(("DLLRelease(): DLL reference count is now %lu.",
              ulcRef));

   return(ulcRef);
}


PUBLIC_CODE PULONG GetDLLRefCountPtr(void)
{
   // REARCHITECT: this is dangerous.  Shouldn't be used like this.

   return(&s_ulcDLLRef);
}


typedef HRESULT (CALLBACK* DLLGETCLASSOBJECTPROC)(REFCLSID, REFIID, void**);

STDMETHODIMP_(BOOL)
PatchForNashville(
    REFCLSID rclsid, 
    REFIID riid, 
    void **ppv,
    HRESULT * phres)
{
    BOOL bRet = FALSE;

    *phres = CLASS_E_CLASSNOTAVAILABLE;       // assume error

    if (IsEqualIID(rclsid, CLSID_InternetShortcut) &&
        PLATFORM_INTEGRATED == WhichPlatform())
    {
        HINSTANCE hinst;

        // Normally we can just patch the registry.  But there is a valid
        // case where url.dll is the InprocServer, and that is when the
        // user has chosen to uninstall IE 4.0 and we haven't restarted 
        // the machine yet.  In this case, we don't want to patch the
        // registry.  Use the "MayChangeDefaultMenu" as an indication
        // of whether we should really patch it or not.

        // Are we uninstalling IE 4.0?
        if (NO_ERROR == SHGetValue(HKEY_CLASSES_ROOT, 
                                   TEXT("CLSID\\{FBF23B40-E3F0-101B-8488-00AA003E56F8}\\shellex\\MayChangeDefaultMenu"),
                                   TEXT(""),
                                   NULL, NULL, NULL))
        {
            // No; patch the registry so shdocvw is the handler again
            SetRegKeyValue(HKEY_CLASSES_ROOT, 
                           "CLSID\\{FBF23B40-E3F0-101B-8488-00AA003E56F8}\\InProcServer32",
                           NULL,
                           REG_SZ,
                           (PCBYTE)"shdocvw.dll", sizeof("shdocvw.dll"));

            // Now call shdocvw's DllGetClassObject
            hinst = GetModuleHandle(TEXT("SHDOCVW.DLL"));
            if (hinst)
            {
                DLLGETCLASSOBJECTPROC pfn = 
                    (DLLGETCLASSOBJECTPROC)GetProcAddress(hinst, "DllGetClassObject");

                if (pfn)
                    {
                    *phres = pfn(rclsid, riid, ppv);
                    bRet = TRUE;
                    }
            }
        }
    }
    return bRet;
}


STDAPI CreateInstance_Intshcut(IUnknown *punkOuter, REFIID riid, void **ppvOut);
STDAPI CreateInstance_MIMEHook(IUnknown *punkOuter, REFIID riid, void **ppvOut);
STDAPI CreateInstance_Internet(IUnknown *punkOuter, REFIID riid, void **ppvOut);
STDAPI CreateInstance_URLExec(IUnknown *punkOuter, REFIID riid, void **ppvOut);

//
// ClassFactory methods.
// 

STDMETHODIMP CClassFactory::QueryInterface(REFIID riid, void **ppvObj)
{
    if (IsEqualIID(riid, IID_IClassFactory) || IsEqualIID(riid, IID_IUnknown))
    {
        *ppvObj = (void *)GET_ICLASSFACTORY(this);
        DLLAddRef();
        return NOERROR;
    }

    *ppvObj = NULL;
    return E_NOINTERFACE;
}

STDMETHODIMP_(ULONG) CClassFactory::AddRef()
{
    return DLLAddRef();
}

STDMETHODIMP_(ULONG) CClassFactory::Release()
{
    return DLLRelease();
}

STDMETHODIMP CClassFactory::CreateInstance(IUnknown *punkOuter, REFIID riid, void **ppvObject)
{
    CObjectInfo *localthis = (CObjectInfo*)(this);
    return localthis->pfnCreate(punkOuter, riid, ppvObject);
}

STDMETHODIMP CClassFactory::LockServer(BOOL fLock)
{
    if (fLock)
        DLLAddRef();
    else
        DLLRelease();
    return S_OK;
}

//
// we always do a linear search here so put your most often used things first
//

CF_TABLE_BEGIN(c_clsmap)

    CF_TABLE_ENTRY(&CLSID_URLExecHook, CreateInstance_URLExec) 
    CF_TABLE_ENTRY(&CLSID_InternetShortcut, CreateInstance_Intshcut)
    CF_TABLE_ENTRY(&CLSID_MIMEFileTypesPropSheetHook, CreateInstance_MIMEHook)
    CF_TABLE_ENTRY(&CLSID_Internet, CreateInstance_Internet)

CF_TABLE_END(c_clsmap)

STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, void **ppv)
{
    HRESULT hres = CLASS_E_CLASSNOTAVAILABLE;
    
    *ppv = NULL;        // assume error

    if (IsEqualIID(riid, IID_IClassFactory))
    {
        // Under Nashville, the internet shortcuts are handled by shdocvw.
        // It is possible that the user installed Netscape after installing
        // Nashville, which would cause url.dll to be the handler again
        // for internet shortcuts.  This patches the registry and calls
        // shdocvw's DllGetClassObject if we're in that scenario.

        // Did we patch for nashville?
        if ( !PatchForNashville(rclsid, riid, ppv, &hres) )
        {
            // No; carry on...
            const CObjectInfo *pcls;
            for (pcls = c_clsmap; pcls->pclsid; pcls++)
            {
                if (IsEqualIID(rclsid, *(pcls->pclsid)))
                {
                    *ppv = (void *)GET_ICLASSFACTORY(pcls);
                    DLLAddRef();        // creation of the CF, CF holds DLL Ref count
                    return NOERROR;
                }
            }
        }
    }

    return hres;
}

STDAPI DllCanUnloadNow(void)
{
    if (s_ulcDLLRef > 0)
        return S_FALSE;

    return InternetCPLCanUnloadNow();
}

#pragma warning(default:4100) /* "unreferenced formal parameter" warning */
