コード例 #1
0
static BOOL move_program() {
    if (MoveFileEx(L"Calibre Portable\\calibre-portable.exe", 
                L"..\\calibre-portable.exe", MOVEFILE_REPLACE_EXISTING) == 0) {
        show_last_error(L"Failed to move calibre-portable.exe, make sure calibre is not running");
        return false;
    }

    if (directory_exists(L"..\\Calibre")) {
        if (!rmtree(L"..\\Calibre")) {
            show_error(L"Failed to delete the Calibre program folder. Make sure calibre is not running.");
            return false;
        }
    }

    if (MoveFileEx(L"Calibre Portable\\Calibre", L"..\\Calibre", 0) == 0) {
        Sleep(4000); // Sleep and try again
        if (MoveFileEx(L"Calibre Portable\\Calibre", L"..\\Calibre", 0) == 0) {
            show_last_error(L"Failed to move calibre program folder. This is usually caused by an antivirus program or a file sync program like DropBox. Turn them off temporarily and try again. Underlying error: ");
            return false;
        }
    }

    if (!directory_exists(L"..\\Calibre Library")) {
        MoveFileEx(L"Calibre Portable\\Calibre Library", L"..\\Calibre Library", 0);
    }

    if (!directory_exists(L"..\\Calibre Settings")) {
        MoveFileEx(L"Calibre Portable\\Calibre Settings", L"..\\Calibre Settings", 0);
    }

    return true;
}
コード例 #2
0
static HANDLE temp_file(LPWSTR name) {
    UINT res;
    HANDLE h;

    res = GetTempFileNameW(L".", L"portable_data", 0, name);

    if (res == 0) { show_last_error(L"Failed to create temporary file to decompress portable data"); return INVALID_HANDLE_VALUE; }

    h = CreateFile(name, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    if (h == INVALID_HANDLE_VALUE) { show_last_error(L"Failed to open temp file to decompress portable data"); }
    return h;

}
コード例 #3
0
static BOOL load_data(LPVOID *data, DWORD *sz) {
    HRSRC rsrc;
    HGLOBAL h;

    rsrc = FindResourceW(NULL, L"extra", L"extra");
    if (rsrc == NULL) { show_last_error(L"Failed to find portable data in exe"); return false; }

    h = LoadResource(NULL, rsrc);
    if (h == NULL) { show_last_error(L"Failed to load portable data from exe"); return false; }

    *data = LockResource(h);
    if (*data == NULL) { show_last_error(L"Failed to lock portable data in exe"); return false; }

    *sz = SizeofResource(NULL, rsrc);
    if (sz == 0) { show_last_error(L"Failed to get size of portable data in exe"); return false; }

    return true;
}
コード例 #4
0
ファイル: portable.c プロジェクト: Eksmo/calibre
void launch_calibre(LPCTSTR exe, LPCTSTR config_dir, LPCTSTR library_dir) {
    DWORD dwFlags=0;
    STARTUPINFO si;
    PROCESS_INFORMATION pi;
    BOOL fSuccess; 
    TCHAR cmdline[BUFSIZE];

    if (! SetEnvironmentVariable(_T("CALIBRE_CONFIG_DIRECTORY"), config_dir)) {
        show_last_error(_T("Failed to set environment variables"));
        ExitProcess(1);
    }

    if (! SetEnvironmentVariable(_T("CALIBRE_PORTABLE_BUILD"), exe)) {
        show_last_error(_T("Failed to set environment variables"));
        ExitProcess(1);
    }

    dwFlags = CREATE_UNICODE_ENVIRONMENT | CREATE_NEW_PROCESS_GROUP;
    _sntprintf_s(cmdline, BUFSIZE, _TRUNCATE, _T(" \"--with-library=%s\""), library_dir);

    ZeroMemory( &si, sizeof(si) );
    si.cb = sizeof(si);
    ZeroMemory( &pi, sizeof(pi) );

    fSuccess = CreateProcess(exe, cmdline,
        NULL,           // Process handle not inheritable
        NULL,           // Thread handle not inheritable
        FALSE,          // Set handle inheritance to FALSE
        dwFlags,        // Creation flags http://msdn.microsoft.com/en-us/library/ms684863(v=vs.85).aspx
        NULL,           // Use parent's environment block
        NULL,           // Use parent's starting directory 
        &si,            // Pointer to STARTUPINFO structure
        &pi             // Pointer to PROCESS_INFORMATION structure
    );

    if (fSuccess == 0) {
        show_last_error(_T("Failed to launch the calibre program"));
    }

    // Close process and thread handles.
    CloseHandle( pi.hProcess );
    CloseHandle( pi.hThread );

}
コード例 #5
0
ファイル: main.c プロジェクト: kovidgoyal/build-calibre
static ENTRYPROC load_launcher_dll() {
    wchar_t buf[MAX_PATH];  // Cannot use a zero initializer for the array as it generates an implicit call to memset()
    wchar_t *dll_point = NULL;
    int i = 0;
    DWORD sz = 0; 
    HMODULE dll = 0;
    ENTRYPROC entrypoint = NULL;

    if ((sz = GetModuleFileNameW(NULL, buf, MAX_PATH)) >= MAX_PATH - 30) {
        show_error(L"Installation directory path too long", L"", 1);
        return NULL;
    }

    while (sz > 0) {
        if (buf[sz] == L'\\' || buf[sz] == L'/') { dll_point = buf + sz + 1; break; }
        sz--;
    }
    if (dll_point == NULL) {
        show_error(L"Executable path has no path separators", L"", 1);
        return NULL;
    }
    wsprintf(dll_point, L"%s\0\0", L"app\\DLLs");
    if (SetDllDirectoryW(buf) == 0) {
        show_last_error(L"Failed to set DLL directory");
        return NULL;
    }
    // Have to load ucrtbase manually first, otherwise loading fails on systems where the
    // Universal CRT is not installed.
    if (!LoadLibraryW(L"ucrtbase.dll")) {
        show_last_error(L"Unable to find ucrtbase.dll. You should install all Windows updates on your computer to get this file.");
        return NULL;
    }
    if (!(dll = LoadLibraryW(L"calibre-launcher.dll"))) {
        show_last_error(L"Failed to load: calibre-launcher.dll");
        return NULL;
    }
    if (!(entrypoint = (ENTRYPROC) GetProcAddress(dll, "execute_python_entrypoint"))) {
        show_last_error(L"Failed to get the calibre-launcher dll entry point");
        return NULL;
    }
    return entrypoint;
}
コード例 #6
0
static LPWSTR make_unpack_dir() {
    WCHAR buf[4*MAX_PATH] = {0};
    LPWSTR ans = NULL;

    if (directory_exists(L"_unpack_calibre_portable"))
        rmtree(L"_unpack_calibre_portable");

    if (!CreateDirectory(L"_unpack_calibre_portable", NULL) && GetLastError() != ERROR_ALREADY_EXISTS) {
        show_last_error(L"Failed to create temporary folder to unpack into");
        return ans;
    }

    if (!GetFullPathName(L"_unpack_calibre_portable", 4*MAX_PATH, buf, NULL)) {
        show_last_error(L"Failed to resolve path");
        return NULL;
    }

    ans = _wcsdup(buf);
    if (ans == NULL) show_error(L"Out of memory");
    return ans;

}
コード例 #7
0
ファイル: a_screen.c プロジェクト: wfp5p/elm
void alias_screen(int modified)
{
	/* Stolen from showscreen() */

	ClearScreen();

	alias_title(modified);

	last_header_page = -1;	 	/* force a redraw regardless */
	show_headers();

	if (mini_menu)
	  show_alias_menu();

	show_last_error();

}
コード例 #8
0
static BOOL find_portable_dir(LPCWSTR base, LPWSTR *result, BOOL *existing) {
    WCHAR buf[4*MAX_PATH] = {0};

    _snwprintf_s(buf, 4*MAX_PATH, _TRUNCATE, L"%s\\calibre-portable.exe", base);
    *existing = true;

    if (file_exists(buf)) {
        *result = _wcsdup(base);
        if (*result == NULL) { show_error(L"Out of memory"); return false; }
        return true;
    }

    WIN32_FIND_DATA fdFile; 
    HANDLE hFind = NULL;
    _snwprintf_s(buf, 4*MAX_PATH, _TRUNCATE, L"%s\\*", base);

    if((hFind = FindFirstFileEx(buf, FindExInfoStandard, &fdFile, FindExSearchLimitToDirectories, NULL, 0)) != INVALID_HANDLE_VALUE) {
        do {
            if(is_dots(fdFile.cFileName)) continue;

            if(fdFile.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
                _snwprintf_s(buf, 4*MAX_PATH, _TRUNCATE, L"%s\\%s\\calibre-portable.exe", base, fdFile.cFileName);
                if (file_exists(buf)) {
                    *result = _wcsdup(buf);
                    if (*result == NULL) { show_error(L"Out of memory"); return false; }
                    PathRemoveFileSpec(*result);
                    FindClose(hFind);
                    return true;
                }
            } 
        } while(FindNextFile(hFind, &fdFile));
        FindClose(hFind);
    }

    *existing = false;
    _snwprintf_s(buf, 4*MAX_PATH, _TRUNCATE, L"%s\\Calibre Portable", base);
    if (!CreateDirectory(buf, NULL) && GetLastError() != ERROR_ALREADY_EXISTS) {
        show_last_error(L"Failed to create Calibre Portable folder");
        return false;
    }
    *result = _wcsdup(buf);
    if (*result == NULL) { show_error(L"Out of memory"); return false; }

    return true;
}
コード例 #9
0
static size_t  
output_callback(void *ctx, const void *buf, size_t size)  
{  
    struct DataStream * ds = (struct DataStream *) ctx;  
    DWORD written = 0;
      
    if (size > 0) {  
        if (!WriteFile(ds->out, buf, size, &written, NULL)) {
            show_last_error(L"Failed to write uncompressed data to temp file");
            output_error_shown = 1;
            return 0;
        }
        written = SetFilePointer(ds->out, 0, NULL, FILE_CURRENT);
        ds->pd->SetProgress(written, UNCOMPRESSED_SIZE);
    }  
   
    return size;  
}  
コード例 #10
0
static void launch_calibre() {
    STARTUPINFO si;
    PROCESS_INFORMATION pi;

    ZeroMemory( &si, sizeof(si) );
    si.cb = sizeof(si);
    ZeroMemory( &pi, sizeof(pi) );


    if (CreateProcess(_wcsdup(L"calibre-portable.exe"), NULL,
            NULL, NULL, FALSE, CREATE_UNICODE_ENVIRONMENT | CREATE_NEW_PROCESS_GROUP,
            NULL, NULL, &si, &pi)
            == 0) {
        show_last_error(L"Failed to launch calibre portable");
    }

    // Close process and thread handles.
    CloseHandle( pi.hProcess );
    CloseHandle( pi.hThread );

}
コード例 #11
0
static BOOL extract(LPVOID cdata, DWORD csz) {
    HANDLE h;
    WCHAR tempnam[MAX_PATH+1] = {0};
    BOOL ret = true;
    HZIP zipf;
    ZIPENTRYW ze;
    ZRESULT res;
    int nitems;
    HRESULT hr;
    IProgressDialog *pd = NULL;

    hr = CoCreateInstance(CLSID_ProgressDialog, NULL,
            CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pd));

    if (FAILED(hr)) { show_error(L"Failed to create progress dialog"); return false; }
    pd->SetTitle(L"Extracting Calibre Portable");
    pd->SetLine(1, L"Decompressing data...", true, NULL);

    h = temp_file(tempnam);
    if (h == INVALID_HANDLE_VALUE) return false;

    pd->StartProgressDialog(NULL, NULL, PROGDLG_NORMAL | PROGDLG_AUTOTIME | PROGDLG_NOCANCEL, NULL);
    if (!decompress(cdata, csz, h, pd)) { ret = false; goto end; }
    SetFilePointer(h, 0, NULL, FILE_BEGIN);
    zipf = OpenZip(h, 0, ZIP_HANDLE);
    if (zipf == 0) { show_last_error(L"Failed to open zipped portable data"); ret = false; goto end; }

    res = GetZipItem(zipf, -1, &ze);
    if (res != ZR_OK) { show_zip_error(L"Failed to get count of items in portable data", L"", res); ret = false; goto end;}
    nitems = ze.index;

    pd->SetLine(1, L"Copying files...", true, NULL);
    if (!unzip(zipf, nitems, pd)) { ret = false; goto end; }
end:
    pd->StopProgressDialog();
    pd->Release();
    CloseHandle(h);
    DeleteFile(tempnam);
    return ret;
}
コード例 #12
0
ファイル: alias.c プロジェクト: wfp5p/elm
void alias(void)
{
/*
 *	Work with alias commands...
 */

	char name[NLEN], *address, buffer[SLEN];
	char *commap;
	static int  newaliases = 0;
	int  ch, i;
	int nutitle = 0;
	int too_long;

/*
 *	We're going to try to match the way elm does it at
 * 	he main menu.  I probably won't be able to use any
 *	main menu routines, but I will "borrow" from them. RLH
 */

	alias_main_state();		/* Save globals for return to main menu */

	open_alias_files(FALSE);	/* First, read the alias files. RLH */

	alias_screen(newaliases);

	while (1) {

	  redraw = 0;
	  nucurr = 0;
	  nufoot = 0;

	  prompt(nls_Prompt);
	  CleartoEOLN();
	  ch = GetKey(0);

	  MoveCursor(LINES-3,strlen(nls_Prompt)); CleartoEOS();

	  dprint(3, (debugfile, "\n-- Alias command: %c\n\n", ch));

	  switch (ch) {
	    case '?': redraw += alias_help();			break;

#ifdef ALLOW_SUBSHELL
	    case '!' : WriteChar('!');
	      alias_main_state(); /** reload index screen vars **/
	      redraw += subshell();
	      alias_main_state(); /** reload alias screen vars **/
	      break;
#endif /* ALLOW_SUBSHELL */

	    case '$': PutLine(-1, -1, catgets(elm_msg_cat,
					AliasesSet, AliasesResync,
					"Resynchronize aliases..."));
	           /*
	            * Process deletions and then see if we need to
	            * re-run the "newalias" routine.
	            */
		      if (resync_aliases(newaliases)) {
		        install_aliases();
	                newaliases = 0;
		        redraw++;
		      }
		      break;

	    case 'a': PutLine(-1, -1, catgets(elm_msg_cat,
					AliasesSet, AliasesAddCurrent,
					"Add address from current message..."));
		      clear_error();
		      if (add_current_alias()) {
		          newaliases++;
		          nutitle++;
		      }
		      break;

	    case 'c':
	              if (curr_alias > 0) {
			  PutLine(-1, -1, catgets(elm_msg_cat,
	                              AliasesSet, AliasesReplaceCurrent,
	                              "Replace current alias in database..."));
		          clear_error();
		          if (add_alias(TRUE, curr_alias-1)) {
		              newaliases++;
		              nutitle++;
		          }
	              }
	              else {
		          show_error(catgets(elm_msg_cat,
	                          AliasesSet, AliasesNoneToReplace,
				  "Warning: no aliases to replace!"));
	              }
		      break;

	    case 'e': PutLine(LINES-3, strlen(nls_Prompt),
	                  catgets(elm_msg_cat, AliasesSet, AliasesEdit,
	                      "Edit %s..."), ALIAS_TEXT);
	           /*
	            * Process aliases.text for deletions, etc.  You
	            * have to do this *before* checking current because
	            * all aliases could be marked for deletion.
	            */
	              (void) resync_aliases(newaliases);
	              if (edit_aliases_text()) {
	                  newaliases = 0;
	              }
		      redraw++;
		      break;

	    case 'm':
	              if (curr_alias > 0) {
			  PutLine(-1, -1, catgets(elm_msg_cat,
					  AliasesSet, AliasesMail, "Mail..."));
	                  redraw += a_sendmsg();
	              }
	              else {
		          show_error(catgets(elm_msg_cat,
	                          AliasesSet, AliasesNoneToMail,
				  "Warning: no aliases to send mail to!"));
	              }
		      break;

	    case 'n': PutLine(-1, -1, catgets(elm_msg_cat,
					AliasesSet, AliasesAddNew,
					"Add a new alias to database..."));
		      clear_error();
		      if (add_alias(FALSE, -1)) {
		          newaliases++;
		          nutitle++;
		      }
		      break;

	    case 'q':
	    case 'Q':
	    case 'i':
	    case 'I':
	    case 'r':
	    case 'R': PutLine(-1, -1, catgets(elm_msg_cat,
	    				AliasesSet, AliasesAddReturn,
					"Return to main menu..."));
	           /*
	            * leaving the alias system.  Must check for
	            * pending deletes, etc.  prompt is set to FALSE
	            * on uppercase letters so that deletions are
	            * NOT queried.
	            */
	              if (delete_aliases(newaliases, islower(ch))) {
	                install_aliases();
	                newaliases = 0;
	              }
		      clear_error();
		      alias_main_state();		/* Done with aliases. */
		      return;

	    case RETURN:
	    case LINE_FEED:
	    case ' ':
	    case 'v':
		      if (newaliases) {		/* Need this ?? */
		          show_error(catgets(elm_msg_cat,
	                          AliasesSet, AliasesNotInstalled,
				  "Warning: new aliases not installed yet!"));
	              }

	              if (curr_alias > 0) {
	                  if (aliases[curr_alias-1]->type & GROUP) {
	                      PutLine(LINES-1, 0, catgets(elm_msg_cat,
	                              AliasesSet, AliasesGroupAlias,
				      "Group alias: %-60.60s"),
	                          aliases[curr_alias-1]->address);
		          }
		          else {
	                      PutLine(LINES-1, 0, catgets(elm_msg_cat,
	                              AliasesSet, AliasesAliasedAddress,
				      "Aliased address: %-60.60s"),
	                          aliases[curr_alias-1]->address);
		          }
		      }
	              else {
		          show_error(catgets(elm_msg_cat,
	                          AliasesSet, AliasesNoneToView,
				  "Warning: no aliases to view!"));
		      }
		      break;

	    case 'x':
	    case 'X': PutLine(-1, -1, catgets(elm_msg_cat,
	    				AliasesSet, AliasesAddReturn,
					"Return to main menu..."));
	              exit_alias();
		      clear_error();
		      alias_main_state();		/* Done with aliases. */
		      return;

	    case 'f':
	    case 'F':
	              if (curr_alias > 0) {
		          clear_error();
		          strcpy(name, aliases[curr_alias-1]->alias);
		          if (ch == 'F') {
		              strcpy(buffer, catgets(elm_msg_cat,
	                              AliasesSet, AliasesFullyExpanded,
				      "Fully expand alias: "));
		              PutLine(LINES-2, 0, buffer);
			      if (enter_string(name, sizeof(name), -1, -1,
					  ESTR_REPLACE) < 0 || name[0] == '\0')
				break;
		          }
	                  too_long = FALSE;
		          address = get_alias_address(name, TRUE, &too_long);
		          if (address != NULL) {
		              while (TRUE) {
	                          ClearScreen();
			          PutLine(2,0, catgets(elm_msg_cat,
	                                  AliasesSet, AliasesAliasedFull,
					  "Aliased address for:\t%s\n\r"),
	                              name);
		                  i = 4;
		                  while (i < LINES-2) {
		                      if ((commap = strchr(address, (int)','))
	                                          == NULL) {
		                          PutLine(i, 4, address);
		                          break;
		                      }
		                      *commap = '\0';
		                      PutLine(i++, 4, address);
		                      address = commap+2;
		                  }
	                          PutLine(LINES-1, 0, catgets(elm_msg_cat,
	                                  AliasesSet, AliasesPressReturn,
					  "Press <return> to continue."));
			          (void) ReadCh();
		                  if (commap == NULL) {
			              redraw++;
		                      break;
		                  }
		              }
		          }
	                  else if (! too_long) {
			      show_error(catgets(elm_msg_cat,
	                              AliasesSet, AliasesNotFound,
				      "Not found."));
		          }
		      }
	              else {
		          show_error(catgets(elm_msg_cat,
	                          AliasesSet, AliasesNoneToView,
				  "Warning: no aliases to view!"));
		      }
		      break;

	  case KEY_REDRAW:
		      redraw = 1;
		      break;

	 /*
	  * None of the menu specific commands were chosen, therefore
	  * it must be a "motion" command (or an error).
	  */
	    default	: motion(ch);

	  }

	  if (redraw) {			/* Redraw screen if necessary */
	      alias_screen(newaliases);
	      nutitle = 0;
	  }

	  if (nutitle) {		/* Redraw title if necessary */
	      alias_title(newaliases);
	      nutitle = 0;
	  }

	  check_range();

	  if (nucurr == NEW_PAGE)
	    show_headers();
	  else if (nucurr == SAME_PAGE)
	    show_current();
	  else if (nufoot) {
	    if (mini_menu) {
	      MoveCursor(LINES-7, 0);
              CleartoEOS();
	      show_alias_menu();
	    }
	    else {
	      MoveCursor(LINES-4, 0);
	      CleartoEOS();
	    }
	    show_last_error();	/* for those operations that have to
				 * clear the footer except for a message.
				 */
	  }
	}			/* BIG while loop... */
}
コード例 #13
0
ファイル: out_utils.c プロジェクト: wfp5p/elm
static void do_showerror(const char *s)
{
  strcpy(err_buffer, s);
  show_last_error();
}
コード例 #14
0
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow)
{

    LPVOID cdata = NULL;
    DWORD csz = 0;
    int ret = 0, argc;
    HRESULT hr;
    LPWSTR tgt = NULL, dest = NULL, *argv, unpack_dir = NULL;
    BOOL existing = false, launch = false, automated = false;
    WCHAR buf[4*MAX_PATH] = {0}, mb_msg[4*MAX_PATH] = {0}, fdest[4*MAX_PATH] = {0};

    if (!load_data(&cdata, &csz)) return 0;

    hr = CoInitialize(NULL);
    if (FAILED(hr)) { show_error(L"Failed to initialize COM"); return 0; }

    // Get the target directory for installation
    argv = CommandLineToArgvW(GetCommandLine(), &argc);
    if (argv == NULL) { show_last_error(L"Failed to get command line"); return 0; }
    if (argc > 1) {
        tgt = argv[1];
        automated = true;
    } else {
        tgt = get_directory_from_user();
        if (tgt == NULL) goto end;
    }

    if (!directory_exists(tgt)) {
        show_detailed_error(L"The specified directory does not exist: ",
                tgt, 1);
        goto end;
    }

    // Ensure the path to Calibre Portable is not too long
    do {
        if (!find_portable_dir(tgt, &dest, &existing)) goto end;

        if (GetFullPathName(dest, MAX_PATH*4, fdest, NULL) == 0) {
            show_last_error(L"Failed to resolve target folder");
            goto end;
        }
        free(dest); dest = NULL;

        if (wcslen(fdest) > 58) {
            _snwprintf_s(buf, 4*MAX_PATH, _TRUNCATE, 
                L"Path to Calibre Portable (%s) too long. Must be less than 59 characters.", fdest);
            if (!existing) RemoveDirectory(fdest);
            show_error(buf);
            tgt = get_directory_from_user();
            if (tgt == NULL) goto end;
        }
    } while (wcslen(fdest) > 58);

    // Confirm the user wants to upgrade
    if (existing && !automated) {
        _snwprintf_s(mb_msg, 4*MAX_PATH, _TRUNCATE, 
            L"An existing install of Calibre Portable was found at %s. Do you want to upgrade it?",
            fdest);
        if (MessageBox(NULL, mb_msg,
                L"Upgrade Calibre Portable?", MB_ICONEXCLAMATION | MB_YESNO | MB_TOPMOST) != IDYES)
            goto end;
    }

    if (existing) {
        if (!ensure_not_running(fdest)) goto end;
    }

    // Make a temp dir to unpack into
    if (!SetCurrentDirectoryW(fdest)) { show_detailed_error(L"Failed to change to unzip directory: ", fdest, 0); goto end; }

    if ( (unpack_dir = make_unpack_dir()) == NULL ) goto end;
    if (!SetCurrentDirectoryW(unpack_dir)) { show_detailed_error(L"Failed to change to unpack directory: ", fdest, 0); goto end; }

    // Extract files
    if (!extract(cdata, csz)) goto end;

    // Move files from temp dir to the install dir
    if (!move_program()) goto end;

    _snwprintf_s(mb_msg, 4*MAX_PATH, _TRUNCATE, 
        L"Calibre Portable successfully installed to %s. Launch calibre?",
        fdest);
    launch = MessageBox(NULL, mb_msg,
        L"Success", MB_ICONINFORMATION | MB_YESNO | MB_TOPMOST) == IDYES;

end:
    if (unpack_dir != NULL) { SetCurrentDirectoryW(L".."); rmtree(unpack_dir); free(unpack_dir); }
    CoUninitialize();
    if (launch) launch_calibre();
    return 0;
}