From 5191c68337069ad0ffdaf17f88929162da78ec3b Mon Sep 17 00:00:00 2001 From: Pete Batard Date: Mon, 10 Jul 2023 11:34:50 +0200 Subject: [PATCH] [ui] keep user preferred image type when saving drive to VHD * Also fix a Coverity warning and use a better description for SELECT image types. --- src/format.c | 3 +- src/net.c | 2 +- src/rufus.c | 13 +-- src/rufus.h | 2 +- src/rufus.rc | 10 +-- src/settings.h | 4 +- src/stdlg.c | 239 ++++++++++++++++++------------------------------- src/vhd.c | 87 ++++++++++-------- 8 files changed, 156 insertions(+), 204 deletions(-) diff --git a/src/format.c b/src/format.c index e955f40e..b9017a37 100644 --- a/src/format.c +++ b/src/format.c @@ -1642,12 +1642,13 @@ DWORD WINAPI FormatThread(void* param) if ((boot_type == BT_IMAGE) && write_as_image) { // Special case for FFU images if (img_report.compression_type == IMG_COMPRESSION_FFU) { - char cmd[MAX_PATH + 128], *physical; + char cmd[MAX_PATH + 128], *physical = NULL; // Should have been filtered out beforehand assert(has_ffu_support); safe_unlockclose(hPhysicalDrive); physical = GetPhysicalName(SelectedDrive.DeviceNumber); static_sprintf(cmd, "dism /Apply-Ffu /ApplyDrive:%s /ImageFile:\"%s\"", physical, image_path); + safe_free(physical); uprintf("Running command: '%s", cmd); cr = RunCommandWithProgress(cmd, sysnative_dir, TRUE, MSG_261); if (cr != 0 && !IS_ERROR(FormatStatus)) { diff --git a/src/net.c b/src/net.c index ff394433..bdbb9c7a 100644 --- a/src/net.c +++ b/src/net.c @@ -1040,7 +1040,7 @@ static DWORD WINAPI DownloadISOThread(LPVOID param) #endif EXT_DECL(img_ext, GetShortName(url), __VA_GROUP__("*.iso"), __VA_GROUP__(lmprintf(MSG_036))); img_save.Type = VIRTUAL_STORAGE_TYPE_DEVICE_ISO; - img_save.ImagePath = FileDialog(TRUE, NULL, &img_ext, 0); + img_save.ImagePath = FileDialog(TRUE, NULL, &img_ext, NULL); if (img_save.ImagePath == NULL) { goto out; } diff --git a/src/rufus.c b/src/rufus.c index 3acf3ae2..ef248a72 100755 --- a/src/rufus.c +++ b/src/rufus.c @@ -139,7 +139,7 @@ char embedded_sl_version_str[2][12] = { "?.??", "?.??" }; char embedded_sl_version_ext[2][32]; char ClusterSizeLabel[MAX_CLUSTER_SIZES][64]; char msgbox[1024], msgbox_title[32], *ini_file = NULL, *image_path = NULL, *short_image_path; -char *archive_path = NULL, image_option_txt[128], *fido_url = NULL; +char *archive_path = NULL, image_option_txt[128], *fido_url = NULL, *save_image_type = NULL; StrArray BlockingProcess, ImageList; // Number of steps for each FS for FCC_STRUCTURE_PROGRESS const int nb_steps[FS_MAX] = { 5, 5, 12, 1, 10, 1, 1, 1, 1 }; @@ -1013,7 +1013,7 @@ BOOL CALLBACK LogCallback(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) log_size = GetDlgItemTextU(hDlg, IDC_LOG_EDIT, log_buffer, log_size); if (log_size != 0) { log_size--; // remove NUL terminator - filepath = FileDialog(TRUE, user_dir, &log_ext, 0); + filepath = FileDialog(TRUE, user_dir, &log_ext, NULL); if (filepath != NULL) FileIO(FILE_IO_WRITE, filepath, &log_buffer, &log_size); safe_free(filepath); @@ -2567,7 +2567,7 @@ static INT_PTR CALLBACK MainCallback(HWND hDlg, UINT message, WPARAM wParam, LPA EXT_DECL(arch_ext, NULL, __VA_GROUP__("*.zip"), __VA_GROUP__(lmprintf(MSG_309))); if (image_path == NULL) break; - archive_path = FileDialog(FALSE, NULL, &arch_ext, 0); + archive_path = FileDialog(FALSE, NULL, &arch_ext, NULL); if (archive_path != NULL) { struct __stat64 stat64 = { 0 }; _stat64U(archive_path, &stat64); @@ -2591,10 +2591,10 @@ static INT_PTR CALLBACK MainCallback(HWND hDlg, UINT message, WPARAM wParam, LPA char extensions[128] = "*.iso;*.img;*.vhd;*.vhdx;*.usb;*.bz2;*.bzip2;*.gz;*.lzma;*.xz;*.Z;*.zip;*.wim;*.esd;*.vtsi"; if (has_ffu_support) strcat(extensions, ";*.ffu"); - // If declared globaly, lmprintf(MSG_036) would be called on each message... + // If declared globaly, lmprintf(MSG_280) would be called on each message... EXT_DECL(img_ext, NULL, __VA_GROUP__(extensions), - __VA_GROUP__(lmprintf(MSG_036))); - image_path = FileDialog(FALSE, NULL, &img_ext, 0); + __VA_GROUP__(lmprintf(MSG_280))); + image_path = FileDialog(FALSE, NULL, &img_ext, NULL); if (image_path == NULL) { if (old_image_path != NULL) { // Reselect previous image @@ -3556,6 +3556,7 @@ skip_args_processing: enable_extra_hashes = ReadSettingBool(SETTING_ENABLE_EXTRA_HASHES); ignore_boot_marker = ReadSettingBool(SETTING_IGNORE_BOOT_MARKER); persistent_log = ReadSettingBool(SETTING_PERSISTENT_LOG); + save_image_type = ReadSettingStr(SETTING_PREFERRED_SAVE_IMAGE_TYPE); // This restores the Windows User Experience/unattend.xml mask from the saved user // settings, and is designed to work even if we add new options later. wue_options = ReadSetting32(SETTING_WUE_OPTIONS); diff --git a/src/rufus.h b/src/rufus.h index 53dc6251..4c485b92 100644 --- a/src/rufus.h +++ b/src/rufus.h @@ -655,7 +655,7 @@ extern BOOL DumpFatDir(const char* path, int32_t cluster); extern BOOL InstallSyslinux(DWORD drive_index, char drive_letter, int fs); extern uint16_t GetSyslinuxVersion(char* buf, size_t buf_size, char** ext); extern BOOL SetAutorun(const char* path); -extern char* FileDialog(BOOL save, char* path, const ext_t* ext, DWORD options); +extern char* FileDialog(BOOL save, char* path, const ext_t* ext, UINT* selected_ext); extern BOOL FileIO(enum file_io_type io_type, char* path, char** buffer, DWORD* size); extern unsigned char* GetResource(HMODULE module, char* name, char* type, const char* desc, DWORD* len, BOOL duplicate); extern DWORD GetResourceSize(HMODULE module, char* name, char* type, const char* desc); diff --git a/src/rufus.rc b/src/rufus.rc index 681afaa9..15de22a2 100644 --- a/src/rufus.rc +++ b/src/rufus.rc @@ -33,7 +33,7 @@ LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL IDD_DIALOG DIALOGEX 12, 12, 232, 326 STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_ACCEPTFILES -CAPTION "Rufus 4.2.2068" +CAPTION "Rufus 4.2.2069" FONT 9, "Segoe UI Symbol", 400, 0, 0x0 BEGIN LTEXT "Drive Properties",IDS_DRIVE_PROPERTIES_TXT,8,6,53,12,NOT WS_GROUP @@ -392,8 +392,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 4,2,2068,0 - PRODUCTVERSION 4,2,2068,0 + FILEVERSION 4,2,2069,0 + PRODUCTVERSION 4,2,2069,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -411,13 +411,13 @@ BEGIN VALUE "Comments", "https://rufus.ie" VALUE "CompanyName", "Akeo Consulting" VALUE "FileDescription", "Rufus" - VALUE "FileVersion", "4.2.2068" + VALUE "FileVersion", "4.2.2069" VALUE "InternalName", "Rufus" VALUE "LegalCopyright", "© 2011-2023 Pete Batard (GPL v3)" VALUE "LegalTrademarks", "https://www.gnu.org/licenses/gpl-3.0.html" VALUE "OriginalFilename", "rufus-4.2.exe" VALUE "ProductName", "Rufus" - VALUE "ProductVersion", "4.2.2068" + VALUE "ProductVersion", "4.2.2069" END END BLOCK "VarFileInfo" diff --git a/src/settings.h b/src/settings.h index 20f0f5ea..bf8c8ab2 100644 --- a/src/settings.h +++ b/src/settings.h @@ -1,7 +1,7 @@ /* * Rufus: The Reliable USB Formatting Utility * Settings access, through either registry or INI file - * Copyright © 2015-2022 Pete Batard + * Copyright © 2015-2023 Pete Batard * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,6 +19,7 @@ #include #include #include "rufus.h" +#include "msapi_utf8.h" #include "registry.h" #pragma once @@ -52,6 +53,7 @@ extern char* ini_file; #define SETTING_USE_UDF_VERSION "UseUdfVersion" #define SETTING_USE_VDS "UseVds" #define SETTING_PERSISTENT_LOG "PersistentLog" +#define SETTING_PREFERRED_SAVE_IMAGE_TYPE "PreferredSaveImageType" #define SETTING_PRESERVE_TIMESTAMPS "PreserveTimestamps" #define SETTING_VERBOSE_UPDATES "VerboseUpdateCheck" #define SETTING_WUE_OPTIONS "WindowsUserExperienceOptions" diff --git a/src/stdlg.c b/src/stdlg.c index 5316fb40..73839e49 100644 --- a/src/stdlg.c +++ b/src/stdlg.c @@ -90,14 +90,9 @@ void SetDialogFocus(HWND hDlg, HWND hCtrl) * *EACH* thread you invoke FileDialog from, as GetDisplayName() will * return error 0x8001010E otherwise. */ -char* FileDialog(BOOL save, char* path, const ext_t* ext, DWORD options) +char* FileDialog(BOOL save, char* path, const ext_t* ext, UINT* selected_ext) { - DWORD tmp; - OPENFILENAMEA ofn; - char selected_name[MAX_PATH]; - char *ext_string = NULL, *all_files = NULL; - size_t i, j, ext_strlen; - BOOL r; + size_t i; char* filepath = NULL; HRESULT hr = FALSE; IFileDialog *pfd = NULL; @@ -108,167 +103,107 @@ char* FileDialog(BOOL save, char* path, const ext_t* ext, DWORD options) if ((ext == NULL) || (ext->count == 0) || (ext->extension == NULL) || (ext->description == NULL)) return NULL; - dialog_showing++; filter_spec = (COMDLG_FILTERSPEC*)calloc(ext->count + 1, sizeof(COMDLG_FILTERSPEC)); - if (filter_spec != NULL) { - // Setup the file extension filter table - for (i = 0; i < ext->count; i++) { - filter_spec[i].pszSpec = utf8_to_wchar(ext->extension[i]); - filter_spec[i].pszName = utf8_to_wchar(ext->description[i]); - } - filter_spec[i].pszSpec = L"*.*"; - filter_spec[i].pszName = utf8_to_wchar(lmprintf(MSG_107)); + if (filter_spec == NULL) + return NULL; - hr = CoCreateInstance(save ? &CLSID_FileSaveDialog : &CLSID_FileOpenDialog, NULL, CLSCTX_INPROC, - &IID_IFileDialog, (LPVOID)&pfd); + dialog_showing++; - if (FAILED(hr)) { - SetLastError(hr); - uprintf("CoCreateInstance for FileOpenDialog failed: %s\n", WindowsErrorString()); - if (pfd != NULL) { - IFileDialog_Release(pfd); - pfd = NULL; // Just in case - } - goto fallback; - } + // Setup the file extension filter table + for (i = 0; i < ext->count; i++) { + filter_spec[i].pszSpec = utf8_to_wchar(ext->extension[i]); + filter_spec[i].pszName = utf8_to_wchar(ext->description[i]); + } + filter_spec[i].pszSpec = L"*.*"; + filter_spec[i].pszName = utf8_to_wchar(lmprintf(MSG_107)); - // Set the file extension filters - IFileDialog_SetFileTypes(pfd, (UINT)ext->count + 1, filter_spec); + hr = CoCreateInstance(save ? &CLSID_FileSaveDialog : &CLSID_FileOpenDialog, NULL, CLSCTX_INPROC, + &IID_IFileDialog, (LPVOID)&pfd); - if (path == NULL) { - // Try to use the "Downloads" folder as the initial default directory - const GUID download_dir_guid = - { 0x374de290, 0x123f, 0x4565, { 0x91, 0x64, 0x39, 0xc4, 0x92, 0x5e, 0x46, 0x7b } }; - hr = SHGetKnownFolderPath(&download_dir_guid, 0, 0, &wpath); - if (SUCCEEDED(hr)) { - hr = SHCreateItemFromParsingName(wpath, NULL, &IID_IShellItem, (LPVOID)&si_path); - if (SUCCEEDED(hr)) { - IFileDialog_SetDefaultFolder(pfd, si_path); - } - CoTaskMemFree(wpath); - } - } else { - wpath = utf8_to_wchar(path); + if (FAILED(hr)) { + SetLastError(hr); + uprintf("CoCreateInstance for FileOpenDialog failed: %s\n", WindowsErrorString()); + if (pfd != NULL) + goto out; + } + + // Set the file extension filters + IFileDialog_SetFileTypes(pfd, (UINT)ext->count + 1, filter_spec); + + if (path == NULL) { + // Try to use the "Downloads" folder as the initial default directory + const GUID download_dir_guid = + { 0x374de290, 0x123f, 0x4565, { 0x91, 0x64, 0x39, 0xc4, 0x92, 0x5e, 0x46, 0x7b } }; + hr = SHGetKnownFolderPath(&download_dir_guid, 0, 0, &wpath); + if (SUCCEEDED(hr)) { hr = SHCreateItemFromParsingName(wpath, NULL, &IID_IShellItem, (LPVOID)&si_path); if (SUCCEEDED(hr)) { - IFileDialog_SetFolder(pfd, si_path); + IFileDialog_SetDefaultFolder(pfd, si_path); } - safe_free(wpath); + CoTaskMemFree(wpath); } - - // Set the default filename - wfilename = utf8_to_wchar((ext->filename == NULL) ? "" : ext->filename); - if (wfilename != NULL) { - IFileDialog_SetFileName(pfd, wfilename); - } - // Set a default extension so that when the user switches filters it gets - // automatically updated. Note that the IFileDialog::SetDefaultExtension() - // doc says the extension shouldn't be prefixed with unwanted characters - // but it appears to work regardless so we don't bother cleaning it. - wext = utf8_to_wchar((ext->extension == NULL) ? "" : ext->extension[0]); - if (wext != NULL) { - IFileDialog_SetDefaultExtension(pfd, wext); - } - - // Display the dialog - hr = IFileDialog_Show(pfd, hMainDialog); - - // Cleanup - safe_free(wext); - safe_free(wfilename); - for (i = 0; i < ext->count; i++) { - safe_free(filter_spec[i].pszSpec); - safe_free(filter_spec[i].pszName); - } - safe_free(filter_spec[i].pszName); - safe_free(filter_spec); - + } else { + wpath = utf8_to_wchar(path); + hr = SHCreateItemFromParsingName(wpath, NULL, &IID_IShellItem, (LPVOID)&si_path); if (SUCCEEDED(hr)) { - // Obtain the result of the user's interaction with the dialog. - hr = IFileDialog_GetResult(pfd, &psiResult); - if (SUCCEEDED(hr)) { - hr = IShellItem_GetDisplayName(psiResult, SIGDN_FILESYSPATH, &wpath); - if (SUCCEEDED(hr)) { - filepath = wchar_to_utf8(wpath); - CoTaskMemFree(wpath); - } else { - SetLastError(hr); - uprintf("Unable to access file path: %s\n", WindowsErrorString()); - } - IShellItem_Release(psiResult); - } - } else if ((hr & 0xFFFF) != ERROR_CANCELLED) { - // If it's not a user cancel, assume the dialog didn't show and fallback - SetLastError(hr); - uprintf("Could not show FileOpenDialog: %s\n", WindowsErrorString()); - goto fallback; + IFileDialog_SetFolder(pfd, si_path); } - IFileDialog_Release(pfd); - dialog_showing--; - return filepath; + safe_free(wpath); } -fallback: + // Set the default filename + wfilename = utf8_to_wchar((ext->filename == NULL) ? "" : ext->filename); + if (wfilename != NULL) + IFileDialog_SetFileName(pfd, wfilename); + // Set a default extension so that when the user switches filters it gets + // automatically updated. Note that the IFileDialog::SetDefaultExtension() + // doc says the extension shouldn't be prefixed with unwanted characters + // but it appears to work regardless so we don't bother cleaning it. + wext = utf8_to_wchar((ext->extension == NULL) ? "" : ext->extension[0]); + if (wext != NULL) + IFileDialog_SetDefaultExtension(pfd, wext); + // Set the current selected extension + IFileDialog_SetFileTypeIndex(pfd, selected_ext == NULL ? 0 : *selected_ext); + + // Display the dialog and (optionally) get the selected extension index + hr = IFileDialog_Show(pfd, hMainDialog); + if (selected_ext != NULL) + IFileDialog_GetFileTypeIndex(pfd, selected_ext); + + // Cleanup + safe_free(wext); + safe_free(wfilename); + for (i = 0; i < ext->count; i++) { + safe_free(filter_spec[i].pszSpec); + safe_free(filter_spec[i].pszName); + } + safe_free(filter_spec[i].pszName); safe_free(filter_spec); - if (pfd != NULL) { - IFileDialog_Release(pfd); + + if (SUCCEEDED(hr)) { + // Obtain the result of the user's interaction with the dialog. + hr = IFileDialog_GetResult(pfd, &psiResult); + if (SUCCEEDED(hr)) { + hr = IShellItem_GetDisplayName(psiResult, SIGDN_FILESYSPATH, &wpath); + if (SUCCEEDED(hr)) { + filepath = wchar_to_utf8(wpath); + CoTaskMemFree(wpath); + } else { + SetLastError(hr); + uprintf("Unable to access file path: %s", WindowsErrorString()); + } + IShellItem_Release(psiResult); + } + } else if ((hr & 0xFFFF) != ERROR_CANCELLED) { + // If it's not a user cancel, assume the dialog didn't show and fallback + SetLastError(hr); + uprintf("Could not show FileOpenDialog: %s", WindowsErrorString()); } - memset(&ofn, 0, sizeof(ofn)); - ofn.lStructSize = sizeof(ofn); - ofn.hwndOwner = hMainDialog; - // Selected File name - static_sprintf(selected_name, "%s", (ext->filename == NULL)?"":ext->filename); - ofn.lpstrFile = selected_name; - ofn.nMaxFile = MAX_PATH; - // Set the file extension filters - all_files = lmprintf(MSG_107); - ext_strlen = 0; - for (i=0; icount; i++) { - ext_strlen += safe_strlen(ext->description[i]) + 2*safe_strlen(ext->extension[i]) + sizeof(" ()\r\r"); - } - ext_strlen += safe_strlen(all_files) + sizeof(" (*.*)\r*.*\r"); - ext_string = (char*)malloc(ext_strlen+1); - if (ext_string == NULL) - return NULL; - ext_string[0] = 0; - for (i=0, j=0; icount; i++) { - j += _snprintf(&ext_string[j], ext_strlen-j, "%s (%s)\r%s\r", ext->description[i], ext->extension[i], ext->extension[i]); - } - j = _snprintf(&ext_string[j], ext_strlen-j, "%s (*.*)\r*.*\r", all_files); - // Microsoft could really have picked a better delimiter! - for (i=0; i (UINT)img_ext.count) + i = 2; + img_save.ImagePath = FileDialog(TRUE, NULL, &img_ext, &i); + assert(i > 0 && i <= (UINT)img_ext.count); + save_image_type = (char*) &_img_ext_x[i - 1][2]; + WriteSettingStr(SETTING_PREFERRED_SAVE_IMAGE_TYPE, save_image_type); + switch (i) { + case 1: img_save.Type = VIRTUAL_STORAGE_TYPE_DEVICE_VHD; - else if (safe_strstr(img_save.ImagePath, ".ffu") != NULL) + break; + case 3: img_save.Type = VIRTUAL_STORAGE_TYPE_DEVICE_FFU; + break; + default: + img_save.Type = VIRTUAL_STORAGE_TYPE_DEVICE_VHDX; + break; + } img_save.BufSize = DD_BUFFER_SIZE; img_save.DeviceSize = SelectedDrive.DiskSize; if (img_save.DevicePath != NULL && img_save.ImagePath != NULL) { // Reset all progress bars SendMessage(hMainDialog, UM_PROGRESS_INIT, 0, 0); FormatStatus = 0; - free_space.QuadPart = 0; - if ((GetVolumePathNameA(img_save.ImagePath, path, sizeof(path))) - && (GetDiskFreeSpaceExA(path, &free_space, NULL, NULL)) - && ((LONGLONG)free_space.QuadPart > (SelectedDrive.DiskSize + 512))) { - // Disable all controls except Cancel - EnableControls(FALSE, FALSE); - FormatStatus = 0; - InitProgress(TRUE); - format_thread = CreateThread(NULL, 0, img_save.Type == VIRTUAL_STORAGE_TYPE_DEVICE_FFU ? - FfuSaveImageThread : VhdSaveImageThread, &img_save, 0, NULL); - if (format_thread != NULL) { - uprintf("\r\nSave to VHD operation started"); - PrintInfo(0, -1); - SendMessage(hMainDialog, UM_TIMER_START, 0, 0); - } else { - uprintf("Unable to start VHD save thread"); - FormatStatus = ERROR_SEVERITY_ERROR | FAC(FACILITY_STORAGE) | APPERR(ERROR_CANT_START_THREAD); - safe_free(img_save.DevicePath); - safe_free(img_save.ImagePath); - PostMessage(hMainDialog, UM_FORMAT_COMPLETED, (WPARAM)FALSE, 0); - } - } else { - if (free_space.QuadPart == 0) { - uprintf("Unable to isolate drive name for VHD save"); - FormatStatus = ERROR_SEVERITY_ERROR | FAC(FACILITY_STORAGE) | ERROR_PATH_NOT_FOUND; - } else { - // TODO: Might be salvageable for compressed VHDX + if (img_save.Type == VIRTUAL_STORAGE_TYPE_DEVICE_VHD) { + free_space.QuadPart = 0; + if ((GetVolumePathNameA(img_save.ImagePath, path, sizeof(path))) + && (GetDiskFreeSpaceExA(path, &free_space, NULL, NULL)) + && ((LONGLONG)free_space.QuadPart < (SelectedDrive.DiskSize + 512))) { uprintf("The VHD size is too large for the target drive"); FormatStatus = ERROR_SEVERITY_ERROR | FAC(FACILITY_STORAGE) | ERROR_FILE_TOO_LARGE; + PostMessage(hMainDialog, UM_FORMAT_COMPLETED, (WPARAM)FALSE, 0); + goto out; } - safe_free(img_save.DevicePath); - safe_free(img_save.ImagePath); + } + // Disable all controls except Cancel + EnableControls(FALSE, FALSE); + FormatStatus = 0; + InitProgress(TRUE); + format_thread = CreateThread(NULL, 0, img_save.Type == VIRTUAL_STORAGE_TYPE_DEVICE_FFU ? + FfuSaveImageThread : VhdSaveImageThread, &img_save, 0, NULL); + if (format_thread != NULL) { + uprintf("\r\nSave to VHD operation started"); + PrintInfo(0, -1); + SendMessage(hMainDialog, UM_TIMER_START, 0, 0); + } else { + uprintf("Unable to start VHD save thread"); + FormatStatus = ERROR_SEVERITY_ERROR | FAC(FACILITY_STORAGE) | APPERR(ERROR_CANT_START_THREAD); PostMessage(hMainDialog, UM_FORMAT_COMPLETED, (WPARAM)FALSE, 0); } } +out: + if (format_thread == NULL) { + safe_free(img_save.DevicePath); + safe_free(img_save.ImagePath); + } }