#ifndef UNICODE #define UNICODE #endif #ifndef _UNICODE #define _UNICODE #endif #include #include #include #include #include #include #include #include #include #include #include #include "XUnzip.h" #define BUFSIZE 4096 // Error handling {{{ static void show_error(LPCWSTR msg) { MessageBeep(MB_ICONERROR); MessageBox(NULL, msg, L"Error", MB_OK|MB_ICONERROR); } static void show_detailed_error(LPCWSTR preamble, LPCWSTR msg, int code) { LPWSTR buf; buf = (LPWSTR)LocalAlloc(LMEM_ZEROINIT, sizeof(WCHAR)* (wcslen(msg) + wcslen(preamble) + 80)); _snwprintf_s(buf, LocalSize(buf) / sizeof(WCHAR), _TRUNCATE, L"%s\r\n %s (Error Code: %d)\r\n", preamble, msg, code); show_error(buf); LocalFree(buf); } static void show_zip_error(LPCWSTR preamble, LPCWSTR msg, ZRESULT code) { LPWSTR buf; char msgbuf[1024] = {0}; FormatZipMessage(code, msgbuf, 1024); buf = (LPWSTR)LocalAlloc(LMEM_ZEROINIT, sizeof(WCHAR)* (wcslen(preamble) + wcslen(msg) + 1100)); _snwprintf_s(buf, LocalSize(buf) / sizeof(WCHAR), _TRUNCATE, L"%s\r\n %s (Error: %S)\r\n", preamble, msg, msgbuf); show_error(buf); LocalFree(buf); } static void show_last_error_crt(LPCWSTR preamble) { WCHAR buf[BUFSIZE]; int err = 0; _get_errno(&err); _wcserror_s(buf, BUFSIZE, err); show_detailed_error(preamble, buf, err); } static void show_last_error(LPCWSTR preamble) { WCHAR *msg = NULL; DWORD dw = GetLastError(); FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, dw, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPWSTR)&msg, 0, NULL ); show_detailed_error(preamble, msg, (int)dw); } // }}} // Load, decompress and extract data {{{ 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; } static BOOL unzip(HZIP zipf, int nitems, IProgressDialog *pd) { int i = 0; ZRESULT res; ZIPENTRYW ze; for (i = 0; i < nitems; i++) { res = GetZipItem(zipf, i, &ze); if (res != ZR_OK) { show_zip_error(L"Failed to get zip item", L"", res); return false;} res = UnzipItem(zipf, i, ze.name, 0, ZIP_FILENAME); if (res != ZR_OK) { CloseZip(zipf); show_zip_error(L"Failed to extract zip item (is your disk full?):", ze.name, res); return false;} pd->SetLine(2, ze.name, true, NULL); pd->SetProgress(i, nitems); } CloseZip(zipf); return true; } 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; } struct DataStream { const unsigned char *in_data; size_t in_len; HANDLE out; IProgressDialog *pd; }; static int input_callback(void *ctx, void *buf, size_t * size) { size_t rd = 0; struct DataStream * ds = (struct DataStream *) ctx; rd = (ds->in_len < *size) ? ds->in_len : *size; if (rd > 0) { memcpy(buf, (void*) ds->in_data, rd); ds->in_data += rd; ds->in_len -= rd; } *size = rd; return 0; } static int output_error_shown = 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; } static BOOL decompress(LPVOID src, DWORD src_sz, HANDLE out, IProgressDialog *pd) { elzma_decompress_handle h; struct DataStream ds; int rc; h = elzma_decompress_alloc(); if (h == NULL) { show_error(L"Out of memory"); return false; } ds.in_data = (unsigned char*)src; ds.in_len = src_sz; ds.out = out; ds.pd = pd; rc = elzma_decompress_run(h, input_callback, (void *) &ds, output_callback, (void *) &ds, ELZMA_lzip); if (rc != ELZMA_E_OK) { if (!output_error_shown) show_detailed_error(L"Failed to decompress portable data", L"", rc); elzma_decompress_free(&h); return false; } elzma_decompress_free(&h); return true; } 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; } // }}} // Find calibre portable directory and install/upgrade into it {{{ static BOOL directory_exists( LPCWSTR path ) { if( _waccess_s( path, 0 ) == 0 ) { struct _stat status; _wstat( path, &status ); return (status.st_mode & S_IFDIR) != 0; } return FALSE; } static BOOL file_exists( LPCWSTR path ) { if( _waccess_s( path, 0 ) == 0 ) { struct _stat status; _wstat( path, &status ); return (status.st_mode & S_IFREG) != 0; } return FALSE; } static LPWSTR get_directory_from_user() { WCHAR name[MAX_PATH+1] = {0}; LPWSTR path = NULL; PIDLIST_ABSOLUTE ret; path = (LPWSTR)calloc(2*MAX_PATH, sizeof(WCHAR)); if (path == NULL) { show_error(L"Out of memory"); return NULL; } int image = 0; BROWSEINFO bi = { NULL, NULL, name, L"Select the folder where you want to install or update Calibre Portable", BIF_RETURNONLYFSDIRS | BIF_DONTGOBELOWDOMAIN | BIF_USENEWUI, NULL, NULL, image }; ret = SHBrowseForFolder(&bi); if (ret == NULL) { return NULL; } if (!SHGetPathFromIDList(ret, path)) { show_detailed_error(L"The selected folder is not valid: ", name, 0); return NULL; } return path; } static bool is_dots(LPCWSTR name) { return wcscmp(name, L".") == 0 || wcscmp(name, L"..") == 0; } static bool rmtree(LPCWSTR path) { SHFILEOPSTRUCTW op; WCHAR buf[4*MAX_PATH + 2] = {0}; if (GetFullPathName(path, 4*MAX_PATH, buf, NULL) == 0) return false; op.hwnd = NULL; op.wFunc = FO_DELETE; op.pFrom = buf; op.pTo = NULL; op.fFlags = FOF_NOCONFIRMATION | FOF_NOERRORUI | FOF_SILENT | FOF_NOCONFIRMMKDIR; op.fAnyOperationsAborted = false; op.hNameMappings = NULL; op.lpszProgressTitle = NULL; return SHFileOperationW(&op) == 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; } 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; } 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; } // }}} static BOOL ensure_not_running(LPCWSTR dest) { DWORD processes[4096], needed, num; unsigned int i; WCHAR name[4*MAX_PATH] = L""; HANDLE h; DWORD len; LPWSTR fname = NULL; if ( !EnumProcesses( processes, sizeof(processes), &needed ) ) { return true; } num = needed / sizeof(DWORD); for (i = 0; i < num; i++) { if (processes[i] == 0) continue; h = OpenProcess( PROCESS_QUERY_INFORMATION, FALSE, processes[i] ); if (h != NULL) { len = GetProcessImageFileNameW(h, name, 4*MAX_PATH); CloseHandle(h); if (len != 0) { name[len] = 0; fname = PathFindFileName(name); if (wcscmp(fname, L"calibre.exe") == 0) { show_error(L"Calibre appears to be running on your computer. Please quit it before trying to install Calibre Portable."); return false; } } } } return true; } 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 ); } 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; }