// DLLExportsExplorer.cpp
//
// Notes:
// Win2000:
// If I register at HKCR\Folder\ShellEx then DLL is locked.
// otherwise it unloads after explorer closing.
// but if I call menu for desktop items it's locked all the time

#include "StdAfx.h"

#include "../../../Common/MyWindows.h"

#if defined(__clang__) && __clang_major__ >= 4
#pragma GCC diagnostic ignored "-Wnonportable-system-include-path"
#endif
// <olectl.h> : in new Windows Kit 10.0.2**** (NTDDI_WIN10_MN is defined)
// <OleCtl.h> : in another Windows Kit versions
#if defined(NTDDI_WIN10_MN) || defined(__MINGW32__) || defined(__MINGW64__)
#include <olectl.h>
#else
#include <OleCtl.h>
#endif

#if defined(__MINGW32__) || defined(__MINGW64__)
#include <shlguid.h>
#else
#include <ShlGuid.h>
#endif

#include "../../../Common/MyInitGuid.h"

#include "../../../Common/ComTry.h"

#include "../../../Windows/DLL.h"
#include "../../../Windows/ErrorMsg.h"
#include "../../../Windows/NtCheck.h"
#include "../../../Windows/Registry.h"

#include "../FileManager/IFolder.h"

#include "ContextMenu.h"

static LPCTSTR const k_ShellExtName = TEXT("7-Zip ZS Shell Extension");
static LPCTSTR const k_Approved = TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Shell Extensions\\Approved");

// {23170F69-40C1-278A-1000-000100020000}
static LPCTSTR const k_Clsid = TEXT("{23170F69-20BB-278A-1000-000100020000}");

Z7_DEFINE_GUID(CLSID_CZipContextMenu,
    k_7zip_GUID_Data1,
    k_7zip_GUID_Data2_ZS,
    k_7zip_GUID_Data3_Common,
    0x10, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00);

using namespace NWindows;

extern
HINSTANCE g_hInstance;
HINSTANCE g_hInstance = NULL;

extern
HWND g_HWND;
HWND g_HWND = NULL;

extern
LONG g_DllRefCount;
LONG g_DllRefCount = 0; // Reference count of this DLL.

extern
bool g_DisableUserQuestions;
bool g_DisableUserQuestions;


// #define ODS(sz) OutputDebugStringW(L#sz)
#define ODS(sz)

class CShellExtClassFactory Z7_final:
  public IClassFactory,
  public CMyUnknownImp
{
  Z7_COM_UNKNOWN_IMP_1_MT(IClassFactory)
  
  STDMETHOD(CreateInstance)(LPUNKNOWN, REFIID, void**) Z7_override Z7_final;
  STDMETHOD(LockServer)(BOOL) Z7_override Z7_final;
public:
   CShellExtClassFactory() { InterlockedIncrement(&g_DllRefCount); }
  ~CShellExtClassFactory() { InterlockedDecrement(&g_DllRefCount); }
};

Z7_COMWF_B CShellExtClassFactory::CreateInstance(LPUNKNOWN pUnkOuter,
    REFIID riid, void **ppvObj)
{
  ODS("CShellExtClassFactory::CreateInstance()\r\n");
  /*
  char s[64];
  ConvertUInt32ToHex(riid.Data1, s);
  OutputDebugStringA(s);
  */
  *ppvObj = NULL;
  if (pUnkOuter)
    return CLASS_E_NOAGGREGATION;
  
  CZipContextMenu *shellExt;
  try
  {
    shellExt = new CZipContextMenu();
  }
  catch(...) { return E_OUTOFMEMORY; }
  if (!shellExt)
    return E_OUTOFMEMORY;
  
  IContextMenu *ctxm = shellExt;
  const HRESULT res = ctxm->QueryInterface(riid, ppvObj);
  if (res != S_OK)
    delete shellExt;
  return res;
}


Z7_COMWF_B CShellExtClassFactory::LockServer(BOOL /* fLock */)
{
  return S_OK; // Check it
}


#if defined(_UNICODE) && !defined(_WIN64) && !defined(UNDER_CE)
#define NT_CHECK_FAIL_ACTION return FALSE;
#endif

extern "C"
BOOL WINAPI DllMain(
  #ifdef UNDER_CE
  HANDLE hInstance
  #else
  HINSTANCE hInstance
  #endif
  , DWORD dwReason, LPVOID);

extern "C"
BOOL WINAPI DllMain(
  #ifdef UNDER_CE
  HANDLE hInstance
  #else
  HINSTANCE hInstance
  #endif
  , DWORD dwReason, LPVOID)
{
  if (dwReason == DLL_PROCESS_ATTACH)
  {
    g_hInstance = (HINSTANCE)hInstance;
    ODS("In DLLMain, DLL_PROCESS_ATTACH\r\n");
    NT_CHECK
  }
  else if (dwReason == DLL_PROCESS_DETACH)
  {
    ODS("In DLLMain, DLL_PROCESS_DETACH\r\n");
  }
  return TRUE;
}


// Used to determine whether the DLL can be unloaded by OLE

STDAPI DllCanUnloadNow(void)
{
  ODS("In DLLCanUnloadNow\r\n");
  /*
  if (g_DllRefCount == 0)
    ODS( "g_DllRefCount == 0");
  else
    ODS( "g_DllRefCount != 0");
  */
  return (g_DllRefCount == 0 ? S_OK : S_FALSE);
}

STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv)
{
  ODS("In DllGetClassObject\r\n");
  *ppv = NULL;
  if (IsEqualIID(rclsid, CLSID_CZipContextMenu))
  {
    CShellExtClassFactory *cf;
    try
    {
      cf = new CShellExtClassFactory;
    }
    catch(...) { return E_OUTOFMEMORY; }
    if (!cf)
      return E_OUTOFMEMORY;
    IClassFactory *cf2 = cf;
    const HRESULT res = cf2->QueryInterface(riid, ppv);
    if (res != S_OK)
      delete cf;
    return res;
  }
  return CLASS_E_CLASSNOTAVAILABLE;
  // return _Module.GetClassObject(rclsid, riid, ppv);
}


static BOOL RegisterServer()
{
  ODS("RegisterServer\r\n");
  FString modulePath;
  if (!NDLL::MyGetModuleFileName(modulePath))
    return FALSE;
  const UString modulePathU = fs2us(modulePath);
  
  CSysString s ("CLSID\\");
  s += k_Clsid;
  
  {
    NRegistry::CKey key;
    if (key.Create(HKEY_CLASSES_ROOT, s, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE) != NOERROR)
      return FALSE;
    key.SetValue(NULL, k_ShellExtName);
    NRegistry::CKey keyInproc;
    if (keyInproc.Create(key, TEXT("InprocServer32"), NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE) != NOERROR)
      return FALSE;
    keyInproc.SetValue(NULL, modulePathU);
    keyInproc.SetValue(TEXT("ThreadingModel"), TEXT("Apartment"));
  }
 
  #if !defined(_WIN64) && !defined(UNDER_CE)
  if (IsItWindowsNT())
  #endif
  {
    NRegistry::CKey key;
    if (key.Create(HKEY_LOCAL_MACHINE, k_Approved, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE) == NOERROR)
      key.SetValue(k_Clsid, k_ShellExtName);
  }
  
  ODS("RegisterServer :: return TRUE");
  return TRUE;
}

STDAPI DllRegisterServer(void)
{
  return RegisterServer() ? S_OK: SELFREG_E_CLASS;
}

static BOOL UnregisterServer()
{
  CSysString s ("CLSID\\");
  s += k_Clsid;

  RegDeleteKey(HKEY_CLASSES_ROOT, s + TEXT("\\InprocServer32"));
  RegDeleteKey(HKEY_CLASSES_ROOT, s);

  #if !defined(_WIN64) && !defined(UNDER_CE)
  if (IsItWindowsNT())
  #endif
  {
    HKEY hKey;
    if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, k_Approved, 0, KEY_SET_VALUE, &hKey) == NOERROR)
    {
      RegDeleteValue(hKey, k_Clsid);
      RegCloseKey(hKey);
    }
  }

  return TRUE;
}

STDAPI DllUnregisterServer(void)
{
  return UnregisterServer() ? S_OK: SELFREG_E_CLASS;
}
