diff --git a/.gitignore b/.gitignore index 1d13f7e..1aef19c 100755 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ -*.o +.gitignore + +.o *.so *.a *.test diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index d6278f2..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "files.associations": { - "cstdlib": "c", - "memory": "cpp", - "semaphore": "cpp", - "stop_token": "cpp", - "iostream": "cpp" - } -} \ No newline at end of file diff --git a/ABSTRACT.md b/ABSTRACT.md new file mode 100644 index 0000000..ff256a3 --- /dev/null +++ b/ABSTRACT.md @@ -0,0 +1,17 @@ +# Windows Binary Fuzzing + +Modern-day fuzzing is one of the most efficient techniques to automatically detect bugs and vulnerabilities in software projects [1]. It works by automatically feeding pseudo-randomly generated input data to the target program under test. However, most fuzzing techniques are only possible for target programs with source code and without a GUI [2]. This is because closed-source programs and programs with a GUI cannot easily be harnessed by fuzzers. This means, that the fuzzer cannot automatically find a good way to feed input into the program. In the scope of this thesis/project, closed-source GUI programs on Windows should be fuzzed. Contrary to the Linux and open-source community, the Windows ecosystem has received little attention. Therefore, in the scope of this project, a tool should be developed, which automatically harnesses and fuzzes Windows applications. To achieve this, simple binary rewriting frameworks [3] may be used to replace GUI file opening dialoges with an automatic way to feed file input into a program. Then, well-established fuzzing tools [4,5,6] can be used to fuzz multiple Windows-only programs. This should provide a first look into, whether the applications in the Windows ecosystem are as vulnerable as expected. + +References: + +[1] https://github.com/google/oss-fuzz + +[2] https://www.usenix.org/system/files/usenixsecurity24-schilling.pdf + +[3] https://ieeexplore.ieee.org/abstract/document/8023154/ + +[4] https://github.com/googleprojectzero/winafl + +[5] https://par.nsf.gov/servlets/purl/10308834 + +[6] https://www.usenix.org/system/files/sec21-nagy.pdf diff --git a/README.md b/README.md index 4895d5c..ebb49a5 100644 --- a/README.md +++ b/README.md @@ -4,12 +4,17 @@ - [x] `GetOpenFileNameA` (ANSI) - [x] `GetOpenFileNameW` (UTF-16) +- [x] `GetSaveFileNameA` (ANSI) +- [x] `GetSaveFileNameW` (UTF-16) - [x] `IFileOpenDialog` +- [x] `IFileSaveDialog` - [x] `QFileDialog::getOpenFileName` - [ ] `QFileDialog::getOpenFileNames` - [x] `QFileDialog::getOpenFileUrl` - [ ] `QFileDialog::getOpenFileUrls` - [ ] `QFileDialog::getOpenFileContent` +- [x] `QFileDialog::getSaveFileName` +- [x] `QFileDialog::getSaveFileUrl` - [ ] `wxFileDialog` ## Build scripts diff --git a/client/src/client.c b/client/src/client.c index 804f9f2..bf18bce 100644 --- a/client/src/client.c +++ b/client/src/client.c @@ -21,8 +21,11 @@ const char *MANGLED_QFileDialog_getOpenFileNames = "?getOpenFileNames@QFileDialo const char *MANGLED_QFileDialog_getOpenFileUrl = "?getOpenFileUrl@QFileDialog@@SA?AVQUrl@@PEAVQWidget@@AEBVQString@@AEBV2@1PEAV4@V?$QFlags@W4Option@QFileDialog@@@@AEBV?$QList@VQString@@@@@Z"; const char *MANGLED_QFileDialog_getOpenFileUrls = "?getOpenFileUrls@QFileDialog@@SA?AV?$QList@VQUrl@@@@PEAVQWidget@@AEBVQString@@AEBVQUrl@@1PEAV4@V?$QFlags@W4Option@QFileDialog@@@@AEBV?$QList@VQString@@@@@Z"; const char *MANGLED_QFileDialog_getOpenFileContent = "?getOpenFileContent@QFileDialog@@SAXAEBVQString@@AEBV?$function@$$A6AXAEBVQString@@AEBVQByteArray@@@Z@std@@PEAVQWidget@@@Z"; +const char *MANGLED_QFileDialog_getSaveFileName = "?getSaveFileName@QFileDialog@@SA?AVQString@@PEAVQWidget@@AEBV2@11PEAV2@V?$QFlags@W4Option@QFileDialog@@@@@Z"; +const char *MANGLED_QFileDialog_getSaveFileUrl = "?getSaveFileUrl@QFileDialog@@SA?AVQUrl@@PEAVQWidget@@AEBVQString@@AEBV2@1PEAV4@V?$QFlags@W4Option@QFileDialog@@@@AEBV?$QList@VQString@@@@@Z"; -char *CUSTOM_FILE = "C:\\Users\\vboxuser\\Downloads\\spoofed_number.txt"; +char *OPEN_FILE = "C:\\Users\\vboxuser\\Downloads\\spoofed_number.txt"; +char *SAVE_FILE = "C:\\Users\\vboxuser\\Downloads\\spoofed_save.txt"; // Function pointers for functions we need to call ourselves static void (*fn_QString_const)(void *qstring, const char *str) = NULL; @@ -46,9 +49,11 @@ static void pre_IFileOpenDialog_GetResult(void *wrapctx, void **user_data); static void pre_IFileOpenDialog_Show(void *wrapctx, void **user_data); static HRESULT custom_IFileDialog_Show(void *, void *); -static HRESULT custom_IFileDialog_GetResult(void *, IShellItem **ppsitem); +static HRESULT custom_IFileOpenDialog_GetResult(void *, IShellItem **ppsitem); +static HRESULT custom_IFileSaveDialog_GetResult(void *, IShellItem **ppsitem); -struct IFileOpenDialogVtbl customFODVtbl = {}; +struct IFileOpenDialogVtbl customFODVtbl = {}; +struct IFileSaveDialogVtbl customFSDVtbl = {}; // --- drwrap Callbacks for WinAPI Functions --- @@ -57,7 +62,7 @@ static void pre_GetOpenFileNameA(void *wrapctx, void **user_data) OPENFILENAMEA *ofn = (OPENFILENAMEA *)drwrap_get_arg(wrapctx, 0); if (ofn && ofn->lpstrFile) { - strncpy(ofn->lpstrFile, CUSTOM_FILE, ofn->nMaxFile - 1); + strncpy(ofn->lpstrFile, OPEN_FILE, ofn->nMaxFile - 1); ofn->lpstrFile[ofn->nMaxFile - 1] = '\0'; } @@ -69,7 +74,31 @@ static void pre_GetOpenFileNameW(void *wrapctx, void **user_data) OPENFILENAMEW *ofn = (OPENFILENAMEW *)drwrap_get_arg(wrapctx, 0); if (ofn && ofn->lpstrFile) { - mbstowcs(ofn->lpstrFile, CUSTOM_FILE, ofn->nMaxFile - 1); + mbstowcs(ofn->lpstrFile, OPEN_FILE, ofn->nMaxFile - 1); + ofn->lpstrFile[ofn->nMaxFile - 1] = L'\0'; + } + + drwrap_skip_call(wrapctx, (void *)1, 0); +} + +static void pre_GetSaveFileNameA(void *wrapctx, void **user_data) +{ + OPENFILENAMEA *ofn = (OPENFILENAMEA *)drwrap_get_arg(wrapctx, 0); + if (ofn && ofn->lpstrFile) + { + strncpy(ofn->lpstrFile, SAVE_FILE, ofn->nMaxFile - 1); + ofn->lpstrFile[ofn->nMaxFile - 1] = '\0'; + } + + drwrap_skip_call(wrapctx, (void *)1, 0); +} + +static void pre_GetSaveFileNameW(void *wrapctx, void **user_data) +{ + OPENFILENAMEW *ofn = (OPENFILENAMEW *)drwrap_get_arg(wrapctx, 0); + if (ofn && ofn->lpstrFile) + { + mbstowcs(ofn->lpstrFile, SAVE_FILE, ofn->nMaxFile - 1); ofn->lpstrFile[ofn->nMaxFile - 1] = L'\0'; } @@ -84,7 +113,7 @@ static void pre_QFileDialog_getOpenFileName(void *wrapctx, void **user_data) return; // The return value is passed as a pointer in the first argument (sret) void *qstring_ret_ptr = (void *)drwrap_get_arg(wrapctx, 0); - fn_QString_const(qstring_ret_ptr, CUSTOM_FILE); + fn_QString_const(qstring_ret_ptr, OPEN_FILE); drwrap_skip_call(wrapctx, NULL, 0); } @@ -95,7 +124,46 @@ static void pre_QFileDialog_getOpenFileUrl(void *wrapctx, void **user_data) const char *prefix = "file:///"; char urlbuf[MAX_PATH + 10]; - snprintf(urlbuf, sizeof(urlbuf), "%s%s", prefix, CUSTOM_FILE); + snprintf(urlbuf, sizeof(urlbuf), "%s%s", prefix, OPEN_FILE); + + // Replace backslashes for URL format + for (char *p = urlbuf; *p; ++p) + { + if (*p == '\\') + *p = '/'; + } + + // Create a temporary QString for the URL + char local_qstring[24]; // Allocate space for a QString object on the stack + fn_QString_const(local_qstring, urlbuf); + + // Get the return QUrl object pointer (sret) + void *qurl_ret_ptr = (void *)drwrap_get_arg(wrapctx, 0); + fn_QUrl_const(qurl_ret_ptr, local_qstring, 0); // 0 is TolerantMode + + drwrap_skip_call(wrapctx, NULL, 0); +} + +static void pre_QFileDialog_getSaveFileName(void *wrapctx, void **user_data) +{ + if (fn_QString_const == NULL) + return; + + // The return value is passed as a pointer in the first argument (sret) + void *qstring_ret_ptr = (void *)drwrap_get_arg(wrapctx, 0); + fn_QString_const(qstring_ret_ptr, SAVE_FILE); + drwrap_skip_call(wrapctx, NULL, 0); +} + +static void pre_QFileDialog_getSaveFileUrl(void *wrapctx, void **user_data) +{ + if (fn_QString_const == NULL || fn_QUrl_const == NULL) + return; + + const char *prefix = "file:///"; + char urlbuf[MAX_PATH + 10]; + snprintf(urlbuf, sizeof(urlbuf), "%s%s", prefix, SAVE_FILE); + // Replace backslashes for URL format for (char *p = urlbuf; *p; ++p) { @@ -116,25 +184,54 @@ static void pre_QFileDialog_getOpenFileUrl(void *wrapctx, void **user_data) // --- COM Wrapping Logic --- +const int TYPE_IFileOpenDialog = 1; +const int TYPE_IFileSaveDialog = 2; + +struct CoCreateInstance_data { + int type; + void *dialog; +}; + static void pre_CoCreateInstance(void *wrapctx, void **user_data) { + *user_data = NULL; + // Arguments: rclsid, pUnkOuter, dwClsContext, riid, ppv const IID *riid = (const IID *)drwrap_get_arg(wrapctx, 3); const CLSID *rclsid = (const CLSID *)drwrap_get_arg(wrapctx, 0); + if (!rclsid || !riid) return; - // We are only interested in the creation of a FileOpenDialog - if (rclsid != NULL && IsEqualIID(rclsid, &CLSID_FileOpenDialog) && - riid != NULL && IsEqualIID(riid, &IID_IFileOpenDialog)) + int type = 0; + void *dialog = NULL; + + if (IsEqualIID(rclsid, &CLSID_FileOpenDialog) && IsEqualIID(riid, &IID_IFileOpenDialog)) { - dr_printf("drwrap: Intercepting CoCreateInstance for FileOpenDialog\n"); // Pass the pointer to the output interface pointer (ppv) to the post-hook - *user_data = drwrap_get_arg(wrapctx, 4); + dialog = drwrap_get_arg(wrapctx, 4); + type = TYPE_IFileOpenDialog; } - else + + if (IsEqualIID(rclsid, &CLSID_FileSaveDialog) && IsEqualIID(riid, &IID_IFileSaveDialog)) { - *user_data = NULL; + dr_printf("drwrap: Intercepting CoCreateInstance for FileSaveDialog\n"); + // Pass the pointer to the output interface pointer (ppv) to the post-hook + dialog = drwrap_get_arg(wrapctx, 4); + type = TYPE_IFileSaveDialog; } + + if (!type || !dialog) return; + + struct CoCreateInstance_data *m_user_data = malloc(sizeof(*m_user_data)); + if (!m_user_data) { + perror("malloc"); + dr_exit_process(1); + } + + m_user_data->type = type; + m_user_data->dialog = dialog; + + *user_data = (void *)m_user_data; } static void post_CoCreateInstance(void *wrapctx, void *user_data) @@ -143,31 +240,76 @@ static void post_CoCreateInstance(void *wrapctx, void *user_data) if (!user_data) return; + struct CoCreateInstance_data m_user_data; + m_user_data = *(struct CoCreateInstance_data *)user_data; + + free(user_data); + + HRESULT hr = (HRESULT)drwrap_get_retval(wrapctx); if (SUCCEEDED(hr)) { - // The user_data is the ppv argument from the pre-hook - IFileOpenDialog **ppv = (IFileOpenDialog **)user_data; - if (ppv && *ppv) - { + if (m_user_data.type == TYPE_IFileOpenDialog) { + IFileOpenDialog **ppv = (IFileOpenDialog **)m_user_data.dialog; + if (!ppv || !*ppv) return; + IFileOpenDialog *fod = *ppv; + + WCHAR wfile[MAX_PATH]; + mbstowcs(wfile, OPEN_FILE, MAX_PATH - 1); + wfile[MAX_PATH - 1] = L'\0'; + + fod->lpVtbl->SetFileName(fod, wfile); customFODVtbl = *fod->lpVtbl; customFODVtbl.Show = custom_IFileDialog_Show; - customFODVtbl.GetResult = custom_IFileDialog_GetResult; + customFODVtbl.GetResult = custom_IFileOpenDialog_GetResult; fod->lpVtbl = &customFODVtbl; } + + if (m_user_data.type == TYPE_IFileSaveDialog) { + IFileSaveDialog **ppv = (IFileSaveDialog **)m_user_data.dialog; + if (!ppv || !*ppv) return; + + IFileSaveDialog *fsd = *ppv; + + WCHAR wfile[MAX_PATH]; + mbstowcs(wfile, SAVE_FILE, MAX_PATH - 1); + wfile[MAX_PATH - 1] = L'\0'; + + customFSDVtbl = *fsd->lpVtbl; + customFSDVtbl.Show = custom_IFileDialog_Show; + customFSDVtbl.GetResult = custom_IFileSaveDialog_GetResult; + + fsd->lpVtbl = &customFSDVtbl; + } } } -static HRESULT custom_IFileDialog_Show(void *self, void *b) { +static HRESULT custom_IFileDialog_Show(void *self, void *b) +{ return S_OK; } -static HRESULT custom_IFileDialog_GetResult(void *self, IShellItem **ppsitem) { +static HRESULT custom_IFileSaveDialog_GetResult(void *self, IShellItem **ppsitem) +{ WCHAR file[MAX_PATH]; - mbstowcs(file, CUSTOM_FILE, MAX_PATH); + mbstowcs(file, SAVE_FILE, MAX_PATH); + file[MAX_PATH - 1] = L'\0'; + + HANDLE handle = CreateFileA(SAVE_FILE, GENERIC_ALL, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL); + CloseHandle(handle); + + return fn_SHCreateItemFromParsingName(file, NULL, &IID_IShellItem, (void **)ppsitem); +} + +static HRESULT custom_IFileOpenDialog_GetResult(void *self, IShellItem **ppsitem) +{ + WCHAR file[MAX_PATH]; + mbstowcs(file, OPEN_FILE, MAX_PATH); file[MAX_PATH - 1] = L'\0'; return fn_SHCreateItemFromParsingName(file, NULL, &IID_IShellItem, (void **)ppsitem); @@ -190,11 +332,11 @@ static void event_module_load(void *drcontext, const module_data_t *mod, bool lo } \ } -#define GET_FUNCTION_POINTER(var, name) \ +#define GET_FUNCTION_POINTER(var, name) \ { \ if (var == NULL) \ { \ - var = (void *)dr_get_proc_address(mod->handle, name); \ + var = (void *)dr_get_proc_address(mod->handle, name); \ if (var != NULL) \ dr_printf("Found function pointer %s in %s\n", name, dr_module_preferred_name(mod)); \ } \ @@ -203,18 +345,21 @@ static void event_module_load(void *drcontext, const module_data_t *mod, bool lo // WinAPI Hooks WRAP_FUNCTION("GetOpenFileNameA", pre_GetOpenFileNameA, NULL); WRAP_FUNCTION("GetOpenFileNameW", pre_GetOpenFileNameW, NULL); + WRAP_FUNCTION("GetSaveFileNameA", pre_GetSaveFileNameA, NULL); + WRAP_FUNCTION("GetSaveFileNameW", pre_GetSaveFileNameW, NULL); WRAP_FUNCTION("CoCreateInstance", pre_CoCreateInstance, post_CoCreateInstance); // Qt Hooks WRAP_FUNCTION(MANGLED_QFileDialog_getOpenFileName, pre_QFileDialog_getOpenFileName, NULL); WRAP_FUNCTION(MANGLED_QFileDialog_getOpenFileUrl, pre_QFileDialog_getOpenFileUrl, NULL); + WRAP_FUNCTION(MANGLED_QFileDialog_getSaveFileName, pre_QFileDialog_getSaveFileName, NULL); + WRAP_FUNCTION(MANGLED_QFileDialog_getSaveFileUrl, pre_QFileDialog_getSaveFileUrl, NULL); // Get pointers to functions we need to call ourselves GET_FUNCTION_POINTER(fn_QString_const, MANGLED_QString_const); GET_FUNCTION_POINTER(fn_QUrl_const, MANGLED_QUrl_const); GET_FUNCTION_POINTER(fn_CoTaskMemAlloc, "CoTaskMemAlloc"); GET_FUNCTION_POINTER(fn_SHCreateItemFromParsingName, "SHCreateItemFromParsingName"); - } static void event_exit(void) diff --git a/programs/build.bat b/programs/build.bat index 60b4a41..5e128c0 100644 --- a/programs/build.bat +++ b/programs/build.bat @@ -1,10 +1,11 @@ cl /Fe:compiled\Foo.exe source\Foo.c /Zi /Od /link /MACHINE:X64 cl /Fe:compiled\GetOpenFileNameA.exe comdlg32.lib source\GetOpenFileNameA.c /Zi /Od /link /MACHINE:X64 - cl /Fe:compiled\GetOpenFileNameW.exe comdlg32.lib source\GetOpenFileNameA.c /Zi /Od /link /MACHINE:X64 - -cl /Fe:compiled\IFileDialog.exe uuid.lib ole32.lib source\IFileDialog.c /Zi /Od /link /MACHINE:X64 +cl /Fe:compiled\GetSaveFileNameA.exe comdlg32.lib source\GetSaveFileNameA.c /Zi /Od /link /MACHINE:X64 +cl /Fe:compiled\GetSaveFileNameW.exe comdlg32.lib source\GetSaveFileNameA.c /Zi /Od /link /MACHINE:X64 +cl /Fe:compiled\IFileOpenDialog.exe uuid.lib ole32.lib source\IFileOpenDialog.c /Zi /Od /link /MACHINE:X64 +cl /Fe:compiled\IFileSaveDialog.exe uuid.lib ole32.lib source\IFileSaveDialog.c /Zi /Od /link /MACHINE:X64 qmake source\QT_getOpenFileContent.pro nmake @@ -21,5 +22,11 @@ nmake qmake source\QT_getOpenFileUrls.pro nmake +qmake source\QT_getSaveFileName.pro +nmake + +qmake source\QT_getSaveFileUrl.pro +nmake + qmake source\QT_TestLib.pro nmake -f Makefile.Debug diff --git a/programs/compiled/Foo.exe b/programs/compiled/Foo.exe index 1533ecd..8abadb1 100644 Binary files a/programs/compiled/Foo.exe and b/programs/compiled/Foo.exe differ diff --git a/programs/compiled/GetOpenFileNameA.exe b/programs/compiled/GetOpenFileNameA.exe index 92624bb..3ad0413 100644 Binary files a/programs/compiled/GetOpenFileNameA.exe and b/programs/compiled/GetOpenFileNameA.exe differ diff --git a/programs/compiled/GetOpenFileNameW.exe b/programs/compiled/GetOpenFileNameW.exe index b29a566..b49b85d 100644 Binary files a/programs/compiled/GetOpenFileNameW.exe and b/programs/compiled/GetOpenFileNameW.exe differ diff --git a/programs/compiled/GetSaveFileNameA.exe b/programs/compiled/GetSaveFileNameA.exe new file mode 100644 index 0000000..2d8cbad Binary files /dev/null and b/programs/compiled/GetSaveFileNameA.exe differ diff --git a/programs/compiled/GetSaveFileNameW.exe b/programs/compiled/GetSaveFileNameW.exe new file mode 100644 index 0000000..b8cddcd Binary files /dev/null and b/programs/compiled/GetSaveFileNameW.exe differ diff --git a/programs/compiled/IFileOpenDialog.exe b/programs/compiled/IFileOpenDialog.exe new file mode 100644 index 0000000..1b00dbd Binary files /dev/null and b/programs/compiled/IFileOpenDialog.exe differ diff --git a/programs/compiled/IFileSaveDialog.exe b/programs/compiled/IFileSaveDialog.exe new file mode 100644 index 0000000..0333ec5 Binary files /dev/null and b/programs/compiled/IFileSaveDialog.exe differ diff --git a/programs/compiled/QT_getOpenFileContent.exe b/programs/compiled/QT_getOpenFileContent.exe index 3d37422..c0ebf73 100644 Binary files a/programs/compiled/QT_getOpenFileContent.exe and b/programs/compiled/QT_getOpenFileContent.exe differ diff --git a/programs/compiled/QT_getSaveFileName.exe b/programs/compiled/QT_getSaveFileName.exe new file mode 100644 index 0000000..7e4ef12 Binary files /dev/null and b/programs/compiled/QT_getSaveFileName.exe differ diff --git a/programs/compiled/QT_getSaveFileUrl.exe b/programs/compiled/QT_getSaveFileUrl.exe new file mode 100644 index 0000000..103ad6f Binary files /dev/null and b/programs/compiled/QT_getSaveFileUrl.exe differ diff --git a/programs/source/GetSaveFileNameA.c b/programs/source/GetSaveFileNameA.c new file mode 100644 index 0000000..0542f08 --- /dev/null +++ b/programs/source/GetSaveFileNameA.c @@ -0,0 +1,42 @@ +#include +#include +#include +#include + +int main() { + char file[MAX_PATH] = {0}; + + OPENFILENAME ofn = { + .lStructSize = sizeof(ofn), + .lpstrFilter = "Text Files\0*.txt\0All Files\0*.*\0", + .lpstrFile = file, + .nMaxFile = MAX_PATH, + .lpstrTitle = "Save File As", + .Flags = OFN_PATHMUSTEXIST | OFN_OVERWRITEPROMPT + }; + + int res = GetSaveFileNameA(&ofn); + + if (!res) { + puts("File selection cancelled or failed!"); + exit(1); + } + + FILE *f = fopen(ofn.lpstrFile, "w"); + if (!f) { + perror("Error opening file for writing"); + exit(1); + } + + if (fprintf(f, "foo") < 0) { + perror("Error writing to file"); + fclose(f); + exit(1); + } else { + printf("Successfully wrote \"foo\" to '%s'.\n", ofn.lpstrFile); + } + + fclose(f); + + return 0; +} diff --git a/programs/source/GetSaveFileNameW.c b/programs/source/GetSaveFileNameW.c new file mode 100644 index 0000000..102057e --- /dev/null +++ b/programs/source/GetSaveFileNameW.c @@ -0,0 +1,42 @@ +#include +#include +#include +#include + +int main() { + WCHAR file[MAX_PATH] = {0}; + + OPENFILENAMEW ofn = { + .lStructSize = sizeof(ofn), + .lpstrFilter = L"Text Files\0*.txt\0All Files\0*.*\0", + .lpstrFile = file, + .nMaxFile = MAX_PATH, + .lpstrTitle = L"Select File to Write To", + .Flags = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST + }; + + int res = GetOpenFileNameW(&ofn); + + if (!res) { + _putws(L"File selection cancelled or failed!"); + exit(1); + } + + FILE *f = _wfopen(ofn.lpstrFile, L"w"); + if (!f) { + _wperror(L"Error opening file for writing"); + exit(1); + } + + if (fwprintf(f, L"foo") < 0) { + _wperror(L"Error writing to file"); + fclose(f); + exit(1); + } else { + wprintf(L"Successfully wrote \"foo\" to '%s'.\n", ofn.lpstrFile); + } + + fclose(f); + + return 0; +} diff --git a/programs/source/IFileDialog.c b/programs/source/IFileOpenDialog.c similarity index 96% rename from programs/source/IFileDialog.c rename to programs/source/IFileOpenDialog.c index 079ffb0..5e7b2de 100644 --- a/programs/source/IFileDialog.c +++ b/programs/source/IFileOpenDialog.c @@ -1,5 +1,5 @@ #include -#include // Required for IFileDialog and related interfaces +#include #include #include diff --git a/programs/source/IFileSaveDialog.c b/programs/source/IFileSaveDialog.c new file mode 100644 index 0000000..712a1e5 --- /dev/null +++ b/programs/source/IFileSaveDialog.c @@ -0,0 +1,82 @@ +#include +#include +#include +#include + +int main() { + HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE); + if (FAILED(hr)) { + fprintf(stderr, "CoInitializeEx failed: 0x%lx\n", hr); + exit(1); + } + + char file[MAX_PATH] = {0}; + IFileSaveDialog *pFileSave = NULL; + IShellItem *pItem = NULL; + PWSTR pszFilePath = NULL; + + hr = CoCreateInstance(&CLSID_FileSaveDialog, NULL, CLSCTX_ALL, + &IID_IFileSaveDialog, (void**)&pFileSave); + + if (SUCCEEDED(hr)) { + COMDLG_FILTERSPEC rgSpec[] = {{L"All Files", L"*.*"}}; + pFileSave->lpVtbl->SetFileTypes(pFileSave, ARRAYSIZE(rgSpec), rgSpec); + + pFileSave->lpVtbl->SetTitle(pFileSave, L"Save File As"); + + DWORD dwFlags; + pFileSave->lpVtbl->GetOptions(pFileSave, &dwFlags); + pFileSave->lpVtbl->SetOptions(pFileSave, dwFlags | FOS_OVERWRITEPROMPT | FOS_PATHMUSTEXIST | FOS_NOCHANGEDIR); + + hr = pFileSave->lpVtbl->Show(pFileSave, NULL); + + if (SUCCEEDED(hr)) { + printf("GetResult...\n"); + hr = pFileSave->lpVtbl->GetResult(pFileSave, &pItem); + + if (SUCCEEDED(hr)) { + printf("GetDisplayName...\n"); + hr = pItem->lpVtbl->GetDisplayName(pItem, SIGDN_FILESYSPATH, &pszFilePath); + + if (SUCCEEDED(hr)) { + wcstombs(file, pszFilePath, MAX_PATH); + printf("Selected file: %s\n", file); + } + } + } + } + + if (pszFilePath) { + CoTaskMemFree(pszFilePath); + } + if (pItem) { + pItem->lpVtbl->Release(pItem); + } + if (pFileSave) { + pFileSave->lpVtbl->Release(pFileSave); + } + CoUninitialize(); + + if (FAILED(hr) || file[0] == '\0') { + puts("File selection cancelled or failed."); + exit(1); + } + + FILE *f = fopen(file, "w"); + if (!f) { + perror("Error opening file for writing"); + exit(1); + } + + if (fprintf(f, "foo") < 0) { + perror("Error writing to file"); + fclose(f); + exit(1); + } else { + puts("Successfully wrote \"foo\" to the file."); + } + + fclose(f); + + return 0; +} diff --git a/programs/source/QT_getSaveFileName.cpp b/programs/source/QT_getSaveFileName.cpp new file mode 100644 index 0000000..07773be --- /dev/null +++ b/programs/source/QT_getSaveFileName.cpp @@ -0,0 +1,34 @@ +#include +#include +#include +#include +#include +#include + +int main(int argc, char *argv[]) { + QApplication app(argc, argv); + + QString fileName = QFileDialog::getSaveFileName(nullptr, "Select File", "", "All Files (*.*)"); + + if (fileName.isEmpty()) { + qDebug() << "File selection cancelled."; + return 0; + } + + QFile file(fileName); + + if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { + std::cerr << "Failed to open file for writing: " << file.errorString().toStdString() << std::endl; + return 1; + } + + QTextStream out(&file); + + out << "foo"; + + file.close(); + + qDebug() << "Successfully wrote \"foo\" to:" << fileName; + + return 0; +} diff --git a/programs/source/QT_getSaveFileName.pro b/programs/source/QT_getSaveFileName.pro new file mode 100644 index 0000000..70f9292 --- /dev/null +++ b/programs/source/QT_getSaveFileName.pro @@ -0,0 +1,5 @@ +QT += core widgets +CONFIG += console +SOURCES += QT_getSaveFileName.cpp +TARGET = QT_getSaveFileName +DESTDIR = compiled diff --git a/programs/source/QT_getSaveFileUrl.cpp b/programs/source/QT_getSaveFileUrl.cpp new file mode 100644 index 0000000..93eab1d --- /dev/null +++ b/programs/source/QT_getSaveFileUrl.cpp @@ -0,0 +1,31 @@ +#include +#include +#include +#include +#include +#include +#include + +int main(int argc, char *argv[]) { + QApplication app(argc, argv); + + QUrl url = QFileDialog::getSaveFileUrl(nullptr, "Select File", QUrl(), "All Files (*.*)"); + if (!url.isValid() || !url.isLocalFile()) { + qDebug() << "File selection cancelled."; + return 1; + } + + QFile file(url.toLocalFile()); + if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { + std::cerr << "Failed to open file for writing: " << file.errorString().toStdString() << std::endl; + return 1; + } + + QTextStream out(&file); + out << "foo"; + file.close(); + + qDebug() << "Successfully wrote \"foo\" to:" << url.toLocalFile(); + + return 0; +} diff --git a/programs/source/QT_getSaveFileUrl.pro b/programs/source/QT_getSaveFileUrl.pro new file mode 100644 index 0000000..c01b6ee --- /dev/null +++ b/programs/source/QT_getSaveFileUrl.pro @@ -0,0 +1,5 @@ +QT += core widgets +CONFIG += console +SOURCES += QT_getSaveFileUrl.cpp +TARGET = QT_getSaveFileUrl +DESTDIR = compiled