From a7575ec7b3112eff56bbcde6113ab07d90a24265 Mon Sep 17 00:00:00 2001 From: Nadia Holmquist Pedersen Date: Wed, 7 Feb 2024 20:12:23 +0100 Subject: [PATCH 1/5] Allow the user to choose the UI theme Mainly useful for those who want dark mode on Windows. --- src/frontend/qt_sdl/Config.cpp | 2 ++ src/frontend/qt_sdl/Config.h | 1 + .../qt_sdl/InterfaceSettingsDialog.cpp | 23 ++++++++++++++++++- .../qt_sdl/InterfaceSettingsDialog.ui | 23 ++++++++++++++++--- src/frontend/qt_sdl/main.cpp | 12 ++++++++++ src/frontend/qt_sdl/main.h | 2 ++ 6 files changed, 59 insertions(+), 4 deletions(-) diff --git a/src/frontend/qt_sdl/Config.cpp b/src/frontend/qt_sdl/Config.cpp index 02f43de7..215ad0b7 100644 --- a/src/frontend/qt_sdl/Config.cpp +++ b/src/frontend/qt_sdl/Config.cpp @@ -142,6 +142,7 @@ bool MouseHide; int MouseHideSeconds; bool PauseLostFocus; +std::string UITheme; int64_t RTCOffset; @@ -344,6 +345,7 @@ ConfigEntry ConfigFile[] = {"MouseHide", 1, &MouseHide, false, false}, {"MouseHideSeconds", 0, &MouseHideSeconds, 5, false}, {"PauseLostFocus", 1, &PauseLostFocus, false, false}, + {"UITheme", 2, &UITheme, (std::string)"", false}, {"RTCOffset", 3, &RTCOffset, (int64_t)0, true}, diff --git a/src/frontend/qt_sdl/Config.h b/src/frontend/qt_sdl/Config.h index 11644dc2..18930656 100644 --- a/src/frontend/qt_sdl/Config.h +++ b/src/frontend/qt_sdl/Config.h @@ -185,6 +185,7 @@ extern bool EnableCheats; extern bool MouseHide; extern int MouseHideSeconds; extern bool PauseLostFocus; +extern std::string UITheme; extern int64_t RTCOffset; diff --git a/src/frontend/qt_sdl/InterfaceSettingsDialog.cpp b/src/frontend/qt_sdl/InterfaceSettingsDialog.cpp index 75497bc3..851e7abf 100644 --- a/src/frontend/qt_sdl/InterfaceSettingsDialog.cpp +++ b/src/frontend/qt_sdl/InterfaceSettingsDialog.cpp @@ -16,15 +16,16 @@ with melonDS. If not, see http://www.gnu.org/licenses/. */ +#include #include "InterfaceSettingsDialog.h" #include "ui_InterfaceSettingsDialog.h" #include "types.h" #include "Platform.h" #include "Config.h" +#include "main.h" InterfaceSettingsDialog* InterfaceSettingsDialog::currentDlg = nullptr; - InterfaceSettingsDialog::InterfaceSettingsDialog(QWidget* parent) : QDialog(parent), ui(new Ui::InterfaceSettingsDialog) { ui->setupUi(this); @@ -35,6 +36,18 @@ InterfaceSettingsDialog::InterfaceSettingsDialog(QWidget* parent) : QDialog(pare ui->spinMouseHideSeconds->setValue(Config::MouseHideSeconds); ui->cbPauseLostFocus->setChecked(Config::PauseLostFocus != 0); ui->spinMaxFPS->setValue(Config::MaxFPS); + + const QList themeKeys = QStyleFactory::keys(); + const QString currentTheme = qApp->style()->objectName(); + + ui->cbxUITheme->addItem("System default", ""); + + for (int i = 0; i < themeKeys.length(); i++) + { + ui->cbxUITheme->addItem(themeKeys[i], themeKeys[i]); + if (!Config::UITheme.empty() && themeKeys[i].compare(currentTheme, Qt::CaseInsensitive) == 0) + ui->cbxUITheme->setCurrentIndex(i + 1); + } } InterfaceSettingsDialog::~InterfaceSettingsDialog() @@ -63,8 +76,16 @@ void InterfaceSettingsDialog::done(int r) Config::PauseLostFocus = ui->cbPauseLostFocus->isChecked() ? 1:0; Config::MaxFPS = ui->spinMaxFPS->value(); + QString themeName = ui->cbxUITheme->currentData().toString(); + Config::UITheme = themeName.toStdString(); + Config::Save(); + if (!Config::UITheme.empty()) + qApp->setStyle(themeName); + else + qApp->setStyle(*systemThemeName); + emit updateMouseTimer(); } diff --git a/src/frontend/qt_sdl/InterfaceSettingsDialog.ui b/src/frontend/qt_sdl/InterfaceSettingsDialog.ui index 01ba4a46..21d8434e 100644 --- a/src/frontend/qt_sdl/InterfaceSettingsDialog.ui +++ b/src/frontend/qt_sdl/InterfaceSettingsDialog.ui @@ -7,7 +7,7 @@ 0 0 337 - 233 + 275 @@ -20,12 +20,29 @@ Interface settings - melonDS - + - Main window + User interface + + + + + + Theme + + + cbxUITheme + + + + + + + + diff --git a/src/frontend/qt_sdl/main.cpp b/src/frontend/qt_sdl/main.cpp index 3a5c1a11..7e34e6af 100644 --- a/src/frontend/qt_sdl/main.cpp +++ b/src/frontend/qt_sdl/main.cpp @@ -114,6 +114,7 @@ QStringList NdsRomExtensions { ".nds", ".srl", ".dsi", ".ids" }; QString GbaRomMimeType = "application/x-gba-rom"; QStringList GbaRomExtensions { ".gba", ".agb" }; +QString* systemThemeName; // This list of supported archive formats is based on libarchive(3) version 3.6.2 (2022-12-09). QStringList ArchiveMimeTypes @@ -292,6 +293,11 @@ int main(int argc, char** argv) qputenv("QT_SCALE_FACTOR", "1"); +#if QT_VERSION_MAJOR == 6 && defined(__WIN32__) + // Allow using the system dark theme palette on Windows + qputenv("QT_QPA_PLATFORM", "windows:darkmode=2"); +#endif + printf("melonDS " MELONDS_VERSION "\n"); printf(MELONDS_URL "\n"); @@ -360,6 +366,12 @@ int main(int argc, char** argv) camManager[0]->setXFlip(Config::Camera[0].XFlip); camManager[1]->setXFlip(Config::Camera[1].XFlip); + systemThemeName = new QString(QApplication::style()->objectName()); + + if (!Config::UITheme.empty()) + { + QApplication::setStyle(QString::fromStdString(Config::UITheme)); + } Input::JoystickID = Config::JoystickID; Input::OpenJoystick(); diff --git a/src/frontend/qt_sdl/main.h b/src/frontend/qt_sdl/main.h index 51157c6c..5751f229 100644 --- a/src/frontend/qt_sdl/main.h +++ b/src/frontend/qt_sdl/main.h @@ -44,4 +44,6 @@ public: bool event(QEvent* event) override; }; +extern QString* systemThemeName; + #endif // MAIN_H From 17a1bfa6734bfd3603a3d9361dc46be3748422ee Mon Sep 17 00:00:00 2001 From: Nadia Holmquist Pedersen Date: Wed, 7 Feb 2024 22:27:04 +0100 Subject: [PATCH 2/5] macOS CI updates (#1973) * Use macOS 14 M1-based runners for macOS CI * Hopefully make the universal build erroring not fail the build (does sometimes with delete-artifact) * Update vcpkg version --- .github/workflows/build-macos.yml | 5 +++-- cmake/ConfigureVcpkg.cmake | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml index 5f4b2815..349f99d2 100644 --- a/.github/workflows/build-macos.yml +++ b/.github/workflows/build-macos.yml @@ -15,7 +15,7 @@ jobs: arch: [x86_64, arm64] name: ${{ matrix.arch }} - runs-on: macos-13 + runs-on: macos-14 steps: - name: Check out sources uses: actions/checkout@v3 @@ -27,7 +27,7 @@ jobs: - name: Set up vcpkg uses: lukka/run-vcpkg@v11 with: - vcpkgGitCommitId: c8696863d371ab7f46e213d8f5ca923c4aef2a00 + vcpkgGitCommitId: 53bef8994c541b6561884a8395ea35715ece75db - name: Build uses: lukka/run-cmake@v10 with: @@ -48,6 +48,7 @@ jobs: name: Universal binary needs: [build-macos] runs-on: macos-13 + continue-on-error: true steps: - name: Download x86_64 uses: actions/download-artifact@v4 diff --git a/cmake/ConfigureVcpkg.cmake b/cmake/ConfigureVcpkg.cmake index be8f0590..c9f3e92f 100644 --- a/cmake/ConfigureVcpkg.cmake +++ b/cmake/ConfigureVcpkg.cmake @@ -7,7 +7,7 @@ if (VCPKG_ROOT STREQUAL "${_DEFAULT_VCPKG_ROOT}") file(LOCK "${_DEFAULT_VCPKG_ROOT}" DIRECTORY GUARD FILE) FetchContent_Declare(vcpkg GIT_REPOSITORY "https://github.com/Microsoft/vcpkg.git" - GIT_TAG 2023.12.12 + GIT_TAG 2024.01.12 SOURCE_DIR "${CMAKE_SOURCE_DIR}/vcpkg") FetchContent_MakeAvailable(vcpkg) endif() From 71e1ba8c40468385f2e66142cdbc943c4efb8f55 Mon Sep 17 00:00:00 2001 From: Nadia Holmquist Pedersen Date: Wed, 7 Feb 2024 22:29:13 +0100 Subject: [PATCH 3/5] Linux CI updates (#1965) * Switch to using Qt 6 * Use Ubuntu 22.04 for newer dependency versions * Combine AppImage and regular x86_64 builds so it doesn't have to build twice * Misc cleanup --- .github/workflows/build-appimage.yml | 55 --------------- .github/workflows/build-ubuntu-aarch64.yml | 51 -------------- .github/workflows/build-ubuntu.yml | 79 +++++++++++++++++----- 3 files changed, 63 insertions(+), 122 deletions(-) delete mode 100644 .github/workflows/build-appimage.yml delete mode 100644 .github/workflows/build-ubuntu-aarch64.yml diff --git a/.github/workflows/build-appimage.yml b/.github/workflows/build-appimage.yml deleted file mode 100644 index be4494e8..00000000 --- a/.github/workflows/build-appimage.yml +++ /dev/null @@ -1,55 +0,0 @@ -name: AppImage - -on: - push: - branches: - - master - pull_request: - branches: - - master - -jobs: - build: - - runs-on: ubuntu-20.04 - - steps: - - uses: actions/checkout@v1 - - name: Install dependencies - run: | - sudo rm -f /etc/apt/sources.list.d/dotnetdev.list /etc/apt/sources.list.d/microsoft-prod.list - sudo apt update - sudo apt install cmake extra-cmake-modules libcurl4-gnutls-dev libpcap0.8-dev libsdl2-dev libqt5multimedia5-plugins qt5-default qtbase5-private-dev qtmultimedia5-dev libslirp0 libslirp-dev libarchive-dev zstd libzstd-dev --allow-downgrades - - name: Create build environment - run: mkdir ${{runner.workspace}}/build - - name: Configure - working-directory: ${{runner.workspace}}/build - run: cmake $GITHUB_WORKSPACE - - name: Make - working-directory: ${{runner.workspace}}/build - run: | - make -j$(nproc --all) - - name: Prepare AppDir for AppImage - working-directory: ${{runner.workspace}}/build - run: | - make install DESTDIR=AppDir - mv ./AppDir/usr/local/bin ./AppDir/usr/bin - mv ./AppDir/usr/local/share ./AppDir/usr/share - rm -rf ./AppDir/usr/local - - name: Prepare necessary Tools for building the AppImage - working-directory: ${{runner.workspace}}/build - run: | - wget https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage - wget https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage - chmod a+x linuxdeploy-x86_64.AppImage - chmod a+x linuxdeploy-plugin-qt-x86_64.AppImage - - name: Build the AppImage - working-directory: ${{runner.workspace}}/build - run: | - ./linuxdeploy-x86_64.AppImage --appdir AppDir --plugin qt --output appimage - mkdir dist - cp ./melonDS*.AppImage ./dist - - uses: actions/upload-artifact@v1 - with: - name: melonDS-appimage-x86_64 - path: ${{runner.workspace}}/build/dist diff --git a/.github/workflows/build-ubuntu-aarch64.yml b/.github/workflows/build-ubuntu-aarch64.yml deleted file mode 100644 index 43f4d8b6..00000000 --- a/.github/workflows/build-ubuntu-aarch64.yml +++ /dev/null @@ -1,51 +0,0 @@ -name: Ubuntu - -on: - push: - branches: - - master - pull_request: - branches: - - master - -env: - BUILD_TYPE: Release - -jobs: - build: - name: aarch64 - runs-on: ubuntu-20.04 - container: ubuntu:20.04 - - steps: - - name: Prepare system - shell: bash - run: | - apt update - apt -y full-upgrade - apt -y install git - - name: Check out source - uses: actions/checkout@v1 - - name: Install dependencies - shell: bash - run: | - dpkg --add-architecture arm64 - sh -c "sed \"s|^deb \([a-z\.:/]*\) \([a-z\-]*\) \(.*\)$|deb [arch=amd64] \1 \2 \3\ndeb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports \2 \3|\" /etc/apt/sources.list > /etc/apt/sources.list.new" - rm /etc/apt/sources.list - mv /etc/apt/sources.list{.new,} - apt update - DEBIAN_FRONTEND=noninteractive apt install -y {gcc-10,g++-10,pkg-config}-aarch64-linux-gnu {libsdl2,qtbase5,qtbase5-private,qtmultimedia5,libslirp,libarchive,libzstd}-dev:arm64 zstd:arm64 cmake extra-cmake-modules dpkg-dev - - name: Configure - shell: bash - run: | - CC=aarch64-linux-gnu-gcc-10 CXX=aarch64-linux-gnu-g++-10 cmake -DPKG_CONFIG_EXECUTABLE=/usr/bin/aarch64-linux-gnu-pkg-config $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -B build - - name: Make - shell: bash - run: | - cmake --build build -j$(nproc --all) - mkdir dist - cp build/melonDS dist - - uses: actions/upload-artifact@v1 - with: - name: melonDS-ubuntu-aarch64 - path: dist diff --git a/.github/workflows/build-ubuntu.yml b/.github/workflows/build-ubuntu.yml index 438fddd0..e96f98aa 100644 --- a/.github/workflows/build-ubuntu.yml +++ b/.github/workflows/build-ubuntu.yml @@ -9,30 +9,77 @@ on: - master jobs: - build: + build-x86_64: name: x86_64 - - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v4 + name: Check out sources - name: Install dependencies run: | sudo rm -f /etc/apt/sources.list.d/dotnetdev.list /etc/apt/sources.list.d/microsoft-prod.list sudo apt update - sudo apt install cmake extra-cmake-modules libcurl4-gnutls-dev libpcap0.8-dev libsdl2-dev qt5-default qtbase5-private-dev qtmultimedia5-dev libslirp0 libslirp-dev libarchive-dev zstd libzstd-dev --allow-downgrades - - name: Create build environment - run: mkdir ${{runner.workspace}}/build + sudo apt install --allow-downgrades cmake ninja-build extra-cmake-modules libpcap0.8-dev libsdl2-dev \ + qt6-{base,base-private,multimedia}-dev libslirp0 libslirp-dev libarchive-dev libzstd-dev libfuse2 - name: Configure - working-directory: ${{runner.workspace}}/build - run: cmake $GITHUB_WORKSPACE - - name: Make - working-directory: ${{runner.workspace}}/build + run: cmake -B build -G Ninja -DUSE_QT6=ON -DCMAKE_INSTALL_PREFIX=/usr + - name: Build run: | - make -j$(nproc --all) - mkdir dist - cp melonDS dist - - uses: actions/upload-artifact@v1 + cmake --build build + DESTDIR=AppDir cmake --install build + - uses: actions/upload-artifact@v4 with: name: melonDS-ubuntu-x86_64 - path: ${{runner.workspace}}/build/dist + path: AppDir/usr/bin/melonDS + - name: Fetch AppImage tools + run: | + wget https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage + wget https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage + chmod a+x linuxdeploy-*.AppImage + - name: Build the AppImage + env: + QMAKE: /usr/lib/qt6/bin/qmake + run: | + ./linuxdeploy-x86_64.AppImage --appdir AppDir --plugin qt --output appimage + - uses: actions/upload-artifact@v4 + with: + name: melonDS-appimage-x86_64 + path: melonDS*.AppImage + + build-aarch64: + name: aarch64 + runs-on: ubuntu-latest + container: ubuntu:22.04 + + steps: + - name: Prepare system + shell: bash + run: | + dpkg --add-architecture arm64 + sh -c "sed \"s|^deb \([a-z\.:/]*\) \([a-z\-]*\) \(.*\)$|deb [arch=amd64] \1 \2 \3\ndeb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports \2 \3|\" /etc/apt/sources.list > /etc/apt/sources.list.new" + rm /etc/apt/sources.list + mv /etc/apt/sources.list{.new,} + apt update + apt -y full-upgrade + apt -y install git {gcc-12,g++-12}-aarch64-linux-gnu cmake ninja-build extra-cmake-modules \ + {libsdl2,qt6-{base,base-private,multimedia},libslirp,libarchive,libzstd}-dev:arm64 \ + pkg-config dpkg-dev + - name: Check out source + uses: actions/checkout@v4 + - name: Configure + shell: bash + run: | + cmake -B build -G Ninja \ + -DPKG_CONFIG_EXECUTABLE=/usr/bin/aarch64-linux-gnu-pkg-config \ + -DCMAKE_C_COMPILER=aarch64-linux-gnu-gcc-12 \ + -DCMAKE_CXX_COMPILER=aarch64-linux-gnu-g++-12 \ + -DUSE_QT6=ON + - name: Build + shell: bash + run: | + cmake --build build + - uses: actions/upload-artifact@v4 + with: + name: melonDS-ubuntu-aarch64 + path: build/melonDS From 5ffa6429804a5668edf198a26a1595c170149798 Mon Sep 17 00:00:00 2001 From: Jaklyy <102590697+Jaklyy@users.noreply.github.com> Date: Wed, 7 Feb 2024 17:04:36 -0500 Subject: [PATCH 4/5] Check for write permissions for some key files (#1972) * check if an nds save file can be opened for writing also add the ability to open a file in append mode * fix multi-instance saves also move the check for file writability into a separate function (probably uneeded?) * implement check for gba roms * move rom load error messages into the functions also finish gba slot (oops) * improve error string * check write perms before saving path settings * fix memory leak * check for writability of firmware/nand/sds * add secondary checks for nand/firmware * add check for config file being writable * Return the file write error as a QString to avoid the invalid char* causing a garbled error message. Qt wants it as QString either way. --- src/Platform.h | 12 ++++ src/frontend/qt_sdl/Config.cpp | 23 ++++-- src/frontend/qt_sdl/Config.h | 2 +- src/frontend/qt_sdl/EmuSettingsDialog.cpp | 32 +++++++++ src/frontend/qt_sdl/PathSettingsDialog.cpp | 20 ++++++ src/frontend/qt_sdl/Platform.cpp | 28 +++++++- src/frontend/qt_sdl/ROMManager.cpp | 82 ++++++++++++++++++++-- src/frontend/qt_sdl/ROMManager.h | 5 +- src/frontend/qt_sdl/Window.cpp | 43 +++--------- src/frontend/qt_sdl/main.cpp | 2 +- 10 files changed, 199 insertions(+), 50 deletions(-) diff --git a/src/Platform.h b/src/Platform.h index 21b3d465..425c712c 100644 --- a/src/Platform.h +++ b/src/Platform.h @@ -136,6 +136,11 @@ enum FileMode : unsigned { */ Text = 0b01'00'00, + /** + * Opens a file in append mode. + */ + Append = 0b10'00'00, + /** * Opens a file for reading and writing. * Equivalent to Read | Write. @@ -201,6 +206,13 @@ FileHandle* OpenLocalFile(const std::string& path, FileMode mode); bool FileExists(const std::string& name); bool LocalFileExists(const std::string& name); +// Returns true if we have permission to write to the file. +// Warning: Also creates the file if not present! +bool CheckFileWritable(const std::string& filepath); + +// Same as above (CheckFileWritable()) but for local files. +bool CheckLocalFileWritable(const std::string& filepath); + /** Close a file opened with \c OpenFile. * @returns \c true if the file was closed successfully, false otherwise. * @post \c file is no longer valid and should not be used. diff --git a/src/frontend/qt_sdl/Config.cpp b/src/frontend/qt_sdl/Config.cpp index 215ad0b7..2fdfc3ba 100644 --- a/src/frontend/qt_sdl/Config.cpp +++ b/src/frontend/qt_sdl/Config.cpp @@ -378,7 +378,7 @@ ConfigEntry ConfigFile[] = }; -void LoadFile(int inst) +bool LoadFile(int inst, int actualinst) { Platform::FileHandle* f; if (inst > 0) @@ -386,11 +386,17 @@ void LoadFile(int inst) char name[100] = {0}; snprintf(name, 99, kUniqueConfigFile, inst+1); f = Platform::OpenLocalFile(name, Platform::FileMode::ReadText); + + if (!Platform::CheckLocalFileWritable(name)) return false; } else + { f = Platform::OpenLocalFile(kConfigFile, Platform::FileMode::ReadText); - if (!f) return; + if (actualinst == 0 && !Platform::CheckLocalFileWritable(kConfigFile)) return false; + } + + if (!f) return true; char linebuf[1024]; char entryname[32]; @@ -425,9 +431,10 @@ void LoadFile(int inst) } CloseFile(f); + return true; } -void Load() +bool Load() { for (ConfigEntry* entry = &ConfigFile[0]; entry->Value; entry++) @@ -440,12 +447,14 @@ void Load() case 3: *(int64_t*)entry->Value = std::get(entry->Default); break; } } - - LoadFile(0); - + int inst = Platform::InstanceID(); + + bool ret = LoadFile(0, inst); if (inst > 0) - LoadFile(inst); + ret = LoadFile(inst, inst); + + return ret; } void Save() diff --git a/src/frontend/qt_sdl/Config.h b/src/frontend/qt_sdl/Config.h index 18930656..722384a3 100644 --- a/src/frontend/qt_sdl/Config.h +++ b/src/frontend/qt_sdl/Config.h @@ -204,7 +204,7 @@ extern bool GdbARM7BreakOnStartup; extern bool GdbARM9BreakOnStartup; -void Load(); +bool Load(); void Save(); } diff --git a/src/frontend/qt_sdl/EmuSettingsDialog.cpp b/src/frontend/qt_sdl/EmuSettingsDialog.cpp index 0a834a65..ca9c6716 100644 --- a/src/frontend/qt_sdl/EmuSettingsDialog.cpp +++ b/src/frontend/qt_sdl/EmuSettingsDialog.cpp @@ -380,6 +380,12 @@ void EmuSettingsDialog::on_btnFirmwareBrowse_clicked() if (file.isEmpty()) return; + if (!Platform::CheckFileWritable(file.toStdString())) + { + QMessageBox::critical(this, "melonDS", "Unable to write to firmware file.\nPlease check file/folder write permissions."); + return; + } + updateLastBIOSFolder(file); ui->txtFirmwarePath->setText(file); @@ -436,6 +442,12 @@ void EmuSettingsDialog::on_btnDLDISDBrowse_clicked() if (file.isEmpty()) return; + if (!Platform::CheckFileWritable(file.toStdString())) + { + QMessageBox::critical(this, "melonDS", "Unable to write to DLDI SD image.\nPlease check file/folder write permissions."); + return; + } + updateLastBIOSFolder(file); ui->txtDLDISDPath->setText(file); @@ -468,6 +480,13 @@ void EmuSettingsDialog::on_btnDSiFirmwareBrowse_clicked() if (file.isEmpty()) return; + if (!Platform::CheckFileWritable(file.toStdString())) + { + QMessageBox::critical(this, "melonDS", "Unable to write to DSi firmware file.\nPlease check file/folder write permissions."); + return; + } + + updateLastBIOSFolder(file); ui->txtDSiFirmwarePath->setText(file); @@ -482,6 +501,13 @@ void EmuSettingsDialog::on_btnDSiNANDBrowse_clicked() if (file.isEmpty()) return; + if (!Platform::CheckFileWritable(file.toStdString())) + { + QMessageBox::critical(this, "melonDS", "Unable to write to DSi NAND image.\nPlease check file/folder write permissions."); + return; + } + + updateLastBIOSFolder(file); ui->txtDSiNANDPath->setText(file); @@ -510,6 +536,12 @@ void EmuSettingsDialog::on_btnDSiSDBrowse_clicked() if (file.isEmpty()) return; + if (!Platform::CheckFileWritable(file.toStdString())) + { + QMessageBox::critical(this, "melonDS", "Unable to write to DSi SD image.\nPlease check file/folder write permissions."); + return; + } + updateLastBIOSFolder(file); ui->txtDSiSDPath->setText(file); diff --git a/src/frontend/qt_sdl/PathSettingsDialog.cpp b/src/frontend/qt_sdl/PathSettingsDialog.cpp index 1d698537..71342087 100644 --- a/src/frontend/qt_sdl/PathSettingsDialog.cpp +++ b/src/frontend/qt_sdl/PathSettingsDialog.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include "types.h" #include "Config.h" @@ -37,6 +38,7 @@ extern bool RunningSomething; bool PathSettingsDialog::needsReset = false; +constexpr char errordialog[] = "melonDS cannot write to that directory."; PathSettingsDialog::PathSettingsDialog(QWidget* parent) : QDialog(parent), ui(new Ui::PathSettingsDialog) { @@ -101,6 +103,12 @@ void PathSettingsDialog::on_btnSaveFileBrowse_clicked() QString::fromStdString(EmuDirectory)); if (dir.isEmpty()) return; + + if (!QTemporaryFile(dir).open()) + { + QMessageBox::critical(this, "melonDS", errordialog); + return; + } ui->txtSaveFilePath->setText(dir); } @@ -112,6 +120,12 @@ void PathSettingsDialog::on_btnSavestateBrowse_clicked() QString::fromStdString(EmuDirectory)); if (dir.isEmpty()) return; + + if (!QTemporaryFile(dir).open()) + { + QMessageBox::critical(this, "melonDS", errordialog); + return; + } ui->txtSavestatePath->setText(dir); } @@ -123,6 +137,12 @@ void PathSettingsDialog::on_btnCheatFileBrowse_clicked() QString::fromStdString(EmuDirectory)); if (dir.isEmpty()) return; + + if (!QTemporaryFile(dir).open()) + { + QMessageBox::critical(this, "melonDS", errordialog); + return; + } ui->txtCheatFilePath->setText(dir); } diff --git a/src/frontend/qt_sdl/Platform.cpp b/src/frontend/qt_sdl/Platform.cpp index 222e512a..9bb19d1a 100644 --- a/src/frontend/qt_sdl/Platform.cpp +++ b/src/frontend/qt_sdl/Platform.cpp @@ -217,6 +217,10 @@ std::string InstanceFileSuffix() constexpr char AccessMode(FileMode mode, bool file_exists) { + + if (mode & FileMode::Append) + return 'a'; + if (!(mode & FileMode::Write)) // If we're only opening the file for reading... return 'r'; @@ -255,7 +259,7 @@ static std::string GetModeString(FileMode mode, bool file_exists) FileHandle* OpenFile(const std::string& path, FileMode mode) { - if ((mode & FileMode::ReadWrite) == FileMode::None) + if ((mode & (FileMode::ReadWrite | FileMode::Append)) == FileMode::None) { // If we aren't reading or writing, then we can't open the file Log(LogLevel::Error, "Attempted to open \"%s\" in neither read nor write mode (FileMode 0x%x)\n", path.c_str(), mode); return nullptr; @@ -327,6 +331,28 @@ bool LocalFileExists(const std::string& name) return true; } +bool CheckFileWritable(const std::string& filepath) +{ + FileHandle* file = Platform::OpenFile(filepath.c_str(), FileMode::Append); + if (file) + { + Platform::CloseFile(file); + return true; + } + else return false; +} + +bool CheckLocalFileWritable(const std::string& name) +{ + FileHandle* file = Platform::OpenLocalFile(name.c_str(), FileMode::Append); + if (file) + { + Platform::CloseFile(file); + return true; + } + else return false; +} + bool FileSeek(FileHandle* file, s64 offset, FileSeekOrigin origin) { int stdorigin; diff --git a/src/frontend/qt_sdl/ROMManager.cpp b/src/frontend/qt_sdl/ROMManager.cpp index 15a9ebf5..e3ceebbe 100644 --- a/src/frontend/qt_sdl/ROMManager.cpp +++ b/src/frontend/qt_sdl/ROMManager.cpp @@ -29,6 +29,7 @@ #include #include +#include #include #ifdef ARCHIVE_SUPPORT_ENABLED @@ -210,6 +211,9 @@ QString VerifyDSFirmware() f = Platform::OpenLocalFile(Config::FirmwarePath, FileMode::Read); if (!f) return "DS firmware was not found or could not be accessed. Check your emu settings."; + if (!Platform::CheckFileWritable(Config::FirmwarePath)) + return "DS firmware is unable to be written to.\nPlease check file/folder write permissions."; + len = FileLength(f); if (len == 0x20000) { @@ -237,6 +241,9 @@ QString VerifyDSiFirmware() f = Platform::OpenLocalFile(Config::DSiFirmwarePath, FileMode::Read); if (!f) return "DSi firmware was not found or could not be accessed. Check your emu settings."; + if (!Platform::CheckFileWritable(Config::FirmwarePath)) + return "DSi firmware is unable to be written to.\nPlease check file/folder write permissions."; + len = FileLength(f); if (len != 0x20000) { @@ -259,6 +266,9 @@ QString VerifyDSiNAND() f = Platform::OpenLocalFile(Config::DSiNANDPath, FileMode::ReadWriteExisting); if (!f) return "DSi NAND was not found or could not be accessed. Check your emu settings."; + if (!Platform::CheckFileWritable(Config::FirmwarePath)) + return "DSi NAND is unable to be written to.\nPlease check file/folder write permissions."; + // TODO: some basic checks // check that it has the nocash footer, and all @@ -1276,7 +1286,18 @@ bool LoadROMData(const QStringList& filepath, std::unique_ptr& filedata, u return false; } -bool LoadROM(EmuThread* emuthread, QStringList filepath, bool reset) +QString GetSavErrorString(std::string& filepath, bool gba) +{ + std::string console = gba ? "GBA" : "DS"; + std::string err1 = "Unable to write to "; + std::string err2 = " save.\nPlease check file/folder write permissions.\n\nAttempted to Access:\n"; + + err1 += console + err2 + filepath; + + return QString::fromStdString(err1); +} + +bool LoadROM(QMainWindow* mainWindow, EmuThread* emuthread, QStringList filepath, bool reset) { unique_ptr filedata = nullptr; u32 filelen; @@ -1284,7 +1305,10 @@ bool LoadROM(EmuThread* emuthread, QStringList filepath, bool reset) std::string romname; if (!LoadROMData(filepath, filedata, filelen, basepath, romname)) + { + QMessageBox::critical(mainWindow, "melonDS", "Failed to load the DS ROM."); return false; + } NDSSave = nullptr; @@ -1300,7 +1324,22 @@ bool LoadROM(EmuThread* emuthread, QStringList filepath, bool reset) savname += Platform::InstanceFileSuffix(); FileHandle* sav = Platform::OpenFile(savname, FileMode::Read); - if (!sav) sav = Platform::OpenFile(origsav, FileMode::Read); + if (!sav) + { + if (!Platform::CheckFileWritable(origsav)) + { + QMessageBox::critical(mainWindow, "melonDS", GetSavErrorString(origsav, false)); + return false; + } + + sav = Platform::OpenFile(origsav, FileMode::Read); + } + else if (!Platform::CheckFileWritable(savname)) + { + QMessageBox::critical(mainWindow, "melonDS", GetSavErrorString(savname, false)); + return false; + } + if (sav) { savelen = (u32)Platform::FileLength(sav); @@ -1322,13 +1361,19 @@ bool LoadROM(EmuThread* emuthread, QStringList filepath, bool reset) auto cart = NDSCart::ParseROM(std::move(filedata), filelen, std::move(cartargs)); if (!cart) + { // If we couldn't parse the ROM... + QMessageBox::critical(mainWindow, "melonDS", "Failed to load the DS ROM."); return false; + } if (reset) { if (!emuthread->UpdateConsole(std::move(cart), Keep {})) + { + QMessageBox::critical(mainWindow, "melonDS", "Failed to load the DS ROM."); return false; + } InitFirmwareSaveManager(emuthread); emuthread->NDS->Reset(); @@ -1351,7 +1396,7 @@ bool LoadROM(EmuThread* emuthread, QStringList filepath, bool reset) NDSSave = std::make_unique(savname); LoadCheats(*emuthread->NDS); - return true; + return true; // success } void EjectCart(NDS& nds) @@ -1388,9 +1433,13 @@ QString CartLabel() } -bool LoadGBAROM(NDS& nds, QStringList filepath) +bool LoadGBAROM(QMainWindow* mainWindow, NDS& nds, QStringList filepath) { - if (nds.ConsoleType == 1) return false; // DSi doesn't have a GBA slot + if (nds.ConsoleType == 1) + { + QMessageBox::critical(mainWindow, "melonDS", "The DSi doesn't have a GBA slot."); + return false; + } unique_ptr filedata = nullptr; u32 filelen; @@ -1398,7 +1447,10 @@ bool LoadGBAROM(NDS& nds, QStringList filepath) std::string romname; if (!LoadROMData(filepath, filedata, filelen, basepath, romname)) + { + QMessageBox::critical(mainWindow, "melonDS", "Failed to load the GBA ROM."); return false; + } GBASave = nullptr; @@ -1414,7 +1466,22 @@ bool LoadGBAROM(NDS& nds, QStringList filepath) savname += Platform::InstanceFileSuffix(); FileHandle* sav = Platform::OpenFile(savname, FileMode::Read); - if (!sav) sav = Platform::OpenFile(origsav, FileMode::Read); + if (!sav) + { + if (!Platform::CheckFileWritable(origsav)) + { + QMessageBox::critical(mainWindow, "melonDS", GetSavErrorString(origsav, true)); + return false; + } + + sav = Platform::OpenFile(origsav, FileMode::Read); + } + else if (!Platform::CheckFileWritable(savname)) + { + QMessageBox::critical(mainWindow, "melonDS", GetSavErrorString(savname, true)); + return false; + } + if (sav) { savelen = (u32)FileLength(sav); @@ -1430,7 +1497,10 @@ bool LoadGBAROM(NDS& nds, QStringList filepath) auto cart = GBACart::ParseROM(std::move(filedata), filelen, std::move(savedata), savelen); if (!cart) + { + QMessageBox::critical(mainWindow, "melonDS", "Failed to load the GBA ROM."); return false; + } nds.SetGBACart(std::move(cart)); GBACartType = 0; diff --git a/src/frontend/qt_sdl/ROMManager.h b/src/frontend/qt_sdl/ROMManager.h index 0b640c84..38ed65cd 100644 --- a/src/frontend/qt_sdl/ROMManager.h +++ b/src/frontend/qt_sdl/ROMManager.h @@ -23,6 +23,7 @@ #include "SaveManager.h" #include "AREngine.h" #include "DSi_NAND.h" +#include #include "MemConstants.h" #include @@ -72,12 +73,12 @@ std::optional LoadFirmware(int type) noexcept; std::optional LoadNAND(const std::array& arm7ibios) noexcept; /// Inserts a ROM into the emulated console. -bool LoadROM(EmuThread*, QStringList filepath, bool reset); +bool LoadROM(QMainWindow* mainWindow, EmuThread*, QStringList filepath, bool reset); void EjectCart(NDS& nds); bool CartInserted(); QString CartLabel(); -bool LoadGBAROM(NDS& nds, QStringList filepath); +bool LoadGBAROM(QMainWindow* mainWindow, NDS& nds, QStringList filepath); void LoadGBAAddon(NDS& nds, int type); void EjectGBACart(NDS& nds); bool GBACartInserted(); diff --git a/src/frontend/qt_sdl/Window.cpp b/src/frontend/qt_sdl/Window.cpp index 962fb76c..0f0b71fd 100644 --- a/src/frontend/qt_sdl/Window.cpp +++ b/src/frontend/qt_sdl/Window.cpp @@ -865,10 +865,8 @@ void MainWindow::dropEvent(QDropEvent* event) if (isNdsRom) { - if (!ROMManager::LoadROM(emuThread, file, true)) + if (!ROMManager::LoadROM(mainWindow, emuThread, file, true)) { - // TODO: better error reporting? - QMessageBox::critical(this, "melonDS", "Failed to load the DS ROM."); emuThread->emuUnpause(); return; } @@ -886,10 +884,8 @@ void MainWindow::dropEvent(QDropEvent* event) } else if (isGbaRom) { - if (!ROMManager::LoadGBAROM(*emuThread->NDS, file)) + if (!ROMManager::LoadGBAROM(mainWindow, *emuThread->NDS, file)) { - // TODO: better error reporting? - QMessageBox::critical(this, "melonDS", "Failed to load the GBA ROM."); emuThread->emuUnpause(); return; } @@ -952,12 +948,7 @@ bool MainWindow::preloadROMs(QStringList file, QStringList gbafile, bool boot) bool gbaloaded = false; if (!gbafile.isEmpty()) { - if (!ROMManager::LoadGBAROM(*emuThread->NDS, gbafile)) - { - // TODO: better error reporting? - QMessageBox::critical(this, "melonDS", "Failed to load the GBA ROM."); - return false; - } + if (!ROMManager::LoadGBAROM(mainWindow, *emuThread->NDS, gbafile)) return false; gbaloaded = true; } @@ -965,12 +956,8 @@ bool MainWindow::preloadROMs(QStringList file, QStringList gbafile, bool boot) bool ndsloaded = false; if (!file.isEmpty()) { - if (!ROMManager::LoadROM(emuThread, file, true)) - { - // TODO: better error reporting? - QMessageBox::critical(this, "melonDS", "Failed to load the ROM."); - return false; - } + if (!ROMManager::LoadROM(mainWindow, emuThread, file, true)) return false; + recentFileList.removeAll(file.join("|")); recentFileList.prepend(file.join("|")); updateRecentFilesMenu(); @@ -1173,11 +1160,9 @@ void MainWindow::onOpenFile() emuThread->emuUnpause(); return; } - - if (!ROMManager::LoadROM(emuThread, file, true)) + + if (!ROMManager::LoadROM(mainWindow, emuThread, file, true)) { - // TODO: better error reporting? - QMessageBox::critical(this, "melonDS", "Failed to load the ROM."); emuThread->emuUnpause(); return; } @@ -1272,11 +1257,9 @@ void MainWindow::onClickRecentFile() emuThread->emuUnpause(); return; } - - if (!ROMManager::LoadROM(emuThread, file, true)) + + if (!ROMManager::LoadROM(mainWindow, emuThread, file, true)) { - // TODO: better error reporting? - QMessageBox::critical(this, "melonDS", "Failed to load the ROM."); emuThread->emuUnpause(); return; } @@ -1326,10 +1309,8 @@ void MainWindow::onInsertCart() return; } - if (!ROMManager::LoadROM(emuThread, file, false)) + if (!ROMManager::LoadROM(mainWindow, emuThread, file, false)) { - // TODO: better error reporting? - QMessageBox::critical(this, "melonDS", "Failed to load the ROM."); emuThread->emuUnpause(); return; } @@ -1361,10 +1342,8 @@ void MainWindow::onInsertGBACart() return; } - if (!ROMManager::LoadGBAROM(*emuThread->NDS, file)) + if (!ROMManager::LoadGBAROM(mainWindow, *emuThread->NDS, file)) { - // TODO: better error reporting? - QMessageBox::critical(this, "melonDS", "Failed to load the ROM."); emuThread->emuUnpause(); return; } diff --git a/src/frontend/qt_sdl/main.cpp b/src/frontend/qt_sdl/main.cpp index 7e34e6af..6e87d5bb 100644 --- a/src/frontend/qt_sdl/main.cpp +++ b/src/frontend/qt_sdl/main.cpp @@ -337,7 +337,7 @@ int main(int argc, char** argv) SDL_InitSubSystem(SDL_INIT_VIDEO); SDL_EnableScreenSaver(); SDL_DisableScreenSaver(); - Config::Load(); + if (!Config::Load()) QMessageBox::critical(NULL, "melonDS", "Unable to write to config.\nPlease check the write permissions of the folder you placed melonDS in."); #define SANITIZE(var, min, max) { var = std::clamp(var, min, max); } SANITIZE(Config::ConsoleType, 0, 1); From 646ed3cb321633a430ae9cbc6428177d132378f5 Mon Sep 17 00:00:00 2001 From: Luca D'Amico Date: Wed, 7 Feb 2024 23:15:30 +0100 Subject: [PATCH 5/5] Add Haiku (BeOS-like OS) support (#1858) --- src/CMakeLists.txt | 4 +++- src/sha1/sha1.c | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index afabc03f..3dfd3b0d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -154,11 +154,13 @@ endif() if (WIN32) target_link_libraries(core PRIVATE ole32 comctl32 wsock32 ws2_32) -elseif(NOT APPLE) +elseif(NOT APPLE AND NOT HAIKU) check_library_exists(rt shm_open "" NEED_LIBRT) if (NEED_LIBRT) target_link_libraries(core PRIVATE rt) endif() +elseif(HAIKU) + target_link_libraries(core PRIVATE network) endif() if (ENABLE_JIT_PROFILING) diff --git a/src/sha1/sha1.c b/src/sha1/sha1.c index c0052b70..c34ace30 100644 --- a/src/sha1/sha1.c +++ b/src/sha1/sha1.c @@ -27,6 +27,9 @@ A million repetitions of "a" #if defined(__sun) #include "solarisfixes.h" #endif +#if defined(__HAIKU__) +#include +#endif #include "sha1.h" #ifndef BYTE_ORDER