/* 7zipUninstall.c - 7-Zip Uninstaller
2024-03-21 : Igor Pavlov : Public domain */

#include "Precomp.h"

// #define SZ_ERROR_ABORT 100

#include "../../7zTypes.h"
#include "../../7zWindows.h"

#if defined(_MSC_VER) && _MSC_VER < 1600
#pragma warning(disable : 4201) // nonstandard extension used : nameless struct/union
#endif

#ifdef Z7_OLD_WIN_SDK
struct IShellView;
#define SHFOLDERAPI  EXTERN_C DECLSPEC_IMPORT HRESULT STDAPICALLTYPE
SHFOLDERAPI SHGetFolderPathW(HWND hwnd, int csidl, HANDLE hToken, DWORD dwFlags, LPWSTR pszPath);
#define BIF_NEWDIALOGSTYLE     0x0040   // Use the new dialog layout with the ability to resize
typedef enum {
    SHGFP_TYPE_CURRENT  = 0,   // current value for user, verify it exists
    SHGFP_TYPE_DEFAULT  = 1,   // default value, may not exist
} SHGFP_TYPE;
#endif
#if defined(__MINGW32__) || defined(__MINGW64__)
#include <shlobj.h>
#else
#include <ShlObj.h>
#endif

#include "../../7zVersion.h"

#include "resource.h"




#define LLL_(quote) L##quote
#define LLL(quote) LLL_(quote)

#define wcscat lstrcatW
#define wcslen (size_t)lstrlenW
#define wcscpy lstrcpyW

// static LPCWSTR const k_7zip = L"7-Zip-Zstandard";

// #define Z7_64BIT_INSTALLER 1

#ifdef _WIN64
  #define Z7_64BIT_INSTALLER 1
#endif

#define k_7zip_with_Ver_base L"7-Zip ZS " LLL(MY_VERSION)

#ifdef Z7_64BIT_INSTALLER

  // #define USE_7ZIP_32_DLL

  #if defined(_M_ARM64) || defined(_M_ARM)
    #define k_Postfix  L" (arm64)"
  #else
    #define k_Postfix  L" (x64)"
    #define USE_7ZIP_32_DLL
  #endif
#else
  #if defined(_M_ARM64) || defined(_M_ARM)
    #define k_Postfix  L" (arm)"
  #else
    // #define k_Postfix  L" (x86)"
    #define k_Postfix
  #endif
#endif

#define k_7zip_with_Ver  k_7zip_with_Ver_base k_Postfix

static LPCWSTR const k_7zip_with_Ver_Uninstall = k_7zip_with_Ver L" Uninstall";

static LPCWSTR const k_Reg_Software_7zip = L"Software\\7-Zip-Zstandard";

static LPCWSTR const k_Reg_Path = L"Path";
 
static LPCWSTR const k_Reg_Path32 = L"Path"
  #ifdef Z7_64BIT_INSTALLER
    L"64"
  #else
    L"32"
  #endif
  ;

#if defined(Z7_64BIT_INSTALLER) && !defined(_WIN64)
  #define k_Reg_WOW_Flag KEY_WOW64_64KEY
#else
  #define k_Reg_WOW_Flag 0
#endif

#ifdef USE_7ZIP_32_DLL
#ifdef _WIN64
  #define k_Reg_WOW_Flag_32 KEY_WOW64_32KEY
#else
  #define k_Reg_WOW_Flag_32 0
#endif
#endif

#define k_7zip_CLSID L"{23170F69-20BB-278A-1000-000100020000}"

static LPCWSTR const k_Reg_CLSID_7zip = L"CLSID\\" k_7zip_CLSID;
static LPCWSTR const k_Reg_CLSID_7zip_Inproc = L"CLSID\\" k_7zip_CLSID L"\\InprocServer32";


#define g_AllUsers True

static BoolInt g_Install_was_Pressed;
static BoolInt g_Finished;
static BoolInt g_SilentMode;

static HWND g_HWND;
static HWND g_Path_HWND;
static HWND g_InfoLine_HWND;
static HWND g_Progress_HWND;

// RegDeleteKeyExW is supported starting from win2003sp1/xp-pro-x64
// Z7_WIN32_WINNT_MIN < 0x0600  // Vista
#if !defined(Z7_WIN32_WINNT_MIN) \
    || Z7_WIN32_WINNT_MIN  < 0x0502  /* < win2003 */ \
    || Z7_WIN32_WINNT_MIN == 0x0502 && !defined(_M_AMD64)
#define Z7_USE_DYN_RegDeleteKeyExW
#endif

#ifdef Z7_USE_DYN_RegDeleteKeyExW
Z7_DIAGNOSTIC_IGNORE_CAST_FUNCTION
typedef LONG (APIENTRY *Func_RegDeleteKeyExW)(HKEY hKey, LPCWSTR lpSubKey, REGSAM samDesired, DWORD Reserved);
static Func_RegDeleteKeyExW func_RegDeleteKeyExW;
#endif

static WCHAR cmd[MAX_PATH + 4];
static WCHAR cmdError[MAX_PATH + 4];
static WCHAR path[MAX_PATH * 2 + 40];
static WCHAR workDir[MAX_PATH + 10];
static WCHAR modulePath[MAX_PATH + 10];
static WCHAR modulePrefix[MAX_PATH + 10];
static WCHAR tempPath[MAX_PATH * 2 + 40];
static WCHAR cmdLine[MAX_PATH * 3 + 40];
static WCHAR copyPath[MAX_PATH * 2 + 40];

static LPCWSTR const kUninstallExe = L"Uninstall.exe";

#define MAKE_CHAR_UPPER(c) ((((c) >= 'a' && (c) <= 'z') ? (c) - 0x20 : (c)))


static void CpyAscii(wchar_t *dest, const char *s)
{
  for (;;)
  {
    const Byte b = (Byte)*s++;
    *dest++ = b;
    if (b == 0)
      return;
  }
}

static void CatAscii(wchar_t *dest, const char *s)
{
  dest += wcslen(dest);
  CpyAscii(dest, s);
}

static void PrintErrorMessage(const char *s1, const wchar_t *s2)
{
  WCHAR m[MAX_PATH + 512];
  m[0] = 0;
  CatAscii(m, "ERROR:");
  if (s1)
  {
    CatAscii(m, "\n");
    CatAscii(m, s1);
  }
  if (s2)
  {
    CatAscii(m, "\n");
    wcscat(m, s2);
  }
  MessageBoxW(g_HWND, m, k_7zip_with_Ver_Uninstall, MB_ICONERROR | MB_OK);
}


static BoolInt AreStringsEqual_NoCase(const wchar_t *s1, const wchar_t *s2)
{
  for (;;)
  {
    wchar_t c1 = *s1++;
    wchar_t c2 = *s2++;
    if (c1 != c2 && MAKE_CHAR_UPPER(c1) != MAKE_CHAR_UPPER(c2))
      return False;
    if (c2 == 0)
      return True;
  }
}

static BoolInt IsString1PrefixedByString2_NoCase(const wchar_t *s1, const wchar_t *s2)
{
  for (;;)
  {
    wchar_t c1;
    const wchar_t c2 = *s2++;
    if (c2 == 0)
      return True;
    c1 = *s1++;
    if (c1 != c2 && MAKE_CHAR_UPPER(c1) != MAKE_CHAR_UPPER(c2))
      return False;
  }
}

static void NormalizePrefix(WCHAR *s)
{
  const size_t len = wcslen(s);
  if (len != 0)
    if (s[len - 1] != WCHAR_PATH_SEPARATOR)
    {
      s[len] = WCHAR_PATH_SEPARATOR;
      s[len + 1] = 0;
    }
}

static int MyRegistry_QueryString(HKEY hKey, LPCWSTR name, LPWSTR dest)
{
  DWORD cnt = MAX_PATH * sizeof(name[0]);
  DWORD type = 0;
  const LONG res = RegQueryValueExW(hKey, name, NULL, &type, (LPBYTE)dest, &cnt);
  if (type != REG_SZ)
    return False;
  return res == ERROR_SUCCESS;
}

static int MyRegistry_QueryString2(HKEY hKey, LPCWSTR keyName, LPCWSTR valName, LPWSTR dest)
{
  HKEY key = 0;
  const LONG res = RegOpenKeyExW(hKey, keyName, 0, KEY_READ | k_Reg_WOW_Flag, &key);
  if (res != ERROR_SUCCESS)
    return False;
  {
    const BoolInt res2 = MyRegistry_QueryString(key, valName, dest);
    RegCloseKey(key);
    return res2;
  }
}

static LONG MyRegistry_OpenKey_ReadWrite(HKEY parentKey, LPCWSTR name, HKEY *destKey)
{
  return RegOpenKeyExW(parentKey, name, 0, KEY_READ | KEY_WRITE | k_Reg_WOW_Flag, destKey);
}

static LONG MyRegistry_DeleteKey(HKEY parentKey, LPCWSTR name)
{
#if k_Reg_WOW_Flag != 0
#ifdef Z7_USE_DYN_RegDeleteKeyExW
    if (!func_RegDeleteKeyExW)
      return E_FAIL;
    return func_RegDeleteKeyExW
#else
    return      RegDeleteKeyExW
#endif
      (parentKey, name, k_Reg_WOW_Flag, 0);
#else
    return RegDeleteKeyW(parentKey, name);
#endif
}

#ifdef USE_7ZIP_32_DLL

static int MyRegistry_QueryString2_32(HKEY hKey, LPCWSTR keyName, LPCWSTR valName, LPWSTR dest)
{
  HKEY key = 0;
  const LONG res = RegOpenKeyExW(hKey, keyName, 0, KEY_READ | k_Reg_WOW_Flag_32, &key);
  if (res != ERROR_SUCCESS)
    return False;
  {
    const BoolInt res2 = MyRegistry_QueryString(key, valName, dest);
    RegCloseKey(key);
    return res2;
  }
}

static LONG MyRegistry_OpenKey_ReadWrite_32(HKEY parentKey, LPCWSTR name, HKEY *destKey)
{
  return RegOpenKeyExW(parentKey, name, 0, KEY_READ | KEY_WRITE | k_Reg_WOW_Flag_32, destKey);
}

static LONG MyRegistry_DeleteKey_32(HKEY parentKey, LPCWSTR name)
{
#if k_Reg_WOW_Flag_32 != 0
#ifdef Z7_USE_DYN_RegDeleteKeyExW
    if (!func_RegDeleteKeyExW)
      return E_FAIL;
    return func_RegDeleteKeyExW
#else
    return      RegDeleteKeyExW
#endif
      (parentKey, name, k_Reg_WOW_Flag_32, 0);
#else
    return RegDeleteKeyW(parentKey, name);
#endif
}

#endif




static void MyReg_DeleteVal_Path_if_Equal(HKEY hKey, LPCWSTR name)
{
  WCHAR s[MAX_PATH + 10];
  if (MyRegistry_QueryString(hKey, name, s))
  {
    NormalizePrefix(s);
    if (AreStringsEqual_NoCase(s, path))
      RegDeleteValueW(hKey, name);
  }
}

static void SetRegKey_Path2(HKEY parentKey)
{
  HKEY key = 0;
  const LONG res = MyRegistry_OpenKey_ReadWrite(parentKey, k_Reg_Software_7zip, &key);
  if (res == ERROR_SUCCESS)
  {
    MyReg_DeleteVal_Path_if_Equal(key, k_Reg_Path32);
    MyReg_DeleteVal_Path_if_Equal(key, k_Reg_Path);

    RegCloseKey(key);
    // MyRegistry_DeleteKey(parentKey, k_Reg_Software_7zip);
  }
}

static void SetRegKey_Path(void)
{
  SetRegKey_Path2(HKEY_CURRENT_USER);
  SetRegKey_Path2(HKEY_LOCAL_MACHINE);
}

static HRESULT CreateShellLink(LPCWSTR srcPath, LPCWSTR targetPath)
{
  IShellLinkW *sl;
 
  // CoInitialize has already been called.
  HRESULT hres = CoCreateInstance(&CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, &IID_IShellLinkW, (LPVOID*)&sl);

  if (SUCCEEDED(hres))
  {
    IPersistFile *pf;
    
    hres = sl->lpVtbl->QueryInterface(sl, &IID_IPersistFile, (LPVOID *)&pf);
   
    if (SUCCEEDED(hres))
    {
      WCHAR s[MAX_PATH + 10];
      hres = pf->lpVtbl->Load(pf, srcPath, TRUE);
      pf->lpVtbl->Release(pf);

      if (SUCCEEDED(hres))
      {
        hres = sl->lpVtbl->GetPath(sl, s, MAX_PATH, NULL, 0); // SLGP_RAWPATH
        if (!AreStringsEqual_NoCase(s, targetPath))
          hres = S_FALSE;
      }
    }

    sl->lpVtbl->Release(sl);
  }

  return hres;
}

static void SetShellProgramsGroup(HWND hwndOwner)
{
  #ifdef UNDER_CE

  UNUSED_VAR(hwndOwner)

  #else

  unsigned i = (g_AllUsers ? 1 : 2);

  for (; i < 3; i++)
  {
    // BoolInt isOK = True;
    WCHAR link[MAX_PATH + 40];
    WCHAR destPath[MAX_PATH + 40];

    link[0] = 0;
    
    if (SHGetFolderPathW(hwndOwner,
        i == 1 ? CSIDL_COMMON_PROGRAMS : CSIDL_PROGRAMS,
        NULL, SHGFP_TYPE_CURRENT, link) != S_OK)
      continue;

    NormalizePrefix(link);
    CatAscii(link, "7-Zip-Zstandard\\");
    
    {
      const size_t baseLen = wcslen(link);
      unsigned k;
      BoolInt needDelete = False;

      for (k = 0; k < 2; k++)
      {
        CpyAscii(link + baseLen, k == 0 ?
            "7-Zip ZS File Manager.lnk" :
            "7-Zip Help.lnk");
        wcscpy(destPath, path);
        CatAscii(destPath, k == 0 ?
            "7zFM.exe" :
            "7-zip.chm");
        
        if (CreateShellLink(link, destPath) == S_OK)
        {
          needDelete = True;
          DeleteFileW(link);
        }
      }

      if (needDelete)
      {
        link[baseLen] = 0;
        RemoveDirectoryW(link);
      }
    }
  }

  #endif
}


static LPCSTR const k_ShellEx_Items[] =
{
    "*\\shellex\\ContextMenuHandlers"
  , "Directory\\shellex\\ContextMenuHandlers"
  , "Folder\\shellex\\ContextMenuHandlers"
  , "Directory\\shellex\\DragDropHandlers"
  , "Drive\\shellex\\DragDropHandlers"
};

static LPCWSTR const k_Shell_Approved = L"Software\\Microsoft\\Windows\\CurrentVersion\\Shell Extensions\\Approved";

static LPCWSTR const k_AppPaths_7zFm = L"Software\\Microsoft\\Windows\\CurrentVersion\\App Paths\\7zFM.exe";
#define k_REG_Uninstall L"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\"
static LPCWSTR const k_Uninstall_7zip = k_REG_Uninstall L"7-Zip-Zstandard";


static void RemoveQuotes(wchar_t *s)
{
  const size_t len = wcslen(s);
  size_t i;
  if (len == 0 || s[0] != '\"' || s[len - 1] != '\"')
    return;
  for (i = 0; i < len; i++)
    s[i] = s[i + 1];
  s[len - 2] = 0;
}

static BoolInt AreEqual_Path_PrefixName(const wchar_t *s, const wchar_t *prefix, const wchar_t *name)
{
  if (!IsString1PrefixedByString2_NoCase(s, prefix))
    return False;
  return AreStringsEqual_NoCase(s + wcslen(prefix), name);
}

static void WriteCLSID(void)
{
  WCHAR s[MAX_PATH + 30];
  
  if (MyRegistry_QueryString2(HKEY_CLASSES_ROOT, k_Reg_CLSID_7zip_Inproc, NULL, s))
  {
    if (AreEqual_Path_PrefixName(s, path, L"7-zip.dll"))
    {
      {
        const LONG res = MyRegistry_DeleteKey(HKEY_CLASSES_ROOT, k_Reg_CLSID_7zip_Inproc);
        if (res == ERROR_SUCCESS)
          MyRegistry_DeleteKey(HKEY_CLASSES_ROOT, k_Reg_CLSID_7zip);
      }

      {
        unsigned i;
        for (i = 0; i < Z7_ARRAY_SIZE(k_ShellEx_Items); i++)
        {
          WCHAR destPath[MAX_PATH];
          CpyAscii(destPath, k_ShellEx_Items[i]);
          CatAscii(destPath, "\\7-Zip-Zstandard");
          
          MyRegistry_DeleteKey(HKEY_CLASSES_ROOT, destPath);
        }
      }

      {
        HKEY destKey = 0;
        const LONG res = MyRegistry_OpenKey_ReadWrite(HKEY_LOCAL_MACHINE, k_Shell_Approved, &destKey);
        if (res == ERROR_SUCCESS)
        {
          RegDeleteValueW(destKey, k_7zip_CLSID);
          /* res = */ RegCloseKey(destKey);
        }
      }
    }
  }

  
  #ifdef USE_7ZIP_32_DLL
  
  if (MyRegistry_QueryString2_32(HKEY_CLASSES_ROOT, k_Reg_CLSID_7zip_Inproc, NULL, s))
  {
    if (AreEqual_Path_PrefixName(s, path, L"7-zip32.dll"))
    {
      {
        const LONG res = MyRegistry_DeleteKey_32(HKEY_CLASSES_ROOT, k_Reg_CLSID_7zip_Inproc);
        if (res == ERROR_SUCCESS)
          MyRegistry_DeleteKey_32(HKEY_CLASSES_ROOT, k_Reg_CLSID_7zip);
      }

      {
        unsigned i;
        for (i = 0; i < Z7_ARRAY_SIZE(k_ShellEx_Items); i++)
        {
          WCHAR destPath[MAX_PATH];
          CpyAscii(destPath, k_ShellEx_Items[i]);
          CatAscii(destPath, "\\7-Zip-Zstandard");
          
          MyRegistry_DeleteKey_32(HKEY_CLASSES_ROOT, destPath);
        }
      }

      {
        HKEY destKey = 0;
        const LONG res = MyRegistry_OpenKey_ReadWrite_32(HKEY_LOCAL_MACHINE, k_Shell_Approved, &destKey);
        if (res == ERROR_SUCCESS)
        {
          RegDeleteValueW(destKey, k_7zip_CLSID);
          /* res = */ RegCloseKey(destKey);
        }
      }
    }
  }
  
  #endif


  if (MyRegistry_QueryString2(HKEY_LOCAL_MACHINE, k_AppPaths_7zFm, NULL, s))
  {
    // RemoveQuotes(s);
    if (AreEqual_Path_PrefixName(s, path, L"7zFM.exe"))
      MyRegistry_DeleteKey(HKEY_LOCAL_MACHINE, k_AppPaths_7zFm);
  }

  if (MyRegistry_QueryString2(HKEY_LOCAL_MACHINE, k_Uninstall_7zip, L"UninstallString", s))
  {
    RemoveQuotes(s);
    if (AreEqual_Path_PrefixName(s, path, kUninstallExe))
      MyRegistry_DeleteKey(HKEY_LOCAL_MACHINE, k_Uninstall_7zip);
  }
}


static const wchar_t *GetCmdParam(const wchar_t *s)
{
  unsigned pos = 0;
  BoolInt quoteMode = False;
  for (;; s++)
  {
    const wchar_t c = *s;
    if (c == 0 || (c == L' ' && !quoteMode))
      break;
    if (c == L'\"')
    {
      quoteMode = !quoteMode;
      continue;
    }
    if (pos >= Z7_ARRAY_SIZE(cmd) - 1)
      exit(1);
    cmd[pos++] = c;
  }
  cmd[pos] = 0;
  return s;
}

/*
static void RemoveQuotes(wchar_t *s)
{
  const wchar_t *src = s;
  for (;;)
  {
    wchar_t c = *src++;
    if (c == '\"')
      continue;
    *s++ = c;
    if (c == 0)
      return;
  }
}
*/

static BoolInt DoesFileOrDirExist(void)
{
  return (GetFileAttributesW(path) != INVALID_FILE_ATTRIBUTES);
}

static BOOL RemoveFileAfterReboot2(const WCHAR *s)
{
  #ifndef UNDER_CE
  return MoveFileExW(s, NULL, MOVEFILE_DELAY_UNTIL_REBOOT);
  #else
  UNUSED_VAR(s)
  return TRUE;
  #endif
}

static BOOL RemoveFileAfterReboot(void)
{
  return RemoveFileAfterReboot2(path);
}

// #define IS_LIMIT_CHAR(c) (c == 0 || c == ' ')

static BoolInt IsThereSpace(const wchar_t *s)
{
  for (;;)
  {
    const wchar_t c = *s++;
    if (c == 0)
      return False;
    if (c == ' ')
      return True;
  }
}

static void AddPathParam(wchar_t *dest, const wchar_t *src)
{
  const BoolInt needQuote = IsThereSpace(src);
  if (needQuote)
    CatAscii(dest, "\"");
  wcscat(dest, src);
  if (needQuote)
    CatAscii(dest, "\"");
}



static BoolInt GetErrorMessage(DWORD errorCode, WCHAR *message)
{
  LPWSTR msgBuf;
  if (FormatMessageW(
          FORMAT_MESSAGE_ALLOCATE_BUFFER
        | FORMAT_MESSAGE_FROM_SYSTEM
        | FORMAT_MESSAGE_IGNORE_INSERTS,
        NULL, errorCode, 0, (LPWSTR) &msgBuf, 0, NULL) == 0)
    return False;
  wcscpy(message, msgBuf);
  LocalFree(msgBuf);
  return True;
}

static BOOL RemoveDir(void)
{
  const DWORD attrib = GetFileAttributesW(path);
  if (attrib == INVALID_FILE_ATTRIBUTES)
    return TRUE;
  if (RemoveDirectoryW(path))
    return TRUE;
  return RemoveFileAfterReboot();
}





#define k_Lang "Lang"

// NUM_LANG_TXT_FILES files are placed before en.ttt
#define NUM_LANG_TXT_FILES 92

#ifdef USE_7ZIP_32_DLL
  #define NUM_EXTRA_FILES_64BIT 1
#else
  #define NUM_EXTRA_FILES_64BIT 0
#endif

#define NUM_FILES (NUM_LANG_TXT_FILES + 1 + 13 + NUM_EXTRA_FILES_64BIT)

static const char * const k_Names =
  "af an ar ast az ba be bg bn br ca co cs cy da de el eo es et eu ext"
  " fa fi fr fur fy ga gl gu he hi hr hu hy id io is it ja ka kaa kab kk ko ku ku-ckb ky"
  " lij lt lv mk mn mng mng2 mr ms nb ne nl nn pa-in pl ps pt pt-br ro ru"
  " sa si sk sl sq sr-spc sr-spl sv sw ta tg th tk tr tt ug uk uz uz-cyrl va vi yo zh-cn zh-tw"
  " en.ttt"
  " descript.ion"
  " History.txt"
  " License.txt"
  " readme.txt"
  " 7-zip.chm"
  " 7z.sfx"
  " 7zCon.sfx"
  " 7z.exe"
  " 7za.exe"
  " 7za.dll"
  " 7zxa.dll"
  " 7zG.exe"
  " 7z.dll"
  " 7zFM.exe"
  #ifdef USE_7ZIP_32_DLL
  " 7-zip32.dll"
  #endif
  " 7-zip.dll"
  " Uninstall.exe";



static int Install(void)
{
  SRes res = SZ_OK;
  WRes winRes = 0;
  
  // BoolInt needReboot = False;
  const size_t pathLen = wcslen(path);

  if (!g_SilentMode)
  {
    ShowWindow(g_Progress_HWND, SW_SHOW);
    ShowWindow(g_InfoLine_HWND, SW_SHOW);
    SendMessage(g_Progress_HWND, PBM_SETRANGE32, 0, NUM_FILES);
  }

  {
    unsigned i;
    const char *curName = k_Names;

    for (i = 0; *curName != 0; i++)
    {
      WCHAR *temp;

      if (!g_SilentMode)
      {
        MSG msg;
        
        // g_HWND
        while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
          if (!IsDialogMessage(g_HWND, &msg))
          {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
          }
          if (!g_HWND)
            return 1;
        }

        // Sleep(1);
        SendMessage(g_Progress_HWND, PBM_SETPOS, i, 0);
      }

      path[pathLen] = 0;
      temp = path + pathLen;
      
      if (i <= NUM_LANG_TXT_FILES)
        CpyAscii(temp, k_Lang "\\");
      
      {
        WCHAR *dest = temp + wcslen(temp);

        for (;;)
        {
          const char c = *curName;
          if (c == 0)
            break;
          curName++;
          if (c == ' ')
            break;
          *dest++ = (Byte)c;
        }

        *dest = 0;
      }

      if (i < NUM_LANG_TXT_FILES)
        CatAscii(temp, ".txt");
  
      if (!g_SilentMode)
        SetWindowTextW(g_InfoLine_HWND, temp);

      {
        const DWORD attrib = GetFileAttributesW(path);
        if (attrib == INVALID_FILE_ATTRIBUTES)
          continue;
        if (attrib & FILE_ATTRIBUTE_READONLY)
          SetFileAttributesW(path, 0);
        if (!DeleteFileW(path))
        {
          if (!RemoveFileAfterReboot())
          {
            winRes = GetLastError();
          }
          /*
          else
            needReboot = True;
          */
        }
      }
    }

    CpyAscii(path + pathLen, k_Lang);
    RemoveDir();

    path[pathLen] = 0;
    RemoveDir();
    
    if (!g_SilentMode)
      SendMessage(g_Progress_HWND, PBM_SETPOS, i, 0);

    if (*curName == 0)
    {
      SetRegKey_Path();
      WriteCLSID();
      SetShellProgramsGroup(g_HWND);
      if (!g_SilentMode)
        SetWindowTextW(g_InfoLine_HWND, k_7zip_with_Ver L" is uninstalled");
    }
  }

  if (winRes != 0)
    res = SZ_ERROR_FAIL;

  if (res == SZ_OK)
  {
    // if (!g_SilentMode && needReboot);
    return 0;
  }

  if (!g_SilentMode)
  {
    WCHAR m[MAX_PATH + 100];
    m[0] = 0;
    if (winRes == 0 || !GetErrorMessage(winRes, m))
      CpyAscii(m, "ERROR");
    PrintErrorMessage("System ERROR:", m);
  }
  
  return 1;
}


static void OnClose(void)
{
  if (g_Install_was_Pressed && !g_Finished)
  {
    if (MessageBoxW(g_HWND,
        L"Do you want to cancel uninstallation?",
        k_7zip_with_Ver_Uninstall,
        MB_ICONQUESTION | MB_YESNO | MB_DEFBUTTON2) != IDYES)
      return;
  }
  DestroyWindow(g_HWND);
  g_HWND = NULL;
}

static
#ifdef Z7_OLD_WIN_SDK
  BOOL
#else
  INT_PTR
#endif
CALLBACK MyDlgProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
  UNUSED_VAR(lParam)

  switch (message)
  {
    case WM_INITDIALOG:
      g_Path_HWND = GetDlgItem(hwnd, IDE_EXTRACT_PATH);
      g_InfoLine_HWND = GetDlgItem(hwnd, IDT_CUR_FILE);
      g_Progress_HWND = GetDlgItem(hwnd, IDC_PROGRESS);

      SetWindowTextW(hwnd, k_7zip_with_Ver_Uninstall);
      SetDlgItemTextW(hwnd, IDE_EXTRACT_PATH, path);

      ShowWindow(g_Progress_HWND, SW_HIDE);
      ShowWindow(g_InfoLine_HWND, SW_HIDE);

      break;

    case WM_COMMAND:
      switch (LOWORD(wParam))
      {
        case IDOK:
        {
          if (g_Finished)
          {
            OnClose();
            break;
          }
          if (!g_Install_was_Pressed)
          {
            SendMessage(hwnd, WM_NEXTDLGCTL, (WPARAM)(void *)GetDlgItem(hwnd, IDCANCEL), TRUE);
            
            EnableWindow(g_Path_HWND, FALSE);
            EnableWindow(GetDlgItem(hwnd, IDOK), FALSE);
            
            g_Install_was_Pressed = True;
            return TRUE;
          }
          break;
        }
        
        case IDCANCEL:
        {
          OnClose();
          break;
        }

        default: return FALSE;
      }
      break;
    
    case WM_CLOSE:
      OnClose();
      break;
    /*
    case WM_DESTROY:
      PostQuitMessage(0);
      return TRUE;
    */
    default:
      return FALSE;
  }
  
  return TRUE;
}


int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
    #ifdef UNDER_CE
      LPWSTR
    #else
      LPSTR
    #endif
    lpCmdLine, int nCmdShow)
{
  const wchar_t *cmdParams;
  BoolInt useTemp = True;

  UNUSED_VAR(hPrevInstance)
  UNUSED_VAR(lpCmdLine)
  UNUSED_VAR(nCmdShow)

#ifndef UNDER_CE
  CoInitialize(NULL);
#endif

#ifndef UNDER_CE
#ifdef Z7_USE_DYN_RegDeleteKeyExW
   func_RegDeleteKeyExW =
  (Func_RegDeleteKeyExW) Z7_CAST_FUNC_C GetProcAddress(GetModuleHandleW(L"advapi32.dll"),
       "RegDeleteKeyExW");
#endif
#endif

  {
    const wchar_t *s = GetCommandLineW();

    #ifndef UNDER_CE
    s = GetCmdParam(s);
    #endif

    cmdParams = s;

    for (;;)
    {
      {
        wchar_t c = *s;
        if (c == 0)
          break;
        if (c == ' ')
        {
          s++;
          continue;
        }
      }

      {
        const wchar_t *s2 = GetCmdParam(s);
        BoolInt error = True;
        if (cmd[0] == '/')
        {
          if (cmd[1] == 'S')
          {
            if (cmd[2] == 0)
            {
              g_SilentMode = True;
              error = False;
            }
          }
          else if (cmd[1] == 'N')
          {
            if (cmd[2] == 0)
            {
              useTemp = False;
              error = False;
            }
          }
          else if (cmd[1] == 'D' && cmd[2] == '=')
          {
            wcscpy(workDir, cmd + 3);
            // RemoveQuotes(workDir);
            useTemp = False;
            error = False;
          }
        }
        s = s2;
        if (error && cmdError[0] == 0)
          wcscpy(cmdError, cmd);
      }
    }

    if (cmdError[0] != 0)
    {
      if (!g_SilentMode)
        PrintErrorMessage("Unsupported command:", cmdError);
      return 1;
    }
  }
  
  {
    wchar_t *name;
    const DWORD len = GetModuleFileNameW(NULL, modulePath, MAX_PATH);
    if (len == 0 || len > MAX_PATH)
      return 1;

    name = NULL;
    wcscpy(modulePrefix, modulePath);

    {
      wchar_t *s = modulePrefix;
      for (;;)
      {
        const wchar_t c = *s++;
        if (c == 0)
          break;
        if (c == WCHAR_PATH_SEPARATOR)
          name = s;
      }
    }
    
    if (!name)
      return 1;

    if (!AreStringsEqual_NoCase(name, kUninstallExe))
      useTemp = False;

    *name = 0; // keep only prefix for modulePrefix
  }


  if (useTemp)
  {
    DWORD winRes = GetTempPathW(MAX_PATH, path);

    // GetTempPath: the returned string ends with a backslash
    /*
      {
        WCHAR s[MAX_PATH + 1];
        wcscpy(s, path);
        GetLongPathNameW(s, path, MAX_PATH);
      }
    */

    if (winRes != 0 && winRes <= MAX_PATH + 1
        && !IsString1PrefixedByString2_NoCase(modulePrefix, path))
    {
      unsigned i;
      DWORD d;
    
      const size_t pathLen = wcslen(path);
      d = (GetTickCount() << 12) ^ (GetCurrentThreadId() << 14) ^ GetCurrentProcessId();
      
      for (i = 0; i < 100; i++, d += GetTickCount())
      {
        CpyAscii(path + pathLen, "7z");
        
        {
          wchar_t *s = path + wcslen(path);
          UInt32 value = d;
          unsigned k;
          for (k = 0; k < 8; k++)
          {
            const unsigned t = value & 0xF;
            value >>= 4;
            s[7 - k] = (wchar_t)((t < 10) ? ('0' + t) : ('A' + (t - 10)));
          }
          s[k] = 0;
        }
        
        if (DoesFileOrDirExist())
          continue;
        if (CreateDirectoryW(path, NULL))
        {
          CatAscii(path, STRING_PATH_SEPARATOR);
          wcscpy(tempPath, path);
          break;
        }
        if (GetLastError() != ERROR_ALREADY_EXISTS)
          break;
      }
      
      if (tempPath[0] != 0)
      {
        wcscpy(copyPath, tempPath);
        CatAscii(copyPath, "Uninst.exe"); // we need not "Uninstall.exe" here
        
        if (CopyFileW(modulePath, copyPath, TRUE))
        {
          RemoveFileAfterReboot2(copyPath);
          RemoveFileAfterReboot2(tempPath);
          
          {
            STARTUPINFOW si;
            PROCESS_INFORMATION pi;
            cmdLine[0] = 0;
            
            // maybe CreateProcess supports path with spaces even without quotes.
            AddPathParam(cmdLine, copyPath);
            CatAscii(cmdLine, " /N /D=");
            AddPathParam(cmdLine, modulePrefix);
            
            if (cmdParams[0] != 0 && wcslen(cmdParams) < MAX_PATH * 2 + 10)
              wcscat(cmdLine, cmdParams);
            
            memset(&si, 0, sizeof(si));
            si.cb = sizeof(si);
            
            if (CreateProcessW(NULL, cmdLine, NULL, NULL, FALSE, 0, NULL, tempPath, &si, &pi))
            {
              CloseHandle(pi.hThread);
              if (pi.hProcess)
              {
                CloseHandle(pi.hProcess);
                return 0;
              }
            }
          }
        }
      }
    }
  }

  wcscpy(path, modulePrefix);

  if (workDir[0] != 0)
  {
    wcscpy(path, workDir);
    NormalizePrefix(path);
  }

  /*
  if (path[0] == 0)
  {
    HKEY key = 0;
    BoolInt ok = False;
    LONG res = RegOpenKeyExW(HKEY_CURRENT_USER, k_Reg_Software_7zip, 0, KEY_READ | k_Reg_WOW_Flag, &key);
    if (res == ERROR_SUCCESS)
    {
      ok = MyRegistry_QueryString(key, k_Reg_Path32, path);
      // ok = MyRegistry_QueryString(key, k_Reg_Path, path);
      RegCloseKey(key);
    }
  }
  */


  if (g_SilentMode)
    return Install();

  {
    int retCode = 1;
    g_HWND = CreateDialog(
        hInstance,
        // GetModuleHandle(NULL),
        MAKEINTRESOURCE(IDD_INSTALL), NULL, MyDlgProc);
    if (!g_HWND)
      return 1;

    {
      const HICON hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ICON));
      // SendMessage(g_HWND, WM_SETICON, (WPARAM)ICON_SMALL, (LPARAM)hIcon);
      SendMessage(g_HWND, WM_SETICON, (WPARAM)ICON_BIG, (LPARAM)hIcon);
    }

    {
      BOOL bRet;
      MSG msg;
      
      while ((bRet = GetMessage(&msg, NULL, 0, 0)) != 0)
      {
        if (bRet == -1)
          return retCode;
        if (!g_HWND)
          return retCode;

        if (!IsDialogMessage(g_HWND, &msg))
        {
          TranslateMessage(&msg);
          DispatchMessage(&msg);
        }
        if (!g_HWND)
          return retCode;

        if (g_Install_was_Pressed && !g_Finished)
        {
          retCode = Install();
          g_Finished = True;
          if (retCode != 0)
            break;
          if (!g_HWND)
            break;
          {
            SetDlgItemTextW(g_HWND, IDOK, L"Close");
            EnableWindow(GetDlgItem(g_HWND, IDOK), TRUE);
            EnableWindow(GetDlgItem(g_HWND, IDCANCEL), FALSE);
            SendMessage(g_HWND, WM_NEXTDLGCTL, (WPARAM)(void *)GetDlgItem(g_HWND, IDOK), TRUE);
          }
        }
      }

      if (g_HWND)
      {
        DestroyWindow(g_HWND);
        g_HWND = NULL;
      }
    }

    return retCode;
  }
}
