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-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/.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
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()
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/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 02f43de7..2fdfc3ba 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},
@@ -376,7 +378,7 @@ ConfigEntry ConfigFile[] =
};
-void LoadFile(int inst)
+bool LoadFile(int inst, int actualinst)
{
Platform::FileHandle* f;
if (inst > 0)
@@ -384,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];
@@ -423,9 +431,10 @@ void LoadFile(int inst)
}
CloseFile(f);
+ return true;
}
-void Load()
+bool Load()
{
for (ConfigEntry* entry = &ConfigFile[0]; entry->Value; entry++)
@@ -438,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 11644dc2..722384a3 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;
@@ -203,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/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/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 3a5c1a11..6e87d5bb 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");
@@ -331,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);
@@ -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
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