mirror of
https://github.com/xemu-project/xemu.git
synced 2026-10-20 01:29:22 +02:00
Passing two levels of invalid paths to `SDL_ShowOpenFileDialog` on Windows causes a crash. E.g., given a directory `d:\foo` that exists, passing `D:\foo\bar\baz` will trigger the crash. In this case, the `FileDialogCallbackWrapper` callback is invoked twice, leading to a double-free. Fixes #2785
112 lines
3.9 KiB
C++
112 lines
3.9 KiB
C++
//
|
|
// xemu User Interface
|
|
//
|
|
// Copyright (C) 2026 Matt Borgerson
|
|
//
|
|
// 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
|
|
// the Free Software Foundation; either version 2 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
//
|
|
#include "misc.hh"
|
|
#include <SDL3/SDL_init.h>
|
|
#include <SDL3/SDL_filesystem.h>
|
|
#include <SDL3/SDL_platform_defines.h>
|
|
#include <filesystem>
|
|
#include <string>
|
|
|
|
static void RunOnMainThread(std::function<void()> &&func)
|
|
{
|
|
auto p = new std::function<void()>(std::move(func));
|
|
if (!SDL_RunOnMainThread(
|
|
[](void *userdata) {
|
|
std::unique_ptr<std::function<void()>> f(
|
|
static_cast<std::function<void()> *>(userdata));
|
|
xemu_main_loop_lock();
|
|
(*f)();
|
|
xemu_main_loop_unlock();
|
|
},
|
|
p, false)) {
|
|
delete p;
|
|
}
|
|
}
|
|
|
|
static void FileDialogCallbackWrapper(void *userdata,
|
|
const char *const *filelist, int filter)
|
|
{
|
|
std::unique_ptr<FileDialogCallback> callback(
|
|
static_cast<FileDialogCallback *>(userdata));
|
|
if (filelist && filelist[0]) {
|
|
std::string path = filelist[0];
|
|
RunOnMainThread([callback = std::move(*callback),
|
|
path = std::move(path)]() { callback(path.c_str()); });
|
|
}
|
|
}
|
|
|
|
// Workaround SDL3 default_location handling:
|
|
// - Linux: only supports folder paths, not file paths as documented
|
|
// - Windows/macOS: directories need trailing separator for proper display
|
|
static std::string NormalizeDefaultLocation(const char *default_location)
|
|
{
|
|
if (!default_location || !*default_location) {
|
|
return {};
|
|
}
|
|
|
|
try {
|
|
std::filesystem::path path(default_location);
|
|
#if defined(SDL_PLATFORM_LINUX)
|
|
if (std::filesystem::is_regular_file(path)) {
|
|
return path.parent_path().string();
|
|
}
|
|
#elif defined(SDL_PLATFORM_WINDOWS) || defined(SDL_PLATFORM_MACOS)
|
|
if (std::filesystem::is_directory(path)) {
|
|
return (path / "").string();
|
|
}
|
|
// Prevent a crash in SDL3 file dialog
|
|
if (!std::filesystem::exists(path)) {
|
|
return {};
|
|
}
|
|
#endif
|
|
} catch (...) {
|
|
// Fall through to return original path
|
|
}
|
|
return default_location;
|
|
}
|
|
|
|
void ShowOpenFileDialog(const SDL_DialogFileFilter *filters, int nfilters,
|
|
const char *default_location,
|
|
FileDialogCallback callback)
|
|
{
|
|
auto *cb = new FileDialogCallback(std::move(callback));
|
|
std::string normalized = NormalizeDefaultLocation(default_location);
|
|
SDL_ShowOpenFileDialog(FileDialogCallbackWrapper, cb, xemu_get_window(),
|
|
filters, nfilters,
|
|
normalized.empty() ? nullptr : normalized.c_str(),
|
|
false);
|
|
}
|
|
|
|
void ShowSaveFileDialog(const SDL_DialogFileFilter *filters, int nfilters,
|
|
const char *default_location,
|
|
FileDialogCallback callback)
|
|
{
|
|
auto *cb = new FileDialogCallback(std::move(callback));
|
|
SDL_ShowSaveFileDialog(FileDialogCallbackWrapper, cb, xemu_get_window(),
|
|
filters, nfilters, default_location);
|
|
}
|
|
|
|
void ShowOpenFolderDialog(const char *default_location,
|
|
FileDialogCallback callback)
|
|
{
|
|
auto *cb = new FileDialogCallback(std::move(callback));
|
|
SDL_ShowOpenFolderDialog(FileDialogCallbackWrapper, cb,
|
|
xemu_get_window(), default_location, false);
|
|
}
|