aboutsummaryrefslogtreecommitdiff
path: root/src/frontend/qt_sdl
diff options
context:
space:
mode:
Diffstat (limited to 'src/frontend/qt_sdl')
-rw-r--r--src/frontend/qt_sdl/ArchiveUtil.cpp96
-rw-r--r--src/frontend/qt_sdl/ArchiveUtil.h11
-rw-r--r--src/frontend/qt_sdl/AudioSettingsDialog.cpp20
-rw-r--r--src/frontend/qt_sdl/AudioSettingsDialog.h2
-rw-r--r--src/frontend/qt_sdl/AudioSettingsDialog.ui11
-rw-r--r--src/frontend/qt_sdl/CMakeLists.txt125
-rw-r--r--src/frontend/qt_sdl/CameraManager.cpp612
-rw-r--r--src/frontend/qt_sdl/CameraManager.h134
-rw-r--r--src/frontend/qt_sdl/CameraSettingsDialog.cpp304
-rw-r--r--src/frontend/qt_sdl/CameraSettingsDialog.h108
-rw-r--r--src/frontend/qt_sdl/CameraSettingsDialog.ui170
-rw-r--r--src/frontend/qt_sdl/CheatsDialog.cpp31
-rw-r--r--src/frontend/qt_sdl/CheatsDialog.h2
-rw-r--r--src/frontend/qt_sdl/Config.cpp536
-rw-r--r--src/frontend/qt_sdl/Config.h142
-rw-r--r--src/frontend/qt_sdl/EmuSettingsDialog.cpp119
-rw-r--r--src/frontend/qt_sdl/EmuSettingsDialog.h2
-rw-r--r--src/frontend/qt_sdl/FirmwareSettingsDialog.cpp43
-rw-r--r--src/frontend/qt_sdl/FirmwareSettingsDialog.h6
-rw-r--r--src/frontend/qt_sdl/FirmwareSettingsDialog.ui13
-rw-r--r--src/frontend/qt_sdl/Input.cpp2
-rw-r--r--src/frontend/qt_sdl/Input.h2
-rw-r--r--src/frontend/qt_sdl/InputConfig/InputConfigDialog.cpp9
-rw-r--r--src/frontend/qt_sdl/InputConfig/InputConfigDialog.h2
-rw-r--r--src/frontend/qt_sdl/InputConfig/InputConfigDialog.ui1115
-rw-r--r--src/frontend/qt_sdl/InputConfig/MapButton.h29
-rw-r--r--src/frontend/qt_sdl/InterfaceSettingsDialog.cpp2
-rw-r--r--src/frontend/qt_sdl/InterfaceSettingsDialog.h2
-rw-r--r--src/frontend/qt_sdl/LAN_PCap.cpp16
-rw-r--r--src/frontend/qt_sdl/LAN_PCap.h2
-rw-r--r--src/frontend/qt_sdl/LAN_Socket.cpp2
-rw-r--r--src/frontend/qt_sdl/LAN_Socket.h2
-rw-r--r--src/frontend/qt_sdl/LocalMP.cpp634
-rw-r--r--src/frontend/qt_sdl/LocalMP.h45
-rw-r--r--src/frontend/qt_sdl/MPSettingsDialog.cpp73
-rw-r--r--src/frontend/qt_sdl/MPSettingsDialog.h65
-rw-r--r--src/frontend/qt_sdl/MPSettingsDialog.ui142
-rw-r--r--src/frontend/qt_sdl/OSD.cpp6
-rw-r--r--src/frontend/qt_sdl/OSD.h2
-rw-r--r--src/frontend/qt_sdl/OSD_shaders.h2
-rw-r--r--src/frontend/qt_sdl/PathSettingsDialog.cpp126
-rw-r--r--src/frontend/qt_sdl/PathSettingsDialog.h67
-rw-r--r--src/frontend/qt_sdl/PathSettingsDialog.ui156
-rw-r--r--src/frontend/qt_sdl/Platform.cpp290
-rw-r--r--src/frontend/qt_sdl/PowerManagement/PowerManagementDialog.cpp154
-rw-r--r--src/frontend/qt_sdl/PowerManagement/PowerManagementDialog.h77
-rw-r--r--src/frontend/qt_sdl/PowerManagement/PowerManagementDialog.ui274
-rw-r--r--src/frontend/qt_sdl/PowerManagement/resources/battery.qrc9
-rw-r--r--src/frontend/qt_sdl/PowerManagement/resources/dsi_battery2.svg171
-rw-r--r--src/frontend/qt_sdl/PowerManagement/resources/dsi_battery3.svg171
-rw-r--r--src/frontend/qt_sdl/PowerManagement/resources/dsi_batteryalmostempty.svg171
-rw-r--r--src/frontend/qt_sdl/PowerManagement/resources/dsi_batteryfull.svg171
-rw-r--r--src/frontend/qt_sdl/PowerManagement/resources/dsi_batterylow.svg171
-rw-r--r--src/frontend/qt_sdl/QPathInput.h2
-rw-r--r--src/frontend/qt_sdl/RAMInfoDialog.cpp302
-rw-r--r--src/frontend/qt_sdl/RAMInfoDialog.h161
-rw-r--r--src/frontend/qt_sdl/RAMInfoDialog.ui237
-rw-r--r--src/frontend/qt_sdl/ROMInfoDialog.cpp20
-rw-r--r--src/frontend/qt_sdl/ROMInfoDialog.h6
-rw-r--r--src/frontend/qt_sdl/ROMInfoDialog.ui65
-rw-r--r--src/frontend/qt_sdl/ROMManager.cpp875
-rw-r--r--src/frontend/qt_sdl/ROMManager.h66
-rw-r--r--src/frontend/qt_sdl/SaveManager.cpp194
-rw-r--r--src/frontend/qt_sdl/SaveManager.h70
-rw-r--r--src/frontend/qt_sdl/TitleManagerDialog.cpp25
-rw-r--r--src/frontend/qt_sdl/TitleManagerDialog.h4
-rw-r--r--src/frontend/qt_sdl/VideoSettingsDialog.cpp2
-rw-r--r--src/frontend/qt_sdl/VideoSettingsDialog.h2
-rw-r--r--src/frontend/qt_sdl/WifiSettingsDialog.cpp21
-rw-r--r--src/frontend/qt_sdl/WifiSettingsDialog.h2
-rw-r--r--src/frontend/qt_sdl/WifiSettingsDialog.ui133
-rw-r--r--src/frontend/qt_sdl/font.h2
-rw-r--r--src/frontend/qt_sdl/main.cpp1311
-rw-r--r--src/frontend/qt_sdl/main.h74
-rw-r--r--src/frontend/qt_sdl/main_shaders.h2
-rw-r--r--src/frontend/qt_sdl/sem_timedwait.cpp488
-rw-r--r--src/frontend/qt_sdl/sem_timedwait.h8
77 files changed, 8919 insertions, 1802 deletions
diff --git a/src/frontend/qt_sdl/ArchiveUtil.cpp b/src/frontend/qt_sdl/ArchiveUtil.cpp
index 6919d48..fff1a94 100644
--- a/src/frontend/qt_sdl/ArchiveUtil.cpp
+++ b/src/frontend/qt_sdl/ArchiveUtil.cpp
@@ -1,5 +1,5 @@
/*
- Copyright 2016-2021 Arisotura, WaluigiWare64
+ Copyright 2016-2022 melonDS team
This file is part of melonDS.
@@ -17,43 +17,66 @@
*/
#include "ArchiveUtil.h"
+#include "Platform.h"
namespace Archive
{
-QVector<QString> ListArchive(const char* path)
+#ifdef __WIN32__
+#define melon_archive_open(a, f, b) archive_read_open_filename_w(a, (const wchar_t*)f.utf16(), b)
+#else
+#define melon_archive_open(a, f, b) archive_read_open_filename(a, f.toUtf8().constData(), b)
+#endif // __WIN32__
+
+bool compareCI(const QString& s1, const QString& s2)
+{
+ return s1.toLower() < s2.toLower();
+}
+
+QVector<QString> ListArchive(QString path)
{
struct archive *a;
struct archive_entry *entry;
int r;
- QVector<QString> fileList = {"OK"};
-
+ QVector<QString> fileList;
+
a = archive_read_new();
+
archive_read_support_filter_all(a);
archive_read_support_format_all(a);
- r = archive_read_open_filename(a, path, 10240);
+
+ //r = archive_read_open_filename(a, path, 10240);
+ r = melon_archive_open(a, path, 10240);
if (r != ARCHIVE_OK)
{
return QVector<QString> {"Err"};
}
-
- while (archive_read_next_header(a, &entry) == ARCHIVE_OK)
+
+ while (archive_read_next_header(a, &entry) == ARCHIVE_OK)
{
- fileList.push_back(archive_entry_pathname(entry));
- archive_read_data_skip(a);
+ if (archive_entry_filetype(entry) != AE_IFREG)
+ continue;
+
+ fileList.push_back(archive_entry_pathname_utf8(entry));
+ archive_read_data_skip(a);
}
+
archive_read_close(a);
- archive_read_free(a);
+ archive_read_free(a);
+
if (r != ARCHIVE_OK)
{
return QVector<QString> {"Err"};
}
-
+
+ std::stable_sort(fileList.begin(), fileList.end(), compareCI);
+ fileList.prepend("OK");
+
return fileList;
}
-QVector<QString> ExtractFileFromArchive(const char* path, const char* wantedFile, QByteArray *romBuffer)
+QVector<QString> ExtractFileFromArchive(QString path, QString wantedFile, QByteArray *romBuffer)
{
struct archive *a = archive_read_new();
struct archive_entry *entry;
@@ -61,8 +84,9 @@ QVector<QString> ExtractFileFromArchive(const char* path, const char* wantedFile
archive_read_support_format_all(a);
archive_read_support_filter_all(a);
-
- r = archive_read_open_filename(a, path, 10240);
+
+ //r = archive_read_open_filename(a, path, 10240);
+ r = melon_archive_open(a, path, 10240);
if (r != ARCHIVE_OK)
{
return QVector<QString> {"Err"};
@@ -70,7 +94,7 @@ QVector<QString> ExtractFileFromArchive(const char* path, const char* wantedFile
while (archive_read_next_header(a, &entry) == ARCHIVE_OK)
{
- if (strcmp(wantedFile, archive_entry_pathname(entry)) == 0)
+ if (strcmp(wantedFile.toUtf8().constData(), archive_entry_pathname_utf8(entry)) == 0)
{
break;
}
@@ -92,7 +116,45 @@ QVector<QString> ExtractFileFromArchive(const char* path, const char* wantedFile
}
-u32 ExtractFileFromArchive(const char* path, const char* wantedFile, u8 **romdata)
+u32 ExtractFileFromArchive(QString path, QString wantedFile, u8** filedata, u32* filesize)
+{
+ struct archive *a = archive_read_new();
+ struct archive_entry *entry;
+ int r;
+
+ if (!filedata) return -1;
+
+ archive_read_support_format_all(a);
+ archive_read_support_filter_all(a);
+
+ //r = archive_read_open_filename(a, path, 10240);
+ r = melon_archive_open(a, path, 10240);
+ if (r != ARCHIVE_OK)
+ {
+ return -1;
+ }
+
+ while (archive_read_next_header(a, &entry) == ARCHIVE_OK)
+ {
+ if (strcmp(wantedFile.toUtf8().constData(), archive_entry_pathname_utf8(entry)) == 0)
+ {
+ break;
+ }
+ }
+
+ size_t bytesToRead = archive_entry_size(entry);
+ if (filesize) *filesize = bytesToRead;
+ *filedata = new u8[bytesToRead];
+ ssize_t bytesRead = archive_read_data(a, *filedata, bytesToRead);
+
+ archive_read_close(a);
+ archive_read_free(a);
+
+ return (u32)bytesRead;
+
+}
+
+/*u32 ExtractFileFromArchive(const char* path, const char* wantedFile, u8 **romdata)
{
QByteArray romBuffer;
QVector<QString> extractResult = ExtractFileFromArchive(path, wantedFile, &romBuffer);
@@ -107,6 +169,6 @@ u32 ExtractFileFromArchive(const char* path, const char* wantedFile, u8 **romdat
memcpy(*romdata, romBuffer.data(), len);
return len;
-}
+}*/
}
diff --git a/src/frontend/qt_sdl/ArchiveUtil.h b/src/frontend/qt_sdl/ArchiveUtil.h
index a8a4a14..761f542 100644
--- a/src/frontend/qt_sdl/ArchiveUtil.h
+++ b/src/frontend/qt_sdl/ArchiveUtil.h
@@ -1,5 +1,5 @@
/*
- Copyright 2016-2021 Arisotura, WaluigiWare64
+ Copyright 2016-2022 melonDS team
This file is part of melonDS.
@@ -34,10 +34,11 @@
namespace Archive
{
-
-QVector<QString> ListArchive(const char* path);
-QVector<QString> ExtractFileFromArchive(const char* path, const char* wantedFile, QByteArray *romBuffer);
-u32 ExtractFileFromArchive(const char* path, const char* wantedFile, u8 **romdata);
+
+QVector<QString> ListArchive(QString path);
+u32 ExtractFileFromArchive(QString path, QString wantedFile, u8** filedata, u32* filesize);
+//QVector<QString> ExtractFileFromArchive(QString path, QString wantedFile, QByteArray *romBuffer);
+//u32 ExtractFileFromArchive(const char* path, const char* wantedFile, u8 **romdata);
}
diff --git a/src/frontend/qt_sdl/AudioSettingsDialog.cpp b/src/frontend/qt_sdl/AudioSettingsDialog.cpp
index d4ce678..4beefaf 100644
--- a/src/frontend/qt_sdl/AudioSettingsDialog.cpp
+++ b/src/frontend/qt_sdl/AudioSettingsDialog.cpp
@@ -1,5 +1,5 @@
/*
- Copyright 2016-2021 Arisotura
+ Copyright 2016-2022 melonDS team
This file is part of melonDS.
@@ -62,11 +62,25 @@ AudioSettingsDialog::AudioSettingsDialog(QWidget* parent) : QDialog(parent), ui(
connect(grpMicMode, SIGNAL(buttonClicked(int)), this, SLOT(onChangeMicMode(int)));
grpMicMode->button(Config::MicInputType)->setChecked(true);
- ui->txtMicWavPath->setText(Config::MicWavPath);
+ ui->txtMicWavPath->setText(QString::fromStdString(Config::MicWavPath));
bool iswav = (Config::MicInputType == 3);
ui->txtMicWavPath->setEnabled(iswav);
ui->btnMicWavBrowse->setEnabled(iswav);
+
+ int inst = Platform::InstanceID();
+ if (inst > 0)
+ {
+ ui->lblInstanceNum->setText(QString("Configuring settings for instance %1").arg(inst+1));
+ ui->cbInterpolation->setEnabled(false);
+ ui->cbBitrate->setEnabled(false);
+ for (QAbstractButton* btn : grpMicMode->buttons())
+ btn->setEnabled(false);
+ ui->txtMicWavPath->setEnabled(false);
+ ui->btnMicWavBrowse->setEnabled(false);
+ }
+ else
+ ui->lblInstanceNum->hide();
}
AudioSettingsDialog::~AudioSettingsDialog()
@@ -77,7 +91,7 @@ AudioSettingsDialog::~AudioSettingsDialog()
void AudioSettingsDialog::on_AudioSettingsDialog_accepted()
{
Config::MicInputType = grpMicMode->checkedId();
- strncpy(Config::MicWavPath, ui->txtMicWavPath->text().toStdString().c_str(), 1023); Config::MicWavPath[1023] = '\0';
+ Config::MicWavPath = ui->txtMicWavPath->text().toStdString();
Config::Save();
closeDlg();
diff --git a/src/frontend/qt_sdl/AudioSettingsDialog.h b/src/frontend/qt_sdl/AudioSettingsDialog.h
index 0bb32c6..498c152 100644
--- a/src/frontend/qt_sdl/AudioSettingsDialog.h
+++ b/src/frontend/qt_sdl/AudioSettingsDialog.h
@@ -1,5 +1,5 @@
/*
- Copyright 2016-2021 Arisotura
+ Copyright 2016-2022 melonDS team
This file is part of melonDS.
diff --git a/src/frontend/qt_sdl/AudioSettingsDialog.ui b/src/frontend/qt_sdl/AudioSettingsDialog.ui
index d7cfadd..8fc38d9 100644
--- a/src/frontend/qt_sdl/AudioSettingsDialog.ui
+++ b/src/frontend/qt_sdl/AudioSettingsDialog.ui
@@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>482</width>
- <height>256</height>
+ <height>301</height>
</rect>
</property>
<property name="sizePolicy">
@@ -24,6 +24,13 @@
<enum>QLayout::SetFixedSize</enum>
</property>
<item>
+ <widget class="QLabel" name="lblInstanceNum">
+ <property name="text">
+ <string>Configuring settings for instance X</string>
+ </property>
+ </widget>
+ </item>
+ <item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Audio output</string>
@@ -76,7 +83,7 @@
<item row="1" column="1">
<widget class="QComboBox" name="cbBitrate">
<property name="whatsThis">
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The bitrate of audio playback. If set to &quot;Automatic&quot; this will be 10-bit for DS mode and 16-bit for DSi mode.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The bitrate of audio playback. If set to &quot;Automatic&quot; this will be 10-bit for DS mode and 16-bit for DSi mode.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
diff --git a/src/frontend/qt_sdl/CMakeLists.txt b/src/frontend/qt_sdl/CMakeLists.txt
index ad38e37..a8d6e4b 100644
--- a/src/frontend/qt_sdl/CMakeLists.txt
+++ b/src/frontend/qt_sdl/CMakeLists.txt
@@ -1,139 +1,123 @@
-project(qt_sdl)
+include(CMakeDependentOption)
-SET(SOURCES_QT_SDL
+include(FixInterfaceIncludes)
+
+set(SOURCES_QT_SDL
main.cpp
main_shaders.h
CheatsDialog.cpp
- Config.cpp
+ Config.cpp
EmuSettingsDialog.cpp
+ PowerManagement/PowerManagementDialog.cpp
+ PowerManagement/resources/battery.qrc
InputConfig/InputConfigDialog.cpp
InputConfig/MapButton.h
InputConfig/resources/ds.qrc
VideoSettingsDialog.cpp
+ CameraSettingsDialog.cpp
AudioSettingsDialog.cpp
FirmwareSettingsDialog.cpp
+ PathSettingsDialog.cpp
+ MPSettingsDialog.cpp
WifiSettingsDialog.cpp
InterfaceSettingsDialog.cpp
ROMInfoDialog.cpp
+ RAMInfoDialog.cpp
TitleManagerDialog.cpp
Input.cpp
LAN_PCap.cpp
LAN_Socket.cpp
+ LocalMP.cpp
OSD.cpp
OSD_shaders.h
font.h
Platform.cpp
QPathInput.h
+ ROMManager.cpp
+ SaveManager.cpp
+ CameraManager.cpp
ArchiveUtil.h
ArchiveUtil.cpp
- ../Util_ROM.cpp
../Util_Video.cpp
../Util_Audio.cpp
../FrontendUtil.h
../mic_blow.h
- ../SharedConfig.h
${CMAKE_SOURCE_DIR}/res/melon.qrc
-)
+ )
+
+if (APPLE)
+ option(USE_QT6 "Build using Qt 6 instead of 5" ON)
+else()
+ option(USE_QT6 "Build using Qt 6 instead of 5" OFF)
+endif()
-option(USE_QT6 "Build using Qt 6 instead of 5" OFF)
if (WIN32)
set(CMAKE_RC_COMPILE_OBJECT "<CMAKE_RC_COMPILER> -i <SOURCE> -o <OBJECT>")
endif()
if (USE_QT6)
- if (BUILD_STATIC AND QT6_STATIC_DIR)
- set(QT6_STATIC_BASE ${QT6_STATIC_DIR}/lib/cmake/Qt6)
- set(Qt6_DIR ${QT6_STATIC_BASE})
- set(Qt6Core_DIR ${QT6_STATIC_BASE}Core)
- set(Qt6Gui_DIR ${QT6_STATIC_BASE}Gui)
- set(Qt6Widgets_DIR ${QT6_STATIC_BASE}Widgets)
- set(Qt6Network_DIR ${QT6_STATIC_BASE}Network)
- set(Qt6OpenGL_DIR ${QT6_STATIC_BASE}OpenGL)
- set(Qt6OpenGLWidgets_DIR ${QT6_STATIC_BASE}OpenGLWidgets)
- endif()
- find_package(Qt6 COMPONENTS Core Gui Widgets Network OpenGL OpenGLWidgets REQUIRED)
- set(QT_LINK_LIBS Qt6::Core Qt6::Gui Qt6::Widgets Qt6::Network Qt6::OpenGL Qt6::OpenGLWidgets)
+ find_package(Qt6 COMPONENTS Core Gui Widgets Network Multimedia OpenGL OpenGLWidgets REQUIRED)
+ set(QT_LINK_LIBS Qt6::Core Qt6::Gui Qt6::Widgets Qt6::Network Qt6::Multimedia Qt6::OpenGL Qt6::OpenGLWidgets)
else()
- if (BUILD_STATIC AND QT5_STATIC_DIR)
- set(QT5_STATIC_BASE ${QT5_STATIC_DIR}/lib/cmake/Qt5)
- set(Qt5_DIR ${QT5_STATIC_BASE})
- set(Qt5Core_DIR ${QT5_STATIC_BASE}Core)
- set(Qt5Gui_DIR ${QT5_STATIC_BASE}Gui)
- set(Qt5Widgets_DIR ${QT5_STATIC_BASE}Widgets)
- set(Qt5Network_DIR ${QT5_STATIC_BASE}Network)
- endif()
- find_package(Qt5 COMPONENTS Core Gui Widgets Network REQUIRED)
- set(QT_LINK_LIBS Qt5::Core Qt5::Gui Qt5::Widgets Qt5::Network)
+ find_package(Qt5 COMPONENTS Core Gui Widgets Network Multimedia REQUIRED)
+ set(QT_LINK_LIBS Qt5::Core Qt5::Gui Qt5::Widgets Qt5::Network Qt5::Multimedia)
endif()
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTORCC ON)
+if (BUILD_STATIC)
+ list(APPEND PKG_CONFIG_EXECUTABLE "--static")
+endif()
+
find_package(Threads REQUIRED)
find_package(PkgConfig REQUIRED)
-find_package(Iconv REQUIRED)
-pkg_check_modules(SDL2 REQUIRED sdl2)
-pkg_check_modules(SLIRP REQUIRED slirp)
-pkg_check_modules(LIBARCHIVE REQUIRED libarchive)
-add_compile_definitions(ARCHIVE_SUPPORT_ENABLED)
+pkg_check_modules(SDL2 REQUIRED IMPORTED_TARGET sdl2)
+pkg_check_modules(Slirp REQUIRED IMPORTED_TARGET slirp)
+pkg_check_modules(LibArchive REQUIRED IMPORTED_TARGET libarchive)
-if (WIN32 AND (CMAKE_BUILD_TYPE STREQUAL Release))
- add_executable(melonDS WIN32 ${SOURCES_QT_SDL})
-else()
- add_executable(melonDS ${SOURCES_QT_SDL})
-endif()
+fix_interface_includes(PkgConfig::SDL2 PkgConfig::Slirp PkgConfig::LibArchive)
-target_link_libraries(melonDS ${CMAKE_THREAD_LIBS_INIT})
+add_compile_definitions(ARCHIVE_SUPPORT_ENABLED)
-target_include_directories(melonDS PRIVATE ${SDL2_INCLUDE_DIRS} ${SDL2_PREFIX}/include ${SLIRP_INCLUDE_DIRS} ${LIBARCHIVE_INCLUDE_DIRS})
-target_link_directories(melonDS PRIVATE ${SDL2_LIBRARY_DIRS} ${SLIRP_LIBRARY_DIRS})
-target_link_directories(melonDS PRIVATE ${LIBARCHIVE_LIBRARY_DIRS})
-
-target_include_directories(melonDS PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}")
-target_include_directories(melonDS PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/..")
-target_include_directories(melonDS PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/../..")
-target_link_libraries(melonDS core)
+add_executable(melonDS ${SOURCES_QT_SDL})
if (BUILD_STATIC)
- target_link_libraries(melonDS -static ${SDL2_STATIC_LIBRARIES} ${SLIRP_STATIC_LIBRARIES} ${LIBARCHIVE_STATIC_LIBRARIES})
qt_import_plugins(melonDS INCLUDE Qt::QSvgPlugin)
-else()
- target_link_libraries(melonDS ${SDL2_LIBRARIES} ${SLIRP_LIBRARIES} ${LIBARCHIVE_LIBRARIES})
+ target_link_options(melonDS PRIVATE -static)
endif()
-if (NOT Iconv_IS_BUILT_IN)
- target_link_libraries(melonDS ${Iconv_LIBRARIES})
-endif()
+target_include_directories(melonDS PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}")
+target_include_directories(melonDS PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/..")
+target_include_directories(melonDS PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/../..")
+target_link_libraries(melonDS PRIVATE core)
+target_link_libraries(melonDS PRIVATE PkgConfig::SDL2 PkgConfig::Slirp PkgConfig::LibArchive)
+target_link_libraries(melonDS PRIVATE ${QT_LINK_LIBS} ${CMAKE_DL_LIBS})
if (UNIX)
option(PORTABLE "Make a portable build that looks for its configuration in the current directory" OFF)
- target_link_libraries(melonDS ${QT_LINK_LIBS})
- if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
- target_link_libraries(melonDS dl)
- endif()
elseif (WIN32)
option(PORTABLE "Make a portable build that looks for its configuration in the current directory" ON)
+
configure_file("${CMAKE_SOURCE_DIR}/res/melon.rc.in" "${CMAKE_SOURCE_DIR}/melon.rc")
target_sources(melonDS PUBLIC "${CMAKE_SOURCE_DIR}/melon.rc")
- target_link_libraries(melonDS comctl32 d2d1 dwrite uxtheme ws2_32 iphlpapi gdi32)
- if (BUILD_STATIC)
- target_link_libraries(melonDS imm32 winmm version setupapi -static z zstd ${QT_LINK_LIBS})
- else()
- target_link_libraries(melonDS ${QT_LINK_LIBS})
- endif()
+ target_link_libraries(melonDS PRIVATE ws2_32 iphlpapi)
+ set_target_properties(melonDS PROPERTIES LINK_FLAGS_DEBUG "-mconsole")
endif()
if (PORTABLE)
- add_definitions(-DPORTABLE)
+ target_compile_definitions(melonDS PRIVATE PORTABLE)
endif()
if (APPLE)
+ target_sources(melonDS PRIVATE sem_timedwait.cpp)
+
# Copy icon into the bundle
set(RESOURCE_FILES "${CMAKE_SOURCE_DIR}/res/melon.icns")
target_sources(melonDS PUBLIC "${RESOURCE_FILES}")
@@ -143,14 +127,7 @@ if (APPLE)
MACOSX_BUNDLE_INFO_PLIST ${CMAKE_SOURCE_DIR}/res/melon.plist.in
OUTPUT_NAME melonDS
RESOURCE "${RESOURCE_FILES}")
-
- # Qt 6 requires macOS 10.15 if building on 10.15 or greater
- if(CMAKE_SYSTEM_VERSION VERSION_GREATER_EQUAL 19.0.0)
- if (USE_QT6)
- set(CMAKE_OSX_DEPLOYMENT_TARGET "10.15" CACHE STRING "Minimum OS X deployment version" FORCE)
- endif()
- endif()
option(MACOS_BUNDLE_LIBS "Bundle libraries with the app on macOS" OFF)
option(MACOS_BUILD_DMG "Build DMG image of the macOS application bundle" OFF)
@@ -168,8 +145,8 @@ endif()
if (UNIX AND NOT APPLE)
foreach(SIZE 16 32 48 64 128 256)
install(FILES ${CMAKE_SOURCE_DIR}/res/icon/melon_${SIZE}x${SIZE}.png
- DESTINATION ${CMAKE_INSTALL_PREFIX}/share/icons/hicolor/${SIZE}x${SIZE}/apps
- RENAME net.kuribo64.melonDS.png)
+ DESTINATION ${CMAKE_INSTALL_PREFIX}/share/icons/hicolor/${SIZE}x${SIZE}/apps
+ RENAME net.kuribo64.melonDS.png)
endforeach()
install(FILES ${CMAKE_SOURCE_DIR}/res/net.kuribo64.melonDS.desktop DESTINATION ${CMAKE_INSTALL_PREFIX}/share/applications)
diff --git a/src/frontend/qt_sdl/CameraManager.cpp b/src/frontend/qt_sdl/CameraManager.cpp
new file mode 100644
index 0000000..19cf8d4
--- /dev/null
+++ b/src/frontend/qt_sdl/CameraManager.cpp
@@ -0,0 +1,612 @@
+/*
+ Copyright 2016-2022 melonDS team
+
+ This file is part of melonDS.
+
+ melonDS 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 3 of the License, or (at your option)
+ any later version.
+
+ melonDS 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 melonDS. If not, see http://www.gnu.org/licenses/.
+*/
+
+#include "CameraManager.h"
+#include "Config.h"
+
+
+#if QT_VERSION >= 0x060000
+
+CameraFrameDumper::CameraFrameDumper(QObject* parent) : QVideoSink(parent)
+{
+ cam = (CameraManager*)parent;
+
+ connect(this, &CameraFrameDumper::videoFrameChanged, this, &CameraFrameDumper::present);
+}
+
+void CameraFrameDumper::present(const QVideoFrame& _frame)
+{
+ QVideoFrame frame(_frame);
+ if (!frame.map(QVideoFrame::ReadOnly))
+ return;
+ if (!frame.isReadable())
+ {
+ frame.unmap();
+ return;
+ }
+
+ switch (frame.pixelFormat())
+ {
+ case QVideoFrameFormat::Format_XRGB8888:
+ case QVideoFrameFormat::Format_YUYV:
+ cam->feedFrame((u32*)frame.bits(0), frame.width(), frame.height(), frame.pixelFormat() == QVideoFrameFormat::Format_YUYV);
+ break;
+
+ case QVideoFrameFormat::Format_UYVY:
+ cam->feedFrame_UYVY((u32*)frame.bits(0), frame.width(), frame.height());
+ break;
+
+ case QVideoFrameFormat::Format_NV12:
+ cam->feedFrame_NV12((u8*)frame.bits(0), (u8*)frame.bits(1), frame.width(), frame.height());
+ break;
+ }
+
+ frame.unmap();
+}
+
+#else
+
+CameraFrameDumper::CameraFrameDumper(QObject* parent) : QAbstractVideoSurface(parent)
+{
+ cam = (CameraManager*)parent;
+}
+
+bool CameraFrameDumper::present(const QVideoFrame& _frame)
+{
+ QVideoFrame frame(_frame);
+ if (!frame.map(QAbstractVideoBuffer::ReadOnly))
+ return false;
+ if (!frame.isReadable())
+ {
+ frame.unmap();
+ return false;
+ }
+
+ switch (frame.pixelFormat())
+ {
+ case QVideoFrame::Format_RGB32:
+ case QVideoFrame::Format_YUYV:
+ cam->feedFrame((u32*)frame.bits(0), frame.width(), frame.height(), frame.pixelFormat() == QVideoFrame::Format_YUYV);
+ break;
+
+ case QVideoFrame::Format_UYVY:
+ cam->feedFrame_UYVY((u32*)frame.bits(0), frame.width(), frame.height());
+ break;
+
+ case QVideoFrame::Format_NV12:
+ cam->feedFrame_NV12((u8*)frame.bits(0), (u8*)frame.bits(1), frame.width(), frame.height());
+ break;
+ }
+
+ frame.unmap();
+
+ return true;
+}
+
+QList<QVideoFrame::PixelFormat> CameraFrameDumper::supportedPixelFormats(QAbstractVideoBuffer::HandleType type) const
+{
+ QList<QVideoFrame::PixelFormat> ret;
+
+ ret.append(QVideoFrame::Format_RGB32);
+ ret.append(QVideoFrame::Format_YUYV);
+ ret.append(QVideoFrame::Format_UYVY);
+ ret.append(QVideoFrame::Format_NV12);
+
+ return ret;
+}
+
+#endif
+
+
+CameraManager::CameraManager(int num, int width, int height, bool yuv) : QObject()
+{
+ this->num = num;
+
+ startNum = 0;
+
+ // QCamera needs to be controlled from the UI thread, hence this
+ connect(this, SIGNAL(camStartSignal()), this, SLOT(camStart()));
+ connect(this, SIGNAL(camStopSignal()), this, SLOT(camStop()));
+
+ frameWidth = width;
+ frameHeight = height;
+ frameFormatYUV = yuv;
+
+ int fbsize = frameWidth * frameHeight;
+ if (yuv) fbsize /= 2;
+ frameBuffer = new u32[fbsize];
+ tempFrameBuffer = new u32[fbsize];
+
+ inputType = -1;
+ xFlip = false;
+ init();
+}
+
+CameraManager::~CameraManager()
+{
+ deInit();
+
+ // save settings here?
+
+ delete[] frameBuffer;
+}
+
+void CameraManager::init()
+{
+ if (inputType != -1)
+ deInit();
+
+ startNum = 0;
+
+ inputType = Config::Camera[num].InputType;
+ imagePath = QString::fromStdString(Config::Camera[num].ImagePath);
+ camDeviceName = QString::fromStdString(Config::Camera[num].CamDeviceName);
+
+ camDevice = nullptr;
+
+ {
+ // fill the framebuffer with black
+
+ int total = frameWidth * frameHeight;
+ u32 fill = 0;
+ if (frameFormatYUV)
+ {
+ total /= 2;
+ fill = 0x80008000;
+ }
+
+ for (int i = 0; i < total; i++)
+ frameBuffer[i] = fill;
+ }
+
+ if (inputType == 1)
+ {
+ // still image
+
+ QImage img(imagePath);
+ if (!img.isNull())
+ {
+ QImage imgconv = img.convertToFormat(QImage::Format_RGB32);
+ if (frameFormatYUV)
+ {
+ copyFrame_RGBtoYUV((u32*)img.bits(), img.width(), img.height(),
+ frameBuffer, frameWidth, frameHeight,
+ false);
+ }
+ else
+ {
+ copyFrame_Straight((u32*)img.bits(), img.width(), img.height(),
+ frameBuffer, frameWidth, frameHeight,
+ false, false);
+ }
+ }
+ }
+ else if (inputType == 2)
+ {
+ // physical camera
+
+#if QT_VERSION >= 0x060000
+ const QList<QCameraDevice> cameras = QMediaDevices::videoInputs();
+ for (const QCameraDevice& cam : cameras)
+ {
+ if (QString(cam.id()) == camDeviceName)
+ {
+ camDevice = new QCamera(cam);
+ break;
+ }
+ }
+
+ if (camDevice)
+ {
+ const QList<QCameraFormat> supported = camDevice->cameraDevice().videoFormats();
+ bool good = false;
+ for (const QCameraFormat& item : supported)
+ {
+ if (item.pixelFormat() != QVideoFrameFormat::Format_YUYV &&
+ item.pixelFormat() != QVideoFrameFormat::Format_UYVY &&
+ item.pixelFormat() != QVideoFrameFormat::Format_NV12 &&
+ item.pixelFormat() != QVideoFrameFormat::Format_XRGB8888)
+ continue;
+
+ if (item.resolution().width() != 640 && item.resolution().height() != 480)
+ continue;
+
+ camDevice->setCameraFormat(item);
+ good = true;
+ break;
+ }
+
+ if (!good)
+ {
+ delete camDevice;
+ camDevice = nullptr;
+ }
+ else
+ {
+ camDumper = new CameraFrameDumper(this);
+
+ camSession = new QMediaCaptureSession(this);
+ camSession->setCamera(camDevice);
+ camSession->setVideoOutput(camDumper);
+ }
+ }
+#else
+ camDevice = new QCamera(camDeviceName.toUtf8());
+ if (camDevice->error() != QCamera::NoError)
+ {
+ delete camDevice;
+ camDevice = nullptr;
+ }
+
+ if (camDevice)
+ {
+ camDevice->load();
+
+ const QList<QCameraViewfinderSettings> supported = camDevice->supportedViewfinderSettings();
+ bool good = false;
+ for (const QCameraViewfinderSettings& item : supported)
+ {
+ if (item.pixelFormat() != QVideoFrame::Format_YUYV &&
+ item.pixelFormat() != QVideoFrame::Format_UYVY &&
+ item.pixelFormat() != QVideoFrame::Format_NV12 &&
+ item.pixelFormat() != QVideoFrame::Format_RGB32)
+ continue;
+
+ if (item.resolution().width() != 640 && item.resolution().height() != 480)
+ continue;
+
+ camDevice->setViewfinderSettings(item);
+ good = true;
+ break;
+ }
+
+ camDevice->unload();
+
+ if (!good)
+ {
+ delete camDevice;
+ camDevice = nullptr;
+ }
+ else
+ {
+ camDumper = new CameraFrameDumper(this);
+ camDevice->setViewfinder(camDumper);
+ }
+ }
+#endif
+ }
+}
+
+void CameraManager::deInit()
+{
+ if (inputType == 2)
+ {
+ if (camDevice)
+ {
+ camDevice->stop();
+ delete camDevice;
+ delete camDumper;
+#if QT_VERSION >= 0x060000
+ delete camSession;
+#endif
+ }
+ }
+
+ camDevice = nullptr;
+ inputType = -1;
+}
+
+void CameraManager::start()
+{
+ if (startNum == 1) return;
+ startNum = 1;
+
+ if (inputType == 2)
+ {
+ emit camStartSignal();
+ }
+}
+
+void CameraManager::stop()
+{
+ if (startNum == 0) return;
+ startNum = 0;
+
+ if (inputType == 2)
+ {
+ emit camStopSignal();
+ }
+}
+
+bool CameraManager::isStarted()
+{
+ return startNum != 0;
+}
+
+void CameraManager::camStart()
+{
+ if (camDevice)
+ camDevice->start();
+}
+
+void CameraManager::camStop()
+{
+ if (camDevice)
+ camDevice->stop();
+}
+
+void CameraManager::setXFlip(bool flip)
+{
+ xFlip = flip;
+}
+
+void CameraManager::captureFrame(u32* frame, int width, int height, bool yuv)
+{
+ frameMutex.lock();
+
+ if ((width == frameWidth) &&
+ (height == frameHeight) &&
+ (yuv == frameFormatYUV) &&
+ (!xFlip))
+ {
+ int len = width * height;
+ if (yuv) len /= 2;
+ memcpy(frame, frameBuffer, len * sizeof(u32));
+ }
+ else
+ {
+ if (yuv == frameFormatYUV)
+ {
+ copyFrame_Straight(frameBuffer, frameWidth, frameHeight,
+ frame, width, height,
+ xFlip, yuv);
+ }
+ else if (yuv)
+ {
+ copyFrame_RGBtoYUV(frameBuffer, frameWidth, frameHeight,
+ frame, width, height,
+ xFlip);
+ }
+ else
+ {
+ copyFrame_YUVtoRGB(frameBuffer, frameWidth, frameHeight,
+ frame, width, height,
+ xFlip);
+ }
+ }
+
+ frameMutex.unlock();
+}
+
+void CameraManager::feedFrame(u32* frame, int width, int height, bool yuv)
+{
+ frameMutex.lock();
+
+ if (width == frameWidth && height == frameHeight && yuv == frameFormatYUV)
+ {
+ int len = width * height;
+ if (yuv) len /= 2;
+ memcpy(frameBuffer, frame, len * sizeof(u32));
+ }
+ else
+ {
+ if (yuv == frameFormatYUV)
+ {
+ copyFrame_Straight(frame, width, height,
+ frameBuffer, frameWidth, frameHeight,
+ false, yuv);
+ }
+ else if (yuv)
+ {
+ copyFrame_RGBtoYUV(frame, width, height,
+ frameBuffer, frameWidth, frameHeight,
+ false);
+ }
+ else
+ {
+ copyFrame_YUVtoRGB(frame, width, height,
+ frameBuffer, frameWidth, frameHeight,
+ false);
+ }
+ }
+
+ frameMutex.unlock();
+}
+
+void CameraManager::feedFrame_UYVY(u32* frame, int width, int height)
+{
+ for (int y = 0; y < frameHeight; y++)
+ {
+ int sy = (y * height) / frameHeight;
+
+ for (int x = 0; x < frameWidth; x+=2)
+ {
+ int sx = (x * width) / frameWidth;
+
+ u32 val = frame[((sy*width) + sx) >> 1];
+
+ val = ((val & 0xFF00FF00) >> 8) | ((val & 0x00FF00FF) << 8);
+
+ tempFrameBuffer[((y*frameWidth) + x) >> 1] = val;
+ }
+ }
+
+ feedFrame(tempFrameBuffer, frameWidth, frameHeight, true);
+}
+
+void CameraManager::feedFrame_NV12(u8* planeY, u8* planeUV, int width, int height)
+{
+ for (int y = 0; y < frameHeight; y++)
+ {
+ int sy = (y * height) / frameHeight;
+
+ for (int x = 0; x < frameWidth; x+=2)
+ {
+ int sx1 = (x * width) / frameWidth;
+ int sx2 = ((x+1) * width) / frameWidth;
+
+ u32 val;
+
+ u8 y1 = planeY[(sy*width) + sx1];
+ u8 y2 = planeY[(sy*width) + sx2];
+
+ int uvpos = (((sy>>1)*(width>>1)) + (sx1>>1));
+ u8 u = planeUV[uvpos << 1];
+ u8 v = planeUV[(uvpos << 1) + 1];
+
+ val = y1 | (u << 8) | (y2 << 16) | (v << 24);
+ tempFrameBuffer[((y*frameWidth) + x) >> 1] = val;
+ }
+ }
+
+ feedFrame(tempFrameBuffer, frameWidth, frameHeight, true);
+}
+
+void CameraManager::copyFrame_Straight(u32* src, int swidth, int sheight, u32* dst, int dwidth, int dheight, bool xflip, bool yuv)
+{
+ if (yuv)
+ {
+ swidth /= 2;
+ dwidth /= 2;
+ }
+
+ for (int dy = 0; dy < dheight; dy++)
+ {
+ int sy = (dy * sheight) / dheight;
+
+ for (int dx = 0; dx < dwidth; dx++)
+ {
+ int sx = (dx * swidth) / dwidth;
+ if (xflip) sx = swidth-1 - sx;
+
+ u32 val = src[(sy * swidth) + sx];
+
+ if (yuv)
+ {
+ if (xflip)
+ val = (val & 0xFF00FF00) |
+ ((val >> 16) & 0xFF) |
+ ((val & 0xFF) << 16);
+ }
+ else
+ val |= 0xFF000000;
+
+ dst[(dy * dwidth) + dx] = val;
+ }
+ }
+}
+
+void CameraManager::copyFrame_RGBtoYUV(u32* src, int swidth, int sheight, u32* dst, int dwidth, int dheight, bool xflip)
+{
+ for (int dy = 0; dy < dheight; dy++)
+ {
+ int sy = (dy * sheight) / dheight;
+
+ for (int dx = 0; dx < dwidth; dx+=2)
+ {
+ int sx;
+
+ sx = (dx * swidth) / dwidth;
+ if (xflip) sx = swidth-1 - sx;
+
+ u32 pixel1 = src[sy*swidth + sx];
+
+ sx = ((dx+1) * swidth) / dwidth;
+ if (xflip) sx = swidth-1 - sx;
+
+ u32 pixel2 = src[sy*swidth + sx];
+
+ int r1 = (pixel1 >> 16) & 0xFF;
+ int g1 = (pixel1 >> 8) & 0xFF;
+ int b1 = pixel1 & 0xFF;
+
+ int r2 = (pixel2 >> 16) & 0xFF;
+ int g2 = (pixel2 >> 8) & 0xFF;
+ int b2 = pixel2 & 0xFF;
+
+ int y1 = ((r1 * 19595) + (g1 * 38470) + (b1 * 7471)) >> 16;
+ int u1 = ((b1 - y1) * 32244) >> 16;
+ int v1 = ((r1 - y1) * 57475) >> 16;
+
+ int y2 = ((r2 * 19595) + (g2 * 38470) + (b2 * 7471)) >> 16;
+ int u2 = ((b2 - y2) * 32244) >> 16;
+ int v2 = ((r2 - y2) * 57475) >> 16;
+
+ u1 += 128; v1 += 128;
+ u2 += 128; v2 += 128;
+
+ y1 = std::clamp(y1, 0, 255); u1 = std::clamp(u1, 0, 255); v1 = std::clamp(v1, 0, 255);
+ y2 = std::clamp(y2, 0, 255); u2 = std::clamp(u2, 0, 255); v2 = std::clamp(v2, 0, 255);
+
+ // huh
+ u1 = (u1 + u2) >> 1;
+ v1 = (v1 + v2) >> 1;
+
+ dst[(dy*dwidth + dx) / 2] = y1 | (u1 << 8) | (y2 << 16) | (v1 << 24);
+ }
+ }
+}
+
+void CameraManager::copyFrame_YUVtoRGB(u32* src, int swidth, int sheight, u32* dst, int dwidth, int dheight, bool xflip)
+{
+ for (int dy = 0; dy < dheight; dy++)
+ {
+ int sy = (dy * sheight) / dheight;
+
+ for (int dx = 0; dx < dwidth; dx+=2)
+ {
+ int sx = (dx * swidth) / dwidth;
+ if (xflip) sx = swidth-2 - sx;
+
+ u32 val = src[(sy*swidth + sx) / 2];
+
+ int y1, y2;
+ if (xflip)
+ {
+ y1 = (val >> 16) & 0xFF;
+ y2 = val & 0xFF;
+ }
+ else
+ {
+ y1 = val & 0xFF;
+ y2 = (val >> 16) & 0xFF;
+ }
+ int u = (val >> 8) & 0xFF;
+ int v = (val >> 24) & 0xFF;
+
+ u -= 128; v -= 128;
+
+ int r1 = y1 + ((v * 91881) >> 16);
+ int g1 = y1 - ((v * 46793) >> 16) - ((u * 22544) >> 16);
+ int b1 = y1 + ((u * 116129) >> 16);
+
+ int r2 = y2 + ((v * 91881) >> 16);
+ int g2 = y2 - ((v * 46793) >> 16) - ((u * 22544) >> 16);
+ int b2 = y2 + ((u * 116129) >> 16);
+
+ r1 = std::clamp(r1, 0, 255); g1 = std::clamp(g1, 0, 255); b1 = std::clamp(b1, 0, 255);
+ r2 = std::clamp(r2, 0, 255); g2 = std::clamp(g2, 0, 255); b2 = std::clamp(b2, 0, 255);
+
+ u32 col1 = 0xFF000000 | (r1 << 16) | (g1 << 8) | b1;
+ u32 col2 = 0xFF000000 | (r2 << 16) | (g2 << 8) | b2;
+
+ dst[dy*dwidth + dx ] = col1;
+ dst[dy*dwidth + dx+1] = col2;
+ }
+ }
+}
diff --git a/src/frontend/qt_sdl/CameraManager.h b/src/frontend/qt_sdl/CameraManager.h
new file mode 100644
index 0000000..6743d19
--- /dev/null
+++ b/src/frontend/qt_sdl/CameraManager.h
@@ -0,0 +1,134 @@
+/*
+ Copyright 2016-2022 melonDS team
+
+ This file is part of melonDS.
+
+ melonDS 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 3 of the License, or (at your option)
+ any later version.
+
+ melonDS 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 melonDS. If not, see http://www.gnu.org/licenses/.
+*/
+
+#ifndef CAMERAMANAGER_H
+#define CAMERAMANAGER_H
+
+#include <QCamera>
+#if QT_VERSION >= 0x060000
+ #include <QMediaDevices>
+ #include <QCameraDevice>
+ #include <QMediaCaptureSession>
+ #include <QVideoSink>
+#else
+ #include <QCameraInfo>
+ #include <QAbstractVideoSurface>
+ #include <QVideoSurfaceFormat>
+#endif
+#include <QMutex>
+
+#include "types.h"
+
+class CameraManager;
+
+
+#if QT_VERSION >= 0x060000
+
+class CameraFrameDumper : public QVideoSink
+{
+ Q_OBJECT
+
+public:
+ CameraFrameDumper(QObject* parent = nullptr);
+
+public slots:
+ void present(const QVideoFrame& frame);
+
+private:
+ CameraManager* cam;
+};
+
+#else
+
+class CameraFrameDumper : public QAbstractVideoSurface
+{
+ Q_OBJECT
+
+public:
+ CameraFrameDumper(QObject* parent = nullptr);
+
+ bool present(const QVideoFrame& frame) override;
+ QList<QVideoFrame::PixelFormat> supportedPixelFormats(QAbstractVideoBuffer::HandleType type = QAbstractVideoBuffer::NoHandle) const override;
+
+private:
+ CameraManager* cam;
+};
+
+#endif
+
+
+class CameraManager : public QObject
+{
+ Q_OBJECT
+
+public:
+ CameraManager(int num, int width, int height, bool yuv);
+ ~CameraManager();
+
+ void init();
+ void deInit();
+
+ void start();
+ void stop();
+ bool isStarted();
+
+ void setXFlip(bool flip);
+
+ void captureFrame(u32* frame, int width, int height, bool yuv);
+
+ void feedFrame(u32* frame, int width, int height, bool yuv);
+ void feedFrame_UYVY(u32* frame, int width, int height);
+ void feedFrame_NV12(u8* planeY, u8* planeUV, int width, int height);
+
+signals:
+ void camStartSignal();
+ void camStopSignal();
+
+private slots:
+ void camStart();
+ void camStop();
+
+private:
+ int num;
+
+ int startNum;
+
+ int inputType;
+ QString imagePath;
+ QString camDeviceName;
+
+ QCamera* camDevice;
+ CameraFrameDumper* camDumper;
+#if QT_VERSION >= 0x060000
+ QMediaCaptureSession* camSession;
+#endif
+
+ int frameWidth, frameHeight;
+ bool frameFormatYUV;
+ u32* frameBuffer;
+ u32* tempFrameBuffer;
+ QMutex frameMutex;
+
+ bool xFlip;
+
+ void copyFrame_Straight(u32* src, int swidth, int sheight, u32* dst, int dwidth, int dheight, bool xflip, bool yuv);
+ void copyFrame_RGBtoYUV(u32* src, int swidth, int sheight, u32* dst, int dwidth, int dheight, bool xflip);
+ void copyFrame_YUVtoRGB(u32* src, int swidth, int sheight, u32* dst, int dwidth, int dheight, bool xflip);
+};
+
+#endif // CAMERAMANAGER_H
diff --git a/src/frontend/qt_sdl/CameraSettingsDialog.cpp b/src/frontend/qt_sdl/CameraSettingsDialog.cpp
new file mode 100644
index 0000000..1844e0f
--- /dev/null
+++ b/src/frontend/qt_sdl/CameraSettingsDialog.cpp
@@ -0,0 +1,304 @@
+/*
+ Copyright 2016-2022 melonDS team
+
+ This file is part of melonDS.
+
+ melonDS 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 3 of the License, or (at your option)
+ any later version.
+
+ melonDS 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 melonDS. If not, see http://www.gnu.org/licenses/.
+*/
+
+#include <stdio.h>
+#include <QFileDialog>
+#include <QPaintEvent>
+#include <QPainter>
+
+#include "types.h"
+
+#include "CameraSettingsDialog.h"
+#include "ui_CameraSettingsDialog.h"
+
+
+CameraSettingsDialog* CameraSettingsDialog::currentDlg = nullptr;
+
+extern std::string EmuDirectory;
+
+extern CameraManager* camManager[2];
+
+
+CameraPreviewPanel::CameraPreviewPanel(QWidget* parent) : QWidget(parent)
+{
+ currentCam = nullptr;
+ updateTimer = startTimer(50);
+}
+
+CameraPreviewPanel::~CameraPreviewPanel()
+{
+ killTimer(updateTimer);
+}
+
+void CameraPreviewPanel::paintEvent(QPaintEvent* event)
+{
+ QPainter painter(this);
+
+ if (!currentCam)
+ {
+ painter.fillRect(event->rect(), QColor::fromRgb(0, 0, 0));
+ return;
+ }
+
+ QImage picture(256, 192, QImage::Format_RGB32);
+ currentCam->captureFrame((u32*)picture.bits(), 256, 192, false);
+ painter.drawImage(0, 0, picture);
+}
+
+
+CameraSettingsDialog::CameraSettingsDialog(QWidget* parent) : QDialog(parent), ui(new Ui::CameraSettingsDialog)
+{
+ previewPanel = nullptr;
+ currentCfg = nullptr;
+ currentCam = nullptr;
+
+ ui->setupUi(this);
+ setAttribute(Qt::WA_DeleteOnClose);
+
+ for (int i = 0; i < 2; i++)
+ {
+ oldCamSettings[i] = Config::Camera[i];
+ }
+
+ ui->cbCameraSel->addItem("DSi outer camera");
+ ui->cbCameraSel->addItem("DSi inner camera");
+
+#if QT_VERSION >= 0x060000
+ const QList<QCameraDevice> cameras = QMediaDevices::videoInputs();
+ for (const QCameraDevice &cameraInfo : cameras)
+ {
+ QString name = cameraInfo.description();
+ QCameraDevice::Position pos = cameraInfo.position();
+ if (pos != QCameraDevice::UnspecifiedPosition)
+ {
+ name += " (";
+ if (pos == QCameraDevice::FrontFace)
+ name += "inner camera";
+ else if (pos == QCameraDevice::BackFace)
+ name += "outer camera";
+ name += ")";
+ }
+
+ ui->cbPhysicalCamera->addItem(name, QString(cameraInfo.id()));
+ }
+#else
+ const QList<QCameraInfo> cameras = QCameraInfo::availableCameras();
+ for (const QCameraInfo &cameraInfo : cameras)
+ {
+ QString name = cameraInfo.description();
+ QCamera::Position pos = cameraInfo.position();
+ if (pos != QCamera::UnspecifiedPosition)
+ {
+ name += " (";
+ if (pos == QCamera::FrontFace)
+ name += "inner camera";
+ else if (pos == QCamera::BackFace)
+ name += "outer camera";
+ name += ")";
+ }
+
+ ui->cbPhysicalCamera->addItem(name, cameraInfo.deviceName());
+ }
+#endif
+ ui->rbPictureCamera->setEnabled(ui->cbPhysicalCamera->count() > 0);
+
+ grpInputType = new QButtonGroup(this);
+ grpInputType->addButton(ui->rbPictureNone, 0);
+ grpInputType->addButton(ui->rbPictureImg, 1);
+ grpInputType->addButton(ui->rbPictureCamera, 2);
+#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
+ connect(grpInputType, SIGNAL(buttonClicked(int)), this, SLOT(onChangeInputType(int)));
+#else
+ connect(grpInputType, SIGNAL(idClicked(int)), this, SLOT(onChangeInputType(int)));
+#endif
+
+ previewPanel = new CameraPreviewPanel(this);
+ QVBoxLayout* previewLayout = new QVBoxLayout();
+ previewLayout->addWidget(previewPanel);
+ ui->grpPreview->setLayout(previewLayout);
+ previewPanel->setMinimumSize(256, 192);
+ previewPanel->setMaximumSize(256, 192);
+
+ on_cbCameraSel_currentIndexChanged(ui->cbCameraSel->currentIndex());
+}
+
+CameraSettingsDialog::~CameraSettingsDialog()
+{
+ delete ui;
+}
+
+void CameraSettingsDialog::on_CameraSettingsDialog_accepted()
+{
+ for (int i = 0; i < 2; i++)
+ {
+ camManager[i]->stop();
+ }
+
+ Config::Save();
+
+ closeDlg();
+}
+
+void CameraSettingsDialog::on_CameraSettingsDialog_rejected()
+{
+ for (int i = 0; i < 2; i++)
+ {
+ camManager[i]->stop();
+ camManager[i]->deInit();
+ Config::Camera[i] = oldCamSettings[i];
+ camManager[i]->init();
+ }
+
+ closeDlg();
+}
+
+void CameraSettingsDialog::on_cbCameraSel_currentIndexChanged(int id)
+{
+ if (!previewPanel) return;
+
+ if (currentCam)
+ {
+ currentCam->stop();
+ }
+
+ currentId = id;
+ currentCfg = &Config::Camera[id];
+ //currentCam = camManager[id];
+ currentCam = nullptr;
+ populateCamControls(id);
+ currentCam = camManager[id];
+ previewPanel->setCurrentCam(currentCam);
+
+ currentCam->start();
+}
+
+void CameraSettingsDialog::onChangeInputType(int type)
+{
+ if (!currentCfg) return;
+
+ if (currentCam)
+ {
+ currentCam->stop();
+ currentCam->deInit();
+ }
+
+ currentCfg->InputType = type;
+
+ ui->txtSrcImagePath->setEnabled(type == 1);
+ ui->btnSrcImageBrowse->setEnabled(type == 1);
+ ui->cbPhysicalCamera->setEnabled((type == 2) && (ui->cbPhysicalCamera->count()>0));
+
+ currentCfg->ImagePath = ui->txtSrcImagePath->text().toStdString();
+
+ if (ui->cbPhysicalCamera->count() > 0)
+ currentCfg->CamDeviceName = ui->cbPhysicalCamera->currentData().toString().toStdString();
+
+ if (currentCam)
+ {
+ currentCam->init();
+ currentCam->start();
+ }
+}
+
+void CameraSettingsDialog::on_txtSrcImagePath_textChanged()
+{
+ if (!currentCfg) return;
+
+ if (currentCam)
+ {
+ currentCam->stop();
+ currentCam->deInit();
+ }
+
+ currentCfg->ImagePath = ui->txtSrcImagePath->text().toStdString();
+
+ if (currentCam)
+ {
+ currentCam->init();
+ currentCam->start();
+ }
+}
+
+void CameraSettingsDialog::on_btnSrcImageBrowse_clicked()
+{
+ QString file = QFileDialog::getOpenFileName(this,
+ "Select image file...",
+ QString::fromStdString(EmuDirectory),
+ "Image files (*.png *.jpg *.jpeg *.bmp);;Any file (*.*)");
+
+ if (file.isEmpty()) return;
+
+ ui->txtSrcImagePath->setText(file);
+}
+
+void CameraSettingsDialog::on_cbPhysicalCamera_currentIndexChanged(int id)
+{
+ if (!currentCfg) return;
+
+ if (currentCam)
+ {
+ currentCam->stop();
+ currentCam->deInit();
+ }
+
+ currentCfg->CamDeviceName = ui->cbPhysicalCamera->itemData(id).toString().toStdString();
+
+ if (currentCam)
+ {
+ currentCam->init();
+ currentCam->start();
+ }
+}
+
+void CameraSettingsDialog::populateCamControls(int id)
+{
+ Config::CameraConfig& cfg = Config::Camera[id];
+
+ int type = cfg.InputType;
+ if (type < 0 || type >= grpInputType->buttons().count()) type = 0;
+ grpInputType->button(type)->setChecked(true);
+
+ ui->txtSrcImagePath->setText(QString::fromStdString(cfg.ImagePath));
+
+ bool deviceset = false;
+ QString device = QString::fromStdString(cfg.CamDeviceName);
+ for (int i = 0; i < ui->cbPhysicalCamera->count(); i++)
+ {
+ QString itemdev = ui->cbPhysicalCamera->itemData(i).toString();
+ if (itemdev == device)
+ {
+ ui->cbPhysicalCamera->setCurrentIndex(i);
+ deviceset = true;
+ break;
+ }
+ }
+ if (!deviceset)
+ ui->cbPhysicalCamera->setCurrentIndex(0);
+
+ onChangeInputType(type);
+
+ ui->chkFlipPicture->setChecked(cfg.XFlip);
+}
+
+void CameraSettingsDialog::on_chkFlipPicture_clicked()
+{
+ if (!currentCfg) return;
+
+ currentCfg->XFlip = ui->chkFlipPicture->isChecked();
+ if (currentCam) currentCam->setXFlip(currentCfg->XFlip);
+}
diff --git a/src/frontend/qt_sdl/CameraSettingsDialog.h b/src/frontend/qt_sdl/CameraSettingsDialog.h
new file mode 100644
index 0000000..8572ac4
--- /dev/null
+++ b/src/frontend/qt_sdl/CameraSettingsDialog.h
@@ -0,0 +1,108 @@
+/*
+ Copyright 2016-2022 melonDS team
+
+ This file is part of melonDS.
+
+ melonDS 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 3 of the License, or (at your option)
+ any later version.
+
+ melonDS 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 melonDS. If not, see http://www.gnu.org/licenses/.
+*/
+
+#ifndef CAMERASETTINGSDIALOG_H
+#define CAMERASETTINGSDIALOG_H
+
+#include <QDialog>
+#include <QButtonGroup>
+
+#include "Config.h"
+#include "CameraManager.h"
+
+namespace Ui { class CameraSettingsDialog; }
+class CameraSettingsDialog;
+
+class CameraPreviewPanel : public QWidget
+{
+ Q_OBJECT
+
+public:
+ CameraPreviewPanel(QWidget* parent);
+ ~CameraPreviewPanel();
+
+ void setCurrentCam(CameraManager* cam)
+ {
+ currentCam = cam;
+ }
+
+protected:
+ void paintEvent(QPaintEvent* event) override;
+ void timerEvent(QTimerEvent* event) override
+ {
+ repaint();
+ }
+
+private:
+ int updateTimer;
+ CameraManager* currentCam;
+};
+
+class CameraSettingsDialog : public QDialog
+{
+ Q_OBJECT
+
+public:
+ explicit CameraSettingsDialog(QWidget* parent);
+ ~CameraSettingsDialog();
+
+ static CameraSettingsDialog* currentDlg;
+ static CameraSettingsDialog* openDlg(QWidget* parent)
+ {
+ if (currentDlg)
+ {
+ currentDlg->activateWindow();
+ return currentDlg;
+ }
+
+ currentDlg = new CameraSettingsDialog(parent);
+ currentDlg->open();
+ return currentDlg;
+ }
+ static void closeDlg()
+ {
+ currentDlg = nullptr;
+ }
+
+private slots:
+ void on_CameraSettingsDialog_accepted();
+ void on_CameraSettingsDialog_rejected();
+
+ void on_cbCameraSel_currentIndexChanged(int id);
+ void onChangeInputType(int type);
+ void on_txtSrcImagePath_textChanged();
+ void on_btnSrcImageBrowse_clicked();
+ void on_cbPhysicalCamera_currentIndexChanged(int id);
+ void on_chkFlipPicture_clicked();
+
+private:
+ Ui::CameraSettingsDialog* ui;
+
+ QButtonGroup* grpInputType;
+ CameraPreviewPanel* previewPanel;
+
+ int currentId;
+ Config::CameraConfig* currentCfg;
+ CameraManager* currentCam;
+
+ Config::CameraConfig oldCamSettings[2];
+
+ void populateCamControls(int id);
+};
+
+#endif // CAMERASETTINGSDIALOG_H
diff --git a/src/frontend/qt_sdl/CameraSettingsDialog.ui b/src/frontend/qt_sdl/CameraSettingsDialog.ui
new file mode 100644
index 0000000..bbaf45b
--- /dev/null
+++ b/src/frontend/qt_sdl/CameraSettingsDialog.ui
@@ -0,0 +1,170 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>CameraSettingsDialog</class>
+ <widget class="QDialog" name="CameraSettingsDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>605</width>
+ <height>341</height>
+ </rect>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="windowTitle">
+ <string>Camera settings - melonDS</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <property name="sizeConstraint">
+ <enum>QLayout::SetFixedSize</enum>
+ </property>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout" stretch="0,1">
+ <item>
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Configure emulated camera:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="cbCameraSel"/>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QGridLayout" name="gridLayout_3">
+ <item row="0" column="0">
+ <widget class="QGroupBox" name="groupBox">
+ <property name="title">
+ <string>Picture source</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_2">
+ <item row="1" column="2">
+ <widget class="QLineEdit" name="txtSrcImagePath"/>
+ </item>
+ <item row="0" column="0" colspan="4">
+ <widget class="QRadioButton" name="rbPictureNone">
+ <property name="text">
+ <string>None (blank)</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="3">
+ <widget class="QPushButton" name="btnSrcImageBrowse">
+ <property name="text">
+ <string>Browse...</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QRadioButton" name="rbPictureCamera">
+ <property name="text">
+ <string>Physical camera:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QRadioButton" name="rbPictureImg">
+ <property name="text">
+ <string>Image file:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="2" colspan="2">
+ <widget class="QComboBox" name="cbPhysicalCamera"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QGroupBox" name="groupBox_3">
+ <property name="title">
+ <string>Picture settings</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_4">
+ <item row="0" column="0">
+ <widget class="QCheckBox" name="chkFlipPicture">
+ <property name="text">
+ <string>Flip horizontally</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="0" column="2" rowspan="3">
+ <widget class="QGroupBox" name="grpPreview">
+ <property name="title">
+ <string>Preview</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>CameraSettingsDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>CameraSettingsDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/src/frontend/qt_sdl/CheatsDialog.cpp b/src/frontend/qt_sdl/CheatsDialog.cpp
index afa0805..4537df3 100644
--- a/src/frontend/qt_sdl/CheatsDialog.cpp
+++ b/src/frontend/qt_sdl/CheatsDialog.cpp
@@ -1,5 +1,5 @@
/*
- Copyright 2016-2021 Arisotura
+ Copyright 2016-2022 melonDS team
This file is part of melonDS.
@@ -24,6 +24,7 @@
#include "types.h"
#include "Platform.h"
#include "Config.h"
+#include "ROMManager.h"
#include "CheatsDialog.h"
#include "ui_CheatsDialog.h"
@@ -33,15 +34,13 @@ CheatsDialog* CheatsDialog::currentDlg = nullptr;
extern std::string EmuDirectory;
-namespace Frontend { extern ARCodeFile* CheatFile; }
-
CheatsDialog::CheatsDialog(QWidget* parent) : QDialog(parent), ui(new Ui::CheatsDialog)
{
ui->setupUi(this);
setAttribute(Qt::WA_DeleteOnClose);
- codeFile = Frontend::CheatFile;
+ codeFile = ROMManager::GetCheatFile();
QStandardItemModel* model = new QStandardItemModel();
ui->tvCodeList->setModel(model);
@@ -55,7 +54,7 @@ CheatsDialog::CheatsDialog(QWidget* parent) : QDialog(parent), ui(new Ui::Cheats
{
ARCodeCat& cat = *i;
- QStandardItem* catitem = new QStandardItem(cat.Name);
+ QStandardItem* catitem = new QStandardItem(QString::fromStdString(cat.Name));
catitem->setEditable(true);
catitem->setData(QVariant::fromValue(i));
root->appendRow(catitem);
@@ -64,7 +63,7 @@ CheatsDialog::CheatsDialog(QWidget* parent) : QDialog(parent), ui(new Ui::Cheats
{
ARCode& code = *j;
- QStandardItem* codeitem = new QStandardItem(code.Name);
+ QStandardItem* codeitem = new QStandardItem(QString::fromStdString(code.Name));
codeitem->setEditable(true);
codeitem->setCheckable(true);
codeitem->setCheckState(code.Enabled ? Qt::Checked : Qt::Unchecked);
@@ -113,13 +112,12 @@ void CheatsDialog::on_btnNewCat_clicked()
ARCodeCat cat;
cat.Codes.clear();
- memset(cat.Name, 0, 128);
- strncpy(cat.Name, "(new category)", 127);
+ cat.Name = "(new category)";
codeFile->Categories.push_back(cat);
ARCodeCatList::iterator id = codeFile->Categories.end(); id--;
- QStandardItem* catitem = new QStandardItem(cat.Name);
+ QStandardItem* catitem = new QStandardItem(QString::fromStdString(cat.Name));
catitem->setEditable(true);
catitem->setData(QVariant::fromValue(id));
root->appendRow(catitem);
@@ -160,8 +158,7 @@ void CheatsDialog::on_btnNewARCode_clicked()
ARCodeCat& cat = *it_cat;
ARCode code;
- memset(code.Name, 0, 128);
- strncpy(code.Name, "(new AR code)", 127);
+ code.Name = "(new AR code)";
code.Enabled = true;
code.CodeLen = 0;
memset(code.Code, 0, sizeof(code.Code));
@@ -169,7 +166,7 @@ void CheatsDialog::on_btnNewARCode_clicked()
cat.Codes.push_back(code);
ARCodeList::iterator id = cat.Codes.end(); id--;
- QStandardItem* codeitem = new QStandardItem(code.Name);
+ QStandardItem* codeitem = new QStandardItem(QString::fromStdString(code.Name));
codeitem->setEditable(true);
codeitem->setCheckable(true);
codeitem->setCheckState(code.Enabled ? Qt::Checked : Qt::Unchecked);
@@ -275,13 +272,12 @@ void CheatsDialog::onCheatEntryModified(QStandardItem* item)
if (item->text().isEmpty())
{
- QString oldname = QString(cat.Name);
+ QString oldname = QString::fromStdString(cat.Name);
item->setText(oldname.isEmpty() ? "(blank category name?)" : oldname);
}
else
{
- strncpy(cat.Name, item->text().toStdString().c_str(), 127);
- cat.Name[127] = '\0';
+ cat.Name = item->text().toStdString();
}
}
else if (data.canConvert<ARCodeList::iterator>())
@@ -290,13 +286,12 @@ void CheatsDialog::onCheatEntryModified(QStandardItem* item)
if (item->text().isEmpty())
{
- QString oldname = QString(code.Name);
+ QString oldname = QString::fromStdString(code.Name);
item->setText(oldname.isEmpty() ? "(blank code name?)" : oldname);
}
else
{
- strncpy(code.Name, item->text().toStdString().c_str(), 127);
- code.Name[127] = '\0';
+ code.Name = item->text().toStdString();
}
code.Enabled = (item->checkState() == Qt::Checked);
diff --git a/src/frontend/qt_sdl/CheatsDialog.h b/src/frontend/qt_sdl/CheatsDialog.h
index 926657c..c5a7e13 100644
--- a/src/frontend/qt_sdl/CheatsDialog.h
+++ b/src/frontend/qt_sdl/CheatsDialog.h
@@ -1,5 +1,5 @@
/*
- Copyright 2016-2021 Arisotura
+ Copyright 2016-2022 melonDS team
This file is part of melonDS.
diff --git a/src/frontend/qt_sdl/Config.cpp b/src/frontend/qt_sdl/Config.cpp
index 30babaf..8b2f3d4 100644
--- a/src/frontend/qt_sdl/Config.cpp
+++ b/src/frontend/qt_sdl/Config.cpp
@@ -1,5 +1,5 @@
/*
- Copyright 2016-2021 Arisotura
+ Copyright 2016-2022 melonDS team
This file is part of melonDS.
@@ -36,290 +36,315 @@ int JoystickID;
int WindowWidth;
int WindowHeight;
-int WindowMaximized;
+bool WindowMaximized;
int ScreenRotation;
int ScreenGap;
int ScreenLayout;
-int ScreenSwap;
+bool ScreenSwap;
int ScreenSizing;
-int IntegerScaling;
+bool IntegerScaling;
int ScreenAspectTop;
int ScreenAspectBot;
-int ScreenFilter;
+bool ScreenFilter;
-int ScreenUseGL;
-int ScreenVSync;
+bool ScreenUseGL;
+bool ScreenVSync;
int ScreenVSyncInterval;
int _3DRenderer;
-int Threaded3D;
+bool Threaded3D;
int GL_ScaleFactor;
-int GL_BetterPolygons;
+bool GL_BetterPolygons;
-int LimitFPS;
-int AudioSync;
-int ShowOSD;
+bool LimitFPS;
+bool AudioSync;
+bool ShowOSD;
int ConsoleType;
-int DirectBoot;
+bool DirectBoot;
#ifdef JIT_ENABLED
-int JIT_Enable = false;
+bool JIT_Enable = false;
int JIT_MaxBlockSize = 32;
-int JIT_BranchOptimisations = true;
-int JIT_LiteralOptimisations = true;
-int JIT_FastMemory = true;
+bool JIT_BranchOptimisations = true;
+bool JIT_LiteralOptimisations = true;
+bool JIT_FastMemory = true;
#endif
-int ExternalBIOSEnable;
+bool ExternalBIOSEnable;
-char BIOS9Path[1024];
-char BIOS7Path[1024];
-char FirmwarePath[1024];
+std::string BIOS9Path;
+std::string BIOS7Path;
+std::string FirmwarePath;
-char DSiBIOS9Path[1024];
-char DSiBIOS7Path[1024];
-char DSiFirmwarePath[1024];
-char DSiNANDPath[1024];
+std::string DSiBIOS9Path;
+std::string DSiBIOS7Path;
+std::string DSiFirmwarePath;
+std::string DSiNANDPath;
-int DLDIEnable;
-char DLDISDPath[1024];
+bool DLDIEnable;
+std::string DLDISDPath;
int DLDISize;
-int DLDIReadOnly;
-int DLDIFolderSync;
-char DLDIFolderPath[1024];
+bool DLDIReadOnly;
+bool DLDIFolderSync;
+std::string DLDIFolderPath;
-int DSiSDEnable;
-char DSiSDPath[1024];
+bool DSiSDEnable;
+std::string DSiSDPath;
int DSiSDSize;
-int DSiSDReadOnly;
-int DSiSDFolderSync;
-char DSiSDFolderPath[1024];
+bool DSiSDReadOnly;
+bool DSiSDFolderSync;
+std::string DSiSDFolderPath;
-int FirmwareOverrideSettings;
-char FirmwareUsername[64];
+bool FirmwareOverrideSettings;
+std::string FirmwareUsername;
int FirmwareLanguage;
int FirmwareBirthdayMonth;
int FirmwareBirthdayDay;
int FirmwareFavouriteColour;
-char FirmwareMessage[1024];
-char FirmwareMAC[18];
-int RandomizeMAC;
+std::string FirmwareMessage;
+std::string FirmwareMAC;
-int SocketBindAnyAddr;
-char LANDevice[128];
-int DirectLAN;
+int MPAudioMode;
+int MPRecvTimeout;
-int SavestateRelocSRAM;
+std::string LANDevice;
+bool DirectLAN;
+
+bool SavestateRelocSRAM;
int AudioInterp;
int AudioBitrate;
int AudioVolume;
int MicInputType;
-char MicWavPath[1024];
+std::string MicWavPath;
+
+std::string LastROMFolder;
-char LastROMFolder[1024];
+std::string RecentROMList[10];
-char RecentROMList[10][1024];
+std::string SaveFilePath;
+std::string SavestatePath;
+std::string CheatFilePath;
-int EnableCheats;
+bool EnableCheats;
-int MouseHide;
+bool MouseHide;
int MouseHideSeconds;
-int PauseLostFocus;
+bool PauseLostFocus;
+
+bool DSBatteryLevelOkay;
+int DSiBatteryLevel;
+bool DSiBatteryCharging;
+
+CameraConfig Camera[2];
const char* kConfigFile = "melonDS.ini";
+const char* kUniqueConfigFile = "melonDS.%d.ini";
ConfigEntry ConfigFile[] =
{
- {"Key_A", 0, &KeyMapping[0], -1, NULL, 0},
- {"Key_B", 0, &KeyMapping[1], -1, NULL, 0},
- {"Key_Select", 0, &KeyMapping[2], -1, NULL, 0},
- {"Key_Start", 0, &KeyMapping[3], -1, NULL, 0},
- {"Key_Right", 0, &KeyMapping[4], -1, NULL, 0},
- {"Key_Left", 0, &KeyMapping[5], -1, NULL, 0},
- {"Key_Up", 0, &KeyMapping[6], -1, NULL, 0},
- {"Key_Down", 0, &KeyMapping[7], -1, NULL, 0},
- {"Key_R", 0, &KeyMapping[8], -1, NULL, 0},
- {"Key_L", 0, &KeyMapping[9], -1, NULL, 0},
- {"Key_X", 0, &KeyMapping[10], -1, NULL, 0},
- {"Key_Y", 0, &KeyMapping[11], -1, NULL, 0},
-
- {"Joy_A", 0, &JoyMapping[0], -1, NULL, 0},
- {"Joy_B", 0, &JoyMapping[1], -1, NULL, 0},
- {"Joy_Select", 0, &JoyMapping[2], -1, NULL, 0},
- {"Joy_Start", 0, &JoyMapping[3], -1, NULL, 0},
- {"Joy_Right", 0, &JoyMapping[4], -1, NULL, 0},
- {"Joy_Left", 0, &JoyMapping[5], -1, NULL, 0},
- {"Joy_Up", 0, &JoyMapping[6], -1, NULL, 0},
- {"Joy_Down", 0, &JoyMapping[7], -1, NULL, 0},
- {"Joy_R", 0, &JoyMapping[8], -1, NULL, 0},
- {"Joy_L", 0, &JoyMapping[9], -1, NULL, 0},
- {"Joy_X", 0, &JoyMapping[10], -1, NULL, 0},
- {"Joy_Y", 0, &JoyMapping[11], -1, NULL, 0},
-
- {"HKKey_Lid", 0, &HKKeyMapping[HK_Lid], -1, NULL, 0},
- {"HKKey_Mic", 0, &HKKeyMapping[HK_Mic], -1, NULL, 0},
- {"HKKey_Pause", 0, &HKKeyMapping[HK_Pause], -1, NULL, 0},
- {"HKKey_Reset", 0, &HKKeyMapping[HK_Reset], -1, NULL, 0},
- {"HKKey_FastForward", 0, &HKKeyMapping[HK_FastForward], -1, NULL, 0},
- {"HKKey_FastForwardToggle", 0, &HKKeyMapping[HK_FastForwardToggle], -1, NULL, 0},
- {"HKKey_FullscreenToggle", 0, &HKKeyMapping[HK_FullscreenToggle], -1, NULL, 0},
- {"HKKey_SwapScreens", 0, &HKKeyMapping[HK_SwapScreens], -1, NULL, 0},
- {"HKKey_SolarSensorDecrease", 0, &HKKeyMapping[HK_SolarSensorDecrease], -1, NULL, 0},
- {"HKKey_SolarSensorIncrease", 0, &HKKeyMapping[HK_SolarSensorIncrease], -1, NULL, 0},
- {"HKKey_FrameStep", 0, &HKKeyMapping[HK_FrameStep], -1, NULL, 0},
-
- {"HKJoy_Lid", 0, &HKJoyMapping[HK_Lid], -1, NULL, 0},
- {"HKJoy_Mic", 0, &HKJoyMapping[HK_Mic], -1, NULL, 0},
- {"HKJoy_Pause", 0, &HKJoyMapping[HK_Pause], -1, NULL, 0},
- {"HKJoy_Reset", 0, &HKJoyMapping[HK_Reset], -1, NULL, 0},
- {"HKJoy_FastForward", 0, &HKJoyMapping[HK_FastForward], -1, NULL, 0},
- {"HKJoy_FastForwardToggle", 0, &HKJoyMapping[HK_FastForwardToggle], -1, NULL, 0},
- {"HKJoy_FullscreenToggle", 0, &HKJoyMapping[HK_FullscreenToggle], -1, NULL, 0},
- {"HKJoy_SwapScreens", 0, &HKJoyMapping[HK_SwapScreens], -1, NULL, 0},
- {"HKJoy_SolarSensorDecrease", 0, &HKJoyMapping[HK_SolarSensorDecrease], -1, NULL, 0},
- {"HKJoy_SolarSensorIncrease", 0, &HKJoyMapping[HK_SolarSensorIncrease], -1, NULL, 0},
- {"HKJoy_FrameStep", 0, &HKJoyMapping[HK_FrameStep], -1, NULL, 0},
-
- {"JoystickID", 0, &JoystickID, 0, NULL, 0},
-
- {"WindowWidth", 0, &WindowWidth, 256, NULL, 0},
- {"WindowHeight", 0, &WindowHeight, 384, NULL, 0},
- {"WindowMax", 0, &WindowMaximized, 0, NULL, 0},
-
- {"ScreenRotation", 0, &ScreenRotation, 0, NULL, 0},
- {"ScreenGap", 0, &ScreenGap, 0, NULL, 0},
- {"ScreenLayout", 0, &ScreenLayout, 0, NULL, 0},
- {"ScreenSwap", 0, &ScreenSwap, 0, NULL, 0},
- {"ScreenSizing", 0, &ScreenSizing, 0, NULL, 0},
- {"IntegerScaling", 0, &IntegerScaling, 0, NULL, 0},
- {"ScreenAspectTop",0, &ScreenAspectTop,0, NULL, 0},
- {"ScreenAspectBot",0, &ScreenAspectBot,0, NULL, 0},
- {"ScreenFilter", 0, &ScreenFilter, 1, NULL, 0},
-
- {"ScreenUseGL", 0, &ScreenUseGL, 0, NULL, 0},
- {"ScreenVSync", 0, &ScreenVSync, 0, NULL, 0},
- {"ScreenVSyncInterval", 0, &ScreenVSyncInterval, 1, NULL, 0},
-
- {"3DRenderer", 0, &_3DRenderer, 0, NULL, 0},
- {"Threaded3D", 0, &Threaded3D, 1, NULL, 0},
-
- {"GL_ScaleFactor", 0, &GL_ScaleFactor, 1, NULL, 0},
- {"GL_BetterPolygons", 0, &GL_BetterPolygons, 0, NULL, 0},
-
- {"LimitFPS", 0, &LimitFPS, 1, NULL, 0},
- {"AudioSync", 0, &AudioSync, 0, NULL, 0},
- {"ShowOSD", 0, &ShowOSD, 1, NULL, 0},
-
- {"ConsoleType", 0, &ConsoleType, 0, NULL, 0},
- {"DirectBoot", 0, &DirectBoot, 1, NULL, 0},
+ {"Key_A", 0, &KeyMapping[0], -1, true},
+ {"Key_B", 0, &KeyMapping[1], -1, true},
+ {"Key_Select", 0, &KeyMapping[2], -1, true},
+ {"Key_Start", 0, &KeyMapping[3], -1, true},
+ {"Key_Right", 0, &KeyMapping[4], -1, true},
+ {"Key_Left", 0, &KeyMapping[5], -1, true},
+ {"Key_Up", 0, &KeyMapping[6], -1, true},
+ {"Key_Down", 0, &KeyMapping[7], -1, true},
+ {"Key_R", 0, &KeyMapping[8], -1, true},
+ {"Key_L", 0, &KeyMapping[9], -1, true},
+ {"Key_X", 0, &KeyMapping[10], -1, true},
+ {"Key_Y", 0, &KeyMapping[11], -1, true},
+
+ {"Joy_A", 0, &JoyMapping[0], -1, true},
+ {"Joy_B", 0, &JoyMapping[1], -1, true},
+ {"Joy_Select", 0, &JoyMapping[2], -1, true},
+ {"Joy_Start", 0, &JoyMapping[3], -1, true},
+ {"Joy_Right", 0, &JoyMapping[4], -1, true},
+ {"Joy_Left", 0, &JoyMapping[5], -1, true},
+ {"Joy_Up", 0, &JoyMapping[6], -1, true},
+ {"Joy_Down", 0, &JoyMapping[7], -1, true},
+ {"Joy_R", 0, &JoyMapping[8], -1, true},
+ {"Joy_L", 0, &JoyMapping[9], -1, true},
+ {"Joy_X", 0, &JoyMapping[10], -1, true},
+ {"Joy_Y", 0, &JoyMapping[11], -1, true},
+
+ {"HKKey_Lid", 0, &HKKeyMapping[HK_Lid], -1, true},
+ {"HKKey_Mic", 0, &HKKeyMapping[HK_Mic], -1, true},
+ {"HKKey_Pause", 0, &HKKeyMapping[HK_Pause], -1, true},
+ {"HKKey_Reset", 0, &HKKeyMapping[HK_Reset], -1, true},
+ {"HKKey_FastForward", 0, &HKKeyMapping[HK_FastForward], -1, true},
+ {"HKKey_FastForwardToggle", 0, &HKKeyMapping[HK_FastForwardToggle], -1, true},
+ {"HKKey_FullscreenToggle", 0, &HKKeyMapping[HK_FullscreenToggle], -1, true},
+ {"HKKey_SwapScreens", 0, &HKKeyMapping[HK_SwapScreens], -1, true},
+ {"HKKey_SolarSensorDecrease", 0, &HKKeyMapping[HK_SolarSensorDecrease], -1, true},
+ {"HKKey_SolarSensorIncrease", 0, &HKKeyMapping[HK_SolarSensorIncrease], -1, true},
+ {"HKKey_FrameStep", 0, &HKKeyMapping[HK_FrameStep], -1, true},
+
+ {"HKJoy_Lid", 0, &HKJoyMapping[HK_Lid], -1, true},
+ {"HKJoy_Mic", 0, &HKJoyMapping[HK_Mic], -1, true},
+ {"HKJoy_Pause", 0, &HKJoyMapping[HK_Pause], -1, true},
+ {"HKJoy_Reset", 0, &HKJoyMapping[HK_Reset], -1, true},
+ {"HKJoy_FastForward", 0, &HKJoyMapping[HK_FastForward], -1, true},
+ {"HKJoy_FastForwardToggle", 0, &HKJoyMapping[HK_FastForwardToggle], -1, true},
+ {"HKJoy_FullscreenToggle", 0, &HKJoyMapping[HK_FullscreenToggle], -1, true},
+ {"HKJoy_SwapScreens", 0, &HKJoyMapping[HK_SwapScreens], -1, true},
+ {"HKJoy_SolarSensorDecrease", 0, &HKJoyMapping[HK_SolarSensorDecrease], -1, true},
+ {"HKJoy_SolarSensorIncrease", 0, &HKJoyMapping[HK_SolarSensorIncrease], -1, true},
+ {"HKJoy_FrameStep", 0, &HKJoyMapping[HK_FrameStep], -1, true},
+
+ {"JoystickID", 0, &JoystickID, 0, true},
+
+ {"WindowWidth", 0, &WindowWidth, 256, true},
+ {"WindowHeight", 0, &WindowHeight, 384, true},
+ {"WindowMax", 1, &WindowMaximized, false, true},
+
+ {"ScreenRotation", 0, &ScreenRotation, 0, true},
+ {"ScreenGap", 0, &ScreenGap, 0, true},
+ {"ScreenLayout", 0, &ScreenLayout, 0, true},
+ {"ScreenSwap", 1, &ScreenSwap, false, true},
+ {"ScreenSizing", 0, &ScreenSizing, 0, true},
+ {"IntegerScaling", 1, &IntegerScaling, false, true},
+ {"ScreenAspectTop",0, &ScreenAspectTop,0, true},
+ {"ScreenAspectBot",0, &ScreenAspectBot,0, true},
+ {"ScreenFilter", 1, &ScreenFilter, true, true},
+
+ {"ScreenUseGL", 1, &ScreenUseGL, false, false},
+ {"ScreenVSync", 1, &ScreenVSync, false, false},
+ {"ScreenVSyncInterval", 0, &ScreenVSyncInterval, 1, false},
+
+ {"3DRenderer", 0, &_3DRenderer, 0, false},
+ {"Threaded3D", 1, &Threaded3D, true, false},
+
+ {"GL_ScaleFactor", 0, &GL_ScaleFactor, 1, false},
+ {"GL_BetterPolygons", 1, &GL_BetterPolygons, false, false},
+
+ {"LimitFPS", 1, &LimitFPS, true, false},
+ {"AudioSync", 1, &AudioSync, false},
+ {"ShowOSD", 1, &ShowOSD, true, false},
+
+ {"ConsoleType", 0, &ConsoleType, 0, false},
+ {"DirectBoot", 1, &DirectBoot, true, false},
#ifdef JIT_ENABLED
- {"JIT_Enable", 0, &JIT_Enable, 0, NULL, 0},
- {"JIT_MaxBlockSize", 0, &JIT_MaxBlockSize, 32, NULL, 0},
- {"JIT_BranchOptimisations", 0, &JIT_BranchOptimisations, 1, NULL, 0},
- {"JIT_LiteralOptimisations", 0, &JIT_LiteralOptimisations, 1, NULL, 0},
+ {"JIT_Enable", 1, &JIT_Enable, false, false},
+ {"JIT_MaxBlockSize", 0, &JIT_MaxBlockSize, 32, false},
+ {"JIT_BranchOptimisations", 1, &JIT_BranchOptimisations, true, false},
+ {"JIT_LiteralOptimisations", 1, &JIT_LiteralOptimisations, true, false},
#ifdef __APPLE__
- {"JIT_FastMemory", 0, &JIT_FastMemory, 0, NULL, 0},
+ {"JIT_FastMemory", 1, &JIT_FastMemory, false, false},
#else
- {"JIT_FastMemory", 0, &JIT_FastMemory, 1, NULL, 0},
+ {"JIT_FastMemory", 1, &JIT_FastMemory, true, false},
#endif
#endif
- {"ExternalBIOSEnable", 0, &ExternalBIOSEnable, 0, NULL, 0},
-
- {"BIOS9Path", 1, BIOS9Path, 0, "", 1023},
- {"BIOS7Path", 1, BIOS7Path, 0, "", 1023},
- {"FirmwarePath", 1, FirmwarePath, 0, "", 1023},
-
- {"DSiBIOS9Path", 1, DSiBIOS9Path, 0, "", 1023},
- {"DSiBIOS7Path", 1, DSiBIOS7Path, 0, "", 1023},
- {"DSiFirmwarePath", 1, DSiFirmwarePath, 0, "", 1023},
- {"DSiNANDPath", 1, DSiNANDPath, 0, "", 1023},
-
- {"DLDIEnable", 0, &DLDIEnable, 0, NULL, 0},
- {"DLDISDPath", 1, DLDISDPath, 0, "dldi.bin", 1023},
- {"DLDISize", 0, &DLDISize, 0, NULL, 0},
- {"DLDIReadOnly", 0, &DLDIReadOnly, 0, NULL, 0},
- {"DLDIFolderSync", 0, &DLDIFolderSync, 0, NULL, 0},
- {"DLDIFolderPath", 1, DLDIFolderPath, 0, "", 1023},
-
- {"DSiSDEnable", 0, &DSiSDEnable, 0, NULL, 0},
- {"DSiSDPath", 1, DSiSDPath, 0, "dsisd.bin", 1023},
- {"DSiSDSize", 0, &DSiSDSize, 0, NULL, 0},
- {"DSiSDReadOnly", 0, &DSiSDReadOnly, 0, NULL, 0},
- {"DSiSDFolderSync", 0, &DSiSDFolderSync, 0, NULL, 0},
- {"DSiSDFolderPath", 1, DSiSDFolderPath, 0, "", 1023},
-
- {"FirmwareOverrideSettings", 0, &FirmwareOverrideSettings, false, NULL, 0},
- {"FirmwareUsername", 1, FirmwareUsername, 0, "melonDS", 63},
- {"FirmwareLanguage", 0, &FirmwareLanguage, 1, NULL, 0},
- {"FirmwareBirthdayMonth", 0, &FirmwareBirthdayMonth, 0, NULL, 0},
- {"FirmwareBirthdayDay", 0, &FirmwareBirthdayDay, 0, NULL, 0},
- {"FirmwareFavouriteColour", 0, &FirmwareFavouriteColour, 0, NULL, 0},
- {"FirmwareMessage", 1, FirmwareMessage, 0, "", 1023},
- {"FirmwareMAC", 1, FirmwareMAC, 0, "", 17},
- {"RandomizeMAC", 0, &RandomizeMAC, 0, NULL, 0},
-
- {"SockBindAnyAddr", 0, &SocketBindAnyAddr, 0, NULL, 0},
- {"LANDevice", 1, LANDevice, 0, "", 127},
- {"DirectLAN", 0, &DirectLAN, 0, NULL, 0},
-
- {"SavStaRelocSRAM", 0, &SavestateRelocSRAM, 0, NULL, 0},
-
- {"AudioInterp", 0, &AudioInterp, 0, NULL, 0},
- {"AudioBitrate", 0, &AudioBitrate, 0, NULL, 0},
- {"AudioVolume", 0, &AudioVolume, 256, NULL, 0},
- {"MicInputType", 0, &MicInputType, 1, NULL, 0},
- {"MicWavPath", 1, MicWavPath, 0, "", 1023},
-
- {"LastROMFolder", 1, LastROMFolder, 0, "", 1023},
-
- {"RecentROM_0", 1, RecentROMList[0], 0, "", 1023},
- {"RecentROM_1", 1, RecentROMList[1], 0, "", 1023},
- {"RecentROM_2", 1, RecentROMList[2], 0, "", 1023},
- {"RecentROM_3", 1, RecentROMList[3], 0, "", 1023},
- {"RecentROM_4", 1, RecentROMList[4], 0, "", 1023},
- {"RecentROM_5", 1, RecentROMList[5], 0, "", 1023},
- {"RecentROM_6", 1, RecentROMList[6], 0, "", 1023},
- {"RecentROM_7", 1, RecentROMList[7], 0, "", 1023},
- {"RecentROM_8", 1, RecentROMList[8], 0, "", 1023},
- {"RecentROM_9", 1, RecentROMList[9], 0, "", 1023},
-
- {"EnableCheats", 0, &EnableCheats, 0, NULL, 0},
-
- {"MouseHide", 0, &MouseHide, 0, NULL, 0},
- {"MouseHideSeconds", 0, &MouseHideSeconds, 5, NULL, 0},
- {"PauseLostFocus", 0, &PauseLostFocus, 0, NULL, 0},
-
- {"", -1, NULL, 0, NULL, 0}
+ {"ExternalBIOSEnable", 1, &ExternalBIOSEnable, false, false},
+
+ {"BIOS9Path", 2, &BIOS9Path, (std::string)"", false},
+ {"BIOS7Path", 2, &BIOS7Path, (std::string)"", false},
+ {"FirmwarePath", 2, &FirmwarePath, (std::string)"", false},
+
+ {"DSiBIOS9Path", 2, &DSiBIOS9Path, (std::string)"", false},
+ {"DSiBIOS7Path", 2, &DSiBIOS7Path, (std::string)"", false},
+ {"DSiFirmwarePath", 2, &DSiFirmwarePath, (std::string)"", false},
+ {"DSiNANDPath", 2, &DSiNANDPath, (std::string)"", false},
+
+ {"DLDIEnable", 1, &DLDIEnable, false, false},
+ {"DLDISDPath", 2, &DLDISDPath, (std::string)"dldi.bin", false},
+ {"DLDISize", 0, &DLDISize, 0, false},
+ {"DLDIReadOnly", 1, &DLDIReadOnly, false, false},
+ {"DLDIFolderSync", 1, &DLDIFolderSync, false, false},
+ {"DLDIFolderPath", 2, &DLDIFolderPath, (std::string)"", false},
+
+ {"DSiSDEnable", 1, &DSiSDEnable, false, false},
+ {"DSiSDPath", 2, &DSiSDPath, (std::string)"dsisd.bin", false},
+ {"DSiSDSize", 0, &DSiSDSize, 0, false},
+ {"DSiSDReadOnly", 1, &DSiSDReadOnly, false, false},
+ {"DSiSDFolderSync", 1, &DSiSDFolderSync, false, false},
+ {"DSiSDFolderPath", 2, &DSiSDFolderPath, (std::string)"", false},
+
+ {"FirmwareOverrideSettings", 1, &FirmwareOverrideSettings, false, true},
+ {"FirmwareUsername", 2, &FirmwareUsername, (std::string)"melonDS", true},
+ {"FirmwareLanguage", 0, &FirmwareLanguage, 1, true},
+ {"FirmwareBirthdayMonth", 0, &FirmwareBirthdayMonth, 1, true},
+ {"FirmwareBirthdayDay", 0, &FirmwareBirthdayDay, 1, true},
+ {"FirmwareFavouriteColour", 0, &FirmwareFavouriteColour, 0, true},
+ {"FirmwareMessage", 2, &FirmwareMessage, (std::string)"", true},
+ {"FirmwareMAC", 2, &FirmwareMAC, (std::string)"", true},
+
+ {"MPAudioMode", 0, &MPAudioMode, 1, false},
+ {"MPRecvTimeout", 0, &MPRecvTimeout, 25, false},
+
+ {"LANDevice", 2, &LANDevice, (std::string)"", false},
+ {"DirectLAN", 1, &DirectLAN, false, false},
+
+ {"SavStaRelocSRAM", 1, &SavestateRelocSRAM, false, false},
+
+ {"AudioInterp", 0, &AudioInterp, 0, false},
+ {"AudioBitrate", 0, &AudioBitrate, 0, false},
+ {"AudioVolume", 0, &AudioVolume, 256, true},
+ {"MicInputType", 0, &MicInputType, 1, false},
+ {"MicWavPath", 2, &MicWavPath, (std::string)"", false},
+
+ {"LastROMFolder", 2, &LastROMFolder, (std::string)"", true},
+
+ {"RecentROM_0", 2, &RecentROMList[0], (std::string)"", true},
+ {"RecentROM_1", 2, &RecentROMList[1], (std::string)"", true},
+ {"RecentROM_2", 2, &RecentROMList[2], (std::string)"", true},
+ {"RecentROM_3", 2, &RecentROMList[3], (std::string)"", true},
+ {"RecentROM_4", 2, &RecentROMList[4], (std::string)"", true},
+ {"RecentROM_5", 2, &RecentROMList[5], (std::string)"", true},
+ {"RecentROM_6", 2, &RecentROMList[6], (std::string)"", true},
+ {"RecentROM_7", 2, &RecentROMList[7], (std::string)"", true},
+ {"RecentROM_8", 2, &RecentROMList[8], (std::string)"", true},
+ {"RecentROM_9", 2, &RecentROMList[9], (std::string)"", true},
+
+ {"SaveFilePath", 2, &SaveFilePath, (std::string)"", true},
+ {"SavestatePath", 2, &SavestatePath, (std::string)"", true},
+ {"CheatFilePath", 2, &CheatFilePath, (std::string)"", true},
+
+ {"EnableCheats", 1, &EnableCheats, false, true},
+
+ {"MouseHide", 1, &MouseHide, false, false},
+ {"MouseHideSeconds", 0, &MouseHideSeconds, 5, false},
+ {"PauseLostFocus", 1, &PauseLostFocus, false, false},
+
+ {"DSBatteryLevelOkay", 1, &DSBatteryLevelOkay, true, true},
+ {"DSiBatteryLevel", 0, &DSiBatteryLevel, 0xF, true},
+ {"DSiBatteryCharging", 1, &DSiBatteryCharging, true, true},
+
+ // TODO!!
+ // we need a more elegant way to deal with this
+ {"Camera0_InputType", 0, &Camera[0].InputType, 0, false},
+ {"Camera0_ImagePath", 2, &Camera[0].ImagePath, (std::string)"", false},
+ {"Camera0_CamDeviceName", 2, &Camera[0].CamDeviceName, (std::string)"", false},
+ {"Camera0_XFlip", 1, &Camera[0].XFlip, false, false},
+ {"Camera1_InputType", 0, &Camera[1].InputType, 0, false},
+ {"Camera1_ImagePath", 2, &Camera[1].ImagePath, (std::string)"", false},
+ {"Camera1_CamDeviceName", 2, &Camera[1].CamDeviceName, (std::string)"", false},
+ {"Camera1_XFlip", 1, &Camera[1].XFlip, false, false},
+
+ {"", -1, nullptr, 0, false}
};
-void Load()
+void LoadFile(int inst)
{
- ConfigEntry* entry = &ConfigFile[0];
- for (;;)
+ FILE* f;
+ if (inst > 0)
{
- if (!entry->Value) break;
-
- if (entry->Type == 0)
- *(int*)entry->Value = entry->DefaultInt;
- else
- {
- strncpy((char*)entry->Value, entry->DefaultStr, entry->StrLength);
- ((char*)entry->Value)[entry->StrLength] = '\0';
- }
-
- entry++;
+ char name[100] = {0};
+ snprintf(name, 99, kUniqueConfigFile, inst+1);
+ f = Platform::OpenLocalFile(name, "r");
}
+ else
+ f = Platform::OpenLocalFile(kConfigFile, "r");
- FILE* f = Platform::OpenLocalFile(kConfigFile, "r");
if (!f) return;
char linebuf[1024];
@@ -334,44 +359,75 @@ void Load()
entryname[31] = '\0';
if (ret < 2) continue;
- ConfigEntry* entry = &ConfigFile[0];
- for (;;)
+ for (ConfigEntry* entry = &ConfigFile[0]; entry->Value; entry++)
{
- if (!entry->Value) break;
-
if (!strncmp(entry->Name, entryname, 32))
{
- if (entry->Type == 0)
- *(int*)entry->Value = strtol(entryval, NULL, 10);
- else
- strncpy((char*)entry->Value, entryval, entry->StrLength);
+ if ((inst > 0) && (!entry->InstanceUnique))
+ break;
+
+ switch (entry->Type)
+ {
+ case 0: *(int*)entry->Value = strtol(entryval, NULL, 10); break;
+ case 1: *(bool*)entry->Value = strtol(entryval, NULL, 10) ? true:false; break;
+ case 2: *(std::string*)entry->Value = entryval; break;
+ }
break;
}
-
- entry++;
}
}
fclose(f);
}
+void Load()
+{
+
+ for (ConfigEntry* entry = &ConfigFile[0]; entry->Value; entry++)
+ {
+ switch (entry->Type)
+ {
+ case 0: *(int*)entry->Value = std::get<int>(entry->Default); break;
+ case 1: *(bool*)entry->Value = std::get<bool>(entry->Default); break;
+ case 2: *(std::string*)entry->Value = std::get<std::string>(entry->Default); break;
+ }
+ }
+
+ LoadFile(0);
+
+ int inst = Platform::InstanceID();
+ if (inst > 0)
+ LoadFile(inst);
+}
+
void Save()
{
- FILE* f = Platform::OpenLocalFile(kConfigFile, "w");
- if (!f) return;
+ int inst = Platform::InstanceID();
- ConfigEntry* entry = &ConfigFile[0];
- for (;;)
+ FILE* f;
+ if (inst > 0)
{
- if (!entry->Value) break;
+ char name[100] = {0};
+ snprintf(name, 99, kUniqueConfigFile, inst+1);
+ f = Platform::OpenLocalFile(name, "w");
+ }
+ else
+ f = Platform::OpenLocalFile(kConfigFile, "w");
- if (entry->Type == 0)
- fprintf(f, "%s=%d\r\n", entry->Name, *(int*)entry->Value);
- else
- fprintf(f, "%s=%s\r\n", entry->Name, (char*)entry->Value);
+ if (!f) return;
- entry++;
+ for (ConfigEntry* entry = &ConfigFile[0]; entry->Value; entry++)
+ {
+ if ((inst > 0) && (!entry->InstanceUnique))
+ continue;
+
+ switch (entry->Type)
+ {
+ case 0: fprintf(f, "%s=%d\r\n", entry->Name, *(int*)entry->Value); break;
+ case 1: fprintf(f, "%s=%d\r\n", entry->Name, *(bool*)entry->Value ? 1:0); break;
+ case 2: fprintf(f, "%s=%s\r\n", entry->Name, (*(std::string*)entry->Value).c_str()); break;
+ }
}
fclose(f);
diff --git a/src/frontend/qt_sdl/Config.h b/src/frontend/qt_sdl/Config.h
index ad9b4c6..6ccae5f 100644
--- a/src/frontend/qt_sdl/Config.h
+++ b/src/frontend/qt_sdl/Config.h
@@ -1,5 +1,5 @@
/*
- Copyright 2016-2021 Arisotura
+ Copyright 2016-2022 melonDS team
This file is part of melonDS.
@@ -19,6 +19,9 @@
#ifndef PLATFORMCONFIG_H
#define PLATFORMCONFIG_H
+#include <variant>
+#include <string>
+
enum
{
HK_Lid = 0,
@@ -35,17 +38,35 @@ enum
HK_MAX
};
+enum
+{
+ screenSizing_Even,
+ screenSizing_EmphTop,
+ screenSizing_EmphBot,
+ screenSizing_Auto,
+ screenSizing_TopOnly,
+ screenSizing_BotOnly,
+ screenSizing_MAX,
+};
+
namespace Config
{
struct ConfigEntry
{
char Name[32];
- int Type;
- void* Value;
- int DefaultInt;
- const char* DefaultStr;
- int StrLength; // should be set to actual array length minus one
+ int Type; // 0=int 1=bool 2=string
+ void* Value; // pointer to the value variable
+ std::variant<int, bool, std::string> Default;
+ bool InstanceUnique; // whether the setting can exist individually for each instance in multiplayer
+};
+
+struct CameraConfig
+{
+ int InputType; // 0=blank 1=image 2=camera
+ std::string ImagePath;
+ std::string CamDeviceName;
+ bool XFlip;
};
@@ -59,99 +80,110 @@ extern int JoystickID;
extern int WindowWidth;
extern int WindowHeight;
-extern int WindowMaximized;
+extern bool WindowMaximized;
extern int ScreenRotation;
extern int ScreenGap;
extern int ScreenLayout;
-extern int ScreenSwap;
+extern bool ScreenSwap;
extern int ScreenSizing;
extern int ScreenAspectTop;
extern int ScreenAspectBot;
-extern int IntegerScaling;
-extern int ScreenFilter;
+extern bool IntegerScaling;
+extern bool ScreenFilter;
-extern int ScreenUseGL;
-extern int ScreenVSync;
+extern bool ScreenUseGL;
+extern bool ScreenVSync;
extern int ScreenVSyncInterval;
extern int _3DRenderer;
-extern int Threaded3D;
+extern bool Threaded3D;
extern int GL_ScaleFactor;
-extern int GL_BetterPolygons;
+extern bool GL_BetterPolygons;
-extern int LimitFPS;
-extern int AudioSync;
-extern int ShowOSD;
+extern bool LimitFPS;
+extern bool AudioSync;
+extern bool ShowOSD;
extern int ConsoleType;
-extern int DirectBoot;
+extern bool DirectBoot;
#ifdef JIT_ENABLED
-extern int JIT_Enable;
+extern bool JIT_Enable;
extern int JIT_MaxBlockSize;
-extern int JIT_BranchOptimisations;
-extern int JIT_LiteralOptimisations;
-extern int JIT_FastMemory;
+extern bool JIT_BranchOptimisations;
+extern bool JIT_LiteralOptimisations;
+extern bool JIT_FastMemory;
#endif
-extern int ExternalBIOSEnable;
+extern bool ExternalBIOSEnable;
-extern char BIOS9Path[1024];
-extern char BIOS7Path[1024];
-extern char FirmwarePath[1024];
+extern std::string BIOS9Path;
+extern std::string BIOS7Path;
+extern std::string FirmwarePath;
-extern char DSiBIOS9Path[1024];
-extern char DSiBIOS7Path[1024];
-extern char DSiFirmwarePath[1024];
-extern char DSiNANDPath[1024];
+extern std::string DSiBIOS9Path;
+extern std::string DSiBIOS7Path;
+extern std::string DSiFirmwarePath;
+extern std::string DSiNANDPath;
-extern int DLDIEnable;
-extern char DLDISDPath[1024];
+extern bool DLDIEnable;
+extern std::string DLDISDPath;
extern int DLDISize;
-extern int DLDIReadOnly;
-extern int DLDIFolderSync;
-extern char DLDIFolderPath[1024];
+extern bool DLDIReadOnly;
+extern bool DLDIFolderSync;
+extern std::string DLDIFolderPath;
-extern int DSiSDEnable;
-extern char DSiSDPath[1024];
+extern bool DSiSDEnable;
+extern std::string DSiSDPath;
extern int DSiSDSize;
-extern int DSiSDReadOnly;
-extern int DSiSDFolderSync;
-extern char DSiSDFolderPath[1024];
+extern bool DSiSDReadOnly;
+extern bool DSiSDFolderSync;
+extern std::string DSiSDFolderPath;
-extern int FirmwareOverrideSettings;
-extern char FirmwareUsername[64];
+extern bool FirmwareOverrideSettings;
+extern std::string FirmwareUsername;
extern int FirmwareLanguage;
extern int FirmwareBirthdayMonth;
extern int FirmwareBirthdayDay;
extern int FirmwareFavouriteColour;
-extern char FirmwareMessage[1024];
-extern char FirmwareMAC[18];
-extern int RandomizeMAC;
+extern std::string FirmwareMessage;
+extern std::string FirmwareMAC;
+
+extern int MPAudioMode;
+extern int MPRecvTimeout;
-extern int SocketBindAnyAddr;
-extern char LANDevice[128];
-extern int DirectLAN;
+extern std::string LANDevice;
+extern bool DirectLAN;
-extern int SavestateRelocSRAM;
+extern bool SavestateRelocSRAM;
extern int AudioInterp;
extern int AudioBitrate;
extern int AudioVolume;
extern int MicInputType;
-extern char MicWavPath[1024];
+extern std::string MicWavPath;
-extern char LastROMFolder[1024];
+extern std::string LastROMFolder;
-extern char RecentROMList[10][1024];
+extern std::string RecentROMList[10];
-extern int EnableCheats;
+extern std::string SaveFilePath;
+extern std::string SavestatePath;
+extern std::string CheatFilePath;
-extern int MouseHide;
+extern bool EnableCheats;
+
+extern bool MouseHide;
extern int MouseHideSeconds;
-extern int PauseLostFocus;
+extern bool PauseLostFocus;
+
+extern bool DSBatteryLevelOkay;
+extern int DSiBatteryLevel;
+extern bool DSiBatteryCharging;
+
+extern CameraConfig Camera[2];
void Load();
diff --git a/src/frontend/qt_sdl/EmuSettingsDialog.cpp b/src/frontend/qt_sdl/EmuSettingsDialog.cpp
index fd2ca85..bd40568 100644
--- a/src/frontend/qt_sdl/EmuSettingsDialog.cpp
+++ b/src/frontend/qt_sdl/EmuSettingsDialog.cpp
@@ -1,5 +1,5 @@
/*
- Copyright 2016-2021 Arisotura
+ Copyright 2016-2022 melonDS team
This file is part of melonDS.
@@ -42,27 +42,27 @@ EmuSettingsDialog::EmuSettingsDialog(QWidget* parent) : QDialog(parent), ui(new
ui->setupUi(this);
setAttribute(Qt::WA_DeleteOnClose);
- ui->chkExternalBIOS->setChecked(Config::ExternalBIOSEnable != 0);
- ui->txtBIOS9Path->setText(Config::BIOS9Path);
- ui->txtBIOS7Path->setText(Config::BIOS7Path);
- ui->txtFirmwarePath->setText(Config::FirmwarePath);
+ ui->chkExternalBIOS->setChecked(Config::ExternalBIOSEnable);
+ ui->txtBIOS9Path->setText(QString::fromStdString(Config::BIOS9Path));
+ ui->txtBIOS7Path->setText(QString::fromStdString(Config::BIOS7Path));
+ ui->txtFirmwarePath->setText(QString::fromStdString(Config::FirmwarePath));
- ui->txtDSiBIOS9Path->setText(Config::DSiBIOS9Path);
- ui->txtDSiBIOS7Path->setText(Config::DSiBIOS7Path);
- ui->txtDSiFirmwarePath->setText(Config::DSiFirmwarePath);
- ui->txtDSiNANDPath->setText(Config::DSiNANDPath);
+ ui->txtDSiBIOS9Path->setText(QString::fromStdString(Config::DSiBIOS9Path));
+ ui->txtDSiBIOS7Path->setText(QString::fromStdString(Config::DSiBIOS7Path));
+ ui->txtDSiFirmwarePath->setText(QString::fromStdString(Config::DSiFirmwarePath));
+ ui->txtDSiNANDPath->setText(QString::fromStdString(Config::DSiNANDPath));
ui->cbxConsoleType->addItem("DS");
ui->cbxConsoleType->addItem("DSi (experimental)");
ui->cbxConsoleType->setCurrentIndex(Config::ConsoleType);
- ui->chkDirectBoot->setChecked(Config::DirectBoot != 0);
+ ui->chkDirectBoot->setChecked(Config::DirectBoot);
#ifdef JIT_ENABLED
- ui->chkEnableJIT->setChecked(Config::JIT_Enable != 0);
- ui->chkJITBranchOptimisations->setChecked(Config::JIT_BranchOptimisations != 0);
- ui->chkJITLiteralOptimisations->setChecked(Config::JIT_LiteralOptimisations != 0);
- ui->chkJITFastMemory->setChecked(Config::JIT_FastMemory != 0);
+ ui->chkEnableJIT->setChecked(Config::JIT_Enable);
+ ui->chkJITBranchOptimisations->setChecked(Config::JIT_BranchOptimisations);
+ ui->chkJITLiteralOptimisations->setChecked(Config::JIT_LiteralOptimisations);
+ ui->chkJITFastMemory->setChecked(Config::JIT_FastMemory);
#ifdef __APPLE__
ui->chkJITFastMemory->setDisabled(true);
#endif
@@ -101,20 +101,20 @@ EmuSettingsDialog::EmuSettingsDialog(QWidget* parent) : QDialog(parent), ui(new
ui->cbxDSiSDSize->addItem(sizelbl);
}
- ui->cbDLDIEnable->setChecked(Config::DLDIEnable != 0);
- ui->txtDLDISDPath->setText(Config::DLDISDPath);
+ ui->cbDLDIEnable->setChecked(Config::DLDIEnable);
+ ui->txtDLDISDPath->setText(QString::fromStdString(Config::DLDISDPath));
ui->cbxDLDISize->setCurrentIndex(Config::DLDISize);
- ui->cbDLDIReadOnly->setChecked(Config::DLDIReadOnly != 0);
- ui->cbDLDIFolder->setChecked(Config::DLDIFolderSync != 0);
- ui->txtDLDIFolder->setText(Config::DLDIFolderPath);
+ ui->cbDLDIReadOnly->setChecked(Config::DLDIReadOnly);
+ ui->cbDLDIFolder->setChecked(Config::DLDIFolderSync);
+ ui->txtDLDIFolder->setText(QString::fromStdString(Config::DLDIFolderPath));
on_cbDLDIEnable_toggled();
- ui->cbDSiSDEnable->setChecked(Config::DSiSDEnable != 0);
- ui->txtDSiSDPath->setText(Config::DSiSDPath);
+ ui->cbDSiSDEnable->setChecked(Config::DSiSDEnable);
+ ui->txtDSiSDPath->setText(QString::fromStdString(Config::DSiSDPath));
ui->cbxDSiSDSize->setCurrentIndex(Config::DSiSDSize);
- ui->cbDSiSDReadOnly->setChecked(Config::DSiSDReadOnly != 0);
- ui->cbDSiSDFolder->setChecked(Config::DSiSDFolderSync != 0);
- ui->txtDSiSDFolder->setText(Config::DSiSDFolderPath);
+ ui->cbDSiSDReadOnly->setChecked(Config::DSiSDReadOnly);
+ ui->cbDSiSDFolder->setChecked(Config::DSiSDFolderSync);
+ ui->txtDSiSDFolder->setText(QString::fromStdString(Config::DSiSDFolderPath));
on_cbDSiSDEnable_toggled();
}
@@ -140,8 +140,7 @@ void EmuSettingsDialog::verifyFirmware()
// looked at has 0x180 bytes from the header repeated at 0x3FC80, but
// bytes 0x0C-0x14 are different.
- char filename[1024];
- strncpy(filename, ui->txtFirmwarePath->text().toStdString().c_str(), 1023); filename[1023] = '\0';
+ std::string filename = ui->txtFirmwarePath->text().toStdString();
FILE* f = Platform::OpenLocalFile(filename, "rb");
if (!f) return;
u8 chk1[0x180], chk2[0x180];
@@ -175,24 +174,24 @@ void EmuSettingsDialog::done(int r)
verifyFirmware();
int consoleType = ui->cbxConsoleType->currentIndex();
- int directBoot = ui->chkDirectBoot->isChecked() ? 1:0;
+ bool directBoot = ui->chkDirectBoot->isChecked();
- int jitEnable = ui->chkEnableJIT->isChecked() ? 1:0;
+ bool jitEnable = ui->chkEnableJIT->isChecked();
int jitMaxBlockSize = ui->spnJITMaximumBlockSize->value();
- int jitBranchOptimisations = ui->chkJITBranchOptimisations->isChecked() ? 1:0;
- int jitLiteralOptimisations = ui->chkJITLiteralOptimisations->isChecked() ? 1:0;
- int jitFastMemory = ui->chkJITFastMemory->isChecked() ? 1:0;
+ bool jitBranchOptimisations = ui->chkJITBranchOptimisations->isChecked();
+ bool jitLiteralOptimisations = ui->chkJITLiteralOptimisations->isChecked();
+ bool jitFastMemory = ui->chkJITFastMemory->isChecked();
- int externalBiosEnable = ui->chkExternalBIOS->isChecked() ? 1:0;
+ bool externalBiosEnable = ui->chkExternalBIOS->isChecked();
std::string bios9Path = ui->txtBIOS9Path->text().toStdString();
std::string bios7Path = ui->txtBIOS7Path->text().toStdString();
std::string firmwarePath = ui->txtFirmwarePath->text().toStdString();
- int dldiEnable = ui->cbDLDIEnable->isChecked() ? 1:0;
+ bool dldiEnable = ui->cbDLDIEnable->isChecked();
std::string dldiSDPath = ui->txtDLDISDPath->text().toStdString();
int dldiSize = ui->cbxDLDISize->currentIndex();
- int dldiReadOnly = ui->cbDLDIReadOnly->isChecked() ? 1:0;
- int dldiFolderSync = ui->cbDLDIFolder->isChecked() ? 1:0;
+ bool dldiReadOnly = ui->cbDLDIReadOnly->isChecked();
+ bool dldiFolderSync = ui->cbDLDIFolder->isChecked();
std::string dldiFolderPath = ui->txtDLDIFolder->text().toStdString();
std::string dsiBios9Path = ui->txtDSiBIOS9Path->text().toStdString();
@@ -200,11 +199,11 @@ void EmuSettingsDialog::done(int r)
std::string dsiFirmwarePath = ui->txtDSiFirmwarePath->text().toStdString();
std::string dsiNANDPath = ui->txtDSiNANDPath->text().toStdString();
- int dsiSDEnable = ui->cbDSiSDEnable->isChecked() ? 1:0;
+ bool dsiSDEnable = ui->cbDSiSDEnable->isChecked();
std::string dsiSDPath = ui->txtDSiSDPath->text().toStdString();
int dsiSDSize = ui->cbxDSiSDSize->currentIndex();
- int dsiSDReadOnly = ui->cbDSiSDReadOnly->isChecked() ? 1:0;
- int dsiSDFolderSync = ui->cbDSiSDFolder->isChecked() ? 1:0;
+ bool dsiSDReadOnly = ui->cbDSiSDReadOnly->isChecked();
+ bool dsiSDFolderSync = ui->cbDSiSDFolder->isChecked();
std::string dsiSDFolderPath = ui->txtDSiSDFolder->text().toStdString();
if (consoleType != Config::ConsoleType
@@ -217,25 +216,25 @@ void EmuSettingsDialog::done(int r)
|| jitFastMemory != Config::JIT_FastMemory
#endif
|| externalBiosEnable != Config::ExternalBIOSEnable
- || strcmp(Config::BIOS9Path, bios9Path.c_str()) != 0
- || strcmp(Config::BIOS7Path, bios7Path.c_str()) != 0
- || strcmp(Config::FirmwarePath, firmwarePath.c_str()) != 0
+ || bios9Path != Config::BIOS9Path
+ || bios7Path != Config::BIOS7Path
+ || firmwarePath != Config::FirmwarePath
|| dldiEnable != Config::DLDIEnable
- || strcmp(Config::DLDISDPath, dldiSDPath.c_str()) != 0
+ || dldiSDPath != Config::DLDISDPath
|| dldiSize != Config::DLDISize
|| dldiReadOnly != Config::DLDIReadOnly
|| dldiFolderSync != Config::DLDIFolderSync
- || strcmp(Config::DLDIFolderPath, dldiFolderPath.c_str()) != 0
- || strcmp(Config::DSiBIOS9Path, dsiBios9Path.c_str()) != 0
- || strcmp(Config::DSiBIOS7Path, dsiBios7Path.c_str()) != 0
- || strcmp(Config::DSiFirmwarePath, dsiFirmwarePath.c_str()) != 0
- || strcmp(Config::DSiNANDPath, dsiNANDPath.c_str()) != 0
+ || dldiFolderPath != Config::DLDIFolderPath
+ || dsiBios9Path != Config::DSiBIOS9Path
+ || dsiBios7Path != Config::DSiBIOS7Path
+ || dsiFirmwarePath != Config::DSiFirmwarePath
+ || dsiNANDPath != Config::DSiNANDPath
|| dsiSDEnable != Config::DSiSDEnable
- || strcmp(Config::DSiSDPath, dsiSDPath.c_str()) != 0
+ || dsiSDPath != Config::DSiSDPath
|| dsiSDSize != Config::DSiSDSize
|| dsiSDReadOnly != Config::DSiSDReadOnly
|| dsiSDFolderSync != Config::DSiSDFolderSync
- || strcmp(Config::DSiSDFolderPath, dsiSDFolderPath.c_str()) != 0)
+ || dsiSDFolderPath != Config::DSiSDFolderPath)
{
if (RunningSomething
&& QMessageBox::warning(this, "Reset necessary to apply changes",
@@ -244,28 +243,28 @@ void EmuSettingsDialog::done(int r)
return;
Config::ExternalBIOSEnable = externalBiosEnable;
- strncpy(Config::BIOS9Path, bios9Path.c_str(), 1023); Config::BIOS9Path[1023] = '\0';
- strncpy(Config::BIOS7Path, bios7Path.c_str(), 1023); Config::BIOS7Path[1023] = '\0';
- strncpy(Config::FirmwarePath, firmwarePath.c_str(), 1023); Config::FirmwarePath[1023] = '\0';
+ Config::BIOS9Path = bios9Path;
+ Config::BIOS7Path = bios7Path;
+ Config::FirmwarePath = firmwarePath;
Config::DLDIEnable = dldiEnable;
- strncpy(Config::DLDISDPath, dldiSDPath.c_str(), 1023); Config::DLDISDPath[1023] = '\0';
+ Config::DLDISDPath = dldiSDPath;
Config::DLDISize = dldiSize;
Config::DLDIReadOnly = dldiReadOnly;
Config::DLDIFolderSync = dldiFolderSync;
- strncpy(Config::DLDIFolderPath, dldiFolderPath.c_str(), 1023); Config::DLDIFolderPath[1023] = '\0';
+ Config::DLDIFolderPath = dldiFolderPath;
- strncpy(Config::DSiBIOS9Path, dsiBios9Path.c_str(), 1023); Config::DSiBIOS9Path[1023] = '\0';
- strncpy(Config::DSiBIOS7Path, dsiBios7Path.c_str(), 1023); Config::DSiBIOS7Path[1023] = '\0';
- strncpy(Config::DSiFirmwarePath, dsiFirmwarePath.c_str(), 1023); Config::DSiFirmwarePath[1023] = '\0';
- strncpy(Config::DSiNANDPath, dsiNANDPath.c_str(), 1023); Config::DSiNANDPath[1023] = '\0';
+ Config::DSiBIOS9Path = dsiBios9Path;
+ Config::DSiBIOS7Path = dsiBios7Path;
+ Config::DSiFirmwarePath = dsiFirmwarePath;
+ Config::DSiNANDPath = dsiNANDPath;
Config::DSiSDEnable = dsiSDEnable;
- strncpy(Config::DSiSDPath, dsiSDPath.c_str(), 1023); Config::DSiSDPath[1023] = '\0';
+ Config::DSiSDPath = dsiSDPath;
Config::DSiSDSize = dsiSDSize;
Config::DSiSDReadOnly = dsiSDReadOnly;
Config::DSiSDFolderSync = dsiSDFolderSync;
- strncpy(Config::DSiSDFolderPath, dsiSDFolderPath.c_str(), 1023); Config::DSiSDFolderPath[1023] = '\0';
+ Config::DSiSDFolderPath = dsiSDFolderPath;
#ifdef JIT_ENABLED
Config::JIT_Enable = jitEnable;
diff --git a/src/frontend/qt_sdl/EmuSettingsDialog.h b/src/frontend/qt_sdl/EmuSettingsDialog.h
index 60e2160..6a79626 100644
--- a/src/frontend/qt_sdl/EmuSettingsDialog.h
+++ b/src/frontend/qt_sdl/EmuSettingsDialog.h
@@ -1,5 +1,5 @@
/*
- Copyright 2016-2021 Arisotura
+ Copyright 2016-2022 melonDS team
This file is part of melonDS.
diff --git a/src/frontend/qt_sdl/FirmwareSettingsDialog.cpp b/src/frontend/qt_sdl/FirmwareSettingsDialog.cpp
index 0b2cad6..ffca567 100644
--- a/src/frontend/qt_sdl/FirmwareSettingsDialog.cpp
+++ b/src/frontend/qt_sdl/FirmwareSettingsDialog.cpp
@@ -1,5 +1,5 @@
/*
- Copyright 2016-2020 Arisotura
+ Copyright 2016-2022 melonDS team
This file is part of melonDS.
@@ -18,6 +18,7 @@
#include <QMessageBox>
+#include "Platform.h"
#include "Config.h"
#include "FirmwareSettingsDialog.h"
@@ -35,7 +36,7 @@ FirmwareSettingsDialog::FirmwareSettingsDialog(QWidget* parent) : QDialog(parent
ui->setupUi(this);
setAttribute(Qt::WA_DeleteOnClose);
- ui->usernameEdit->setText(Config::FirmwareUsername);
+ ui->usernameEdit->setText(QString::fromStdString(Config::FirmwareUsername));
ui->languageBox->addItems(languages);
ui->languageBox->setCurrentIndex(Config::FirmwareLanguage);
@@ -59,13 +60,19 @@ FirmwareSettingsDialog::FirmwareSettingsDialog(QWidget* parent) : QDialog(parent
}
ui->colorsEdit->setCurrentIndex(Config::FirmwareFavouriteColour);
- ui->messageEdit->setText(Config::FirmwareMessage);
+ ui->messageEdit->setText(QString::fromStdString(Config::FirmwareMessage));
ui->overrideFirmwareBox->setChecked(Config::FirmwareOverrideSettings);
- ui->txtMAC->setText(Config::FirmwareMAC);
- ui->cbRandomizeMAC->setChecked(Config::RandomizeMAC != 0);
- on_cbRandomizeMAC_toggled();
+ ui->txtMAC->setText(QString::fromStdString(Config::FirmwareMAC));
+
+ on_overrideFirmwareBox_toggled();
+
+ int inst = Platform::InstanceID();
+ if (inst > 0)
+ ui->lblInstanceNum->setText(QString("Configuring settings for instance %1").arg(inst+1));
+ else
+ ui->lblInstanceNum->hide();
}
FirmwareSettingsDialog::~FirmwareSettingsDialog()
@@ -123,7 +130,7 @@ void FirmwareSettingsDialog::done(int r)
return;
}
- int newOverride = ui->overrideFirmwareBox->isChecked();
+ bool newOverride = ui->overrideFirmwareBox->isChecked();
std::string newName = ui->usernameEdit->text().toStdString();
int newLanguage = ui->languageBox->currentIndex();
@@ -133,17 +140,15 @@ void FirmwareSettingsDialog::done(int r)
std::string newMessage = ui->messageEdit->text().toStdString();
std::string newMAC = ui->txtMAC->text().toStdString();
- int newRandomizeMAC = ui->cbRandomizeMAC->isChecked() ? 1:0;
if ( newOverride != Config::FirmwareOverrideSettings
- || strcmp(newName.c_str(), Config::FirmwareUsername) != 0
+ || newName != Config::FirmwareUsername
|| newLanguage != Config::FirmwareLanguage
|| newFavColor != Config::FirmwareFavouriteColour
|| newBirthdayDay != Config::FirmwareBirthdayDay
|| newBirthdayMonth != Config::FirmwareBirthdayMonth
- || strcmp(newMessage.c_str(), Config::FirmwareMessage) != 0
- || strcmp(newMAC.c_str(), Config::FirmwareMAC) != 0
- || newRandomizeMAC != Config::RandomizeMAC)
+ || newMessage != Config::FirmwareMessage
+ || newMAC != Config::FirmwareMAC)
{
if (RunningSomething
&& QMessageBox::warning(this, "Reset necessary to apply changes",
@@ -153,15 +158,14 @@ void FirmwareSettingsDialog::done(int r)
Config::FirmwareOverrideSettings = newOverride;
- strncpy(Config::FirmwareUsername, newName.c_str(), 63); Config::FirmwareUsername[63] = '\0';
+ Config::FirmwareUsername = newName;
Config::FirmwareLanguage = newLanguage;
Config::FirmwareFavouriteColour = newFavColor;
Config::FirmwareBirthdayDay = newBirthdayDay;
Config::FirmwareBirthdayMonth = newBirthdayMonth;
- strncpy(Config::FirmwareMessage, newMessage.c_str(), 1023); Config::FirmwareMessage[1023] = '\0';
+ Config::FirmwareMessage = newMessage;
- strncpy(Config::FirmwareMAC, newMAC.c_str(), 17); Config::FirmwareMAC[17] = '\0';
- Config::RandomizeMAC = newRandomizeMAC;
+ Config::FirmwareMAC = newMAC;
Config::Save();
@@ -202,8 +206,9 @@ void FirmwareSettingsDialog::on_cbxBirthdayMonth_currentIndexChanged(int idx)
}
}
-void FirmwareSettingsDialog::on_cbRandomizeMAC_toggled()
+void FirmwareSettingsDialog::on_overrideFirmwareBox_toggled()
{
- bool disable = ui->cbRandomizeMAC->isChecked();
- ui->txtMAC->setDisabled(disable);
+ bool disable = !ui->overrideFirmwareBox->isChecked();
+ ui->grpUserSettings->setDisabled(disable);
+ ui->grpWifiSettings->setDisabled(disable);
}
diff --git a/src/frontend/qt_sdl/FirmwareSettingsDialog.h b/src/frontend/qt_sdl/FirmwareSettingsDialog.h
index 1ae409f..b3695e2 100644
--- a/src/frontend/qt_sdl/FirmwareSettingsDialog.h
+++ b/src/frontend/qt_sdl/FirmwareSettingsDialog.h
@@ -1,5 +1,5 @@
/*
- Copyright 2016-2020 Arisotura
+ Copyright 2016-2022 melonDS team
This file is part of melonDS.
@@ -109,7 +109,7 @@ public:
}
currentDlg = new FirmwareSettingsDialog(parent);
- currentDlg->show();
+ currentDlg->open();
return currentDlg;
}
static void closeDlg()
@@ -123,7 +123,7 @@ private slots:
void done(int r);
void on_cbxBirthdayMonth_currentIndexChanged(int idx);
- void on_cbRandomizeMAC_toggled();
+ void on_overrideFirmwareBox_toggled();
private:
bool verifyMAC();
diff --git a/src/frontend/qt_sdl/FirmwareSettingsDialog.ui b/src/frontend/qt_sdl/FirmwareSettingsDialog.ui
index a97689c..3714629 100644
--- a/src/frontend/qt_sdl/FirmwareSettingsDialog.ui
+++ b/src/frontend/qt_sdl/FirmwareSettingsDialog.ui
@@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>511</width>
- <height>342</height>
+ <height>357</height>
</rect>
</property>
<property name="sizePolicy">
@@ -24,6 +24,13 @@
<enum>QLayout::SetFixedSize</enum>
</property>
<item>
+ <widget class="QLabel" name="lblInstanceNum">
+ <property name="text">
+ <string>Configuring settings for instance X</string>
+ </property>
+ </widget>
+ </item>
+ <item>
<widget class="QGroupBox" name="grpGeneral">
<property name="title">
<string>General</string>
@@ -144,9 +151,9 @@
</widget>
</item>
<item row="1" column="1">
- <widget class="QCheckBox" name="cbRandomizeMAC">
+ <widget class="QLabel" name="label_6">
<property name="text">
- <string>Randomize</string>
+ <string>(leave empty to use default MAC)</string>
</property>
</widget>
</item>
diff --git a/src/frontend/qt_sdl/Input.cpp b/src/frontend/qt_sdl/Input.cpp
index cca11e7..c1ef87c 100644
--- a/src/frontend/qt_sdl/Input.cpp
+++ b/src/frontend/qt_sdl/Input.cpp
@@ -1,5 +1,5 @@
/*
- Copyright 2016-2021 Arisotura
+ Copyright 2016-2022 melonDS team
This file is part of melonDS.
diff --git a/src/frontend/qt_sdl/Input.h b/src/frontend/qt_sdl/Input.h
index d349f19..0d2292d 100644
--- a/src/frontend/qt_sdl/Input.h
+++ b/src/frontend/qt_sdl/Input.h
@@ -1,5 +1,5 @@
/*
- Copyright 2016-2021 Arisotura
+ Copyright 2016-2022 melonDS team
This file is part of melonDS.
diff --git a/src/frontend/qt_sdl/InputConfig/InputConfigDialog.cpp b/src/frontend/qt_sdl/InputConfig/InputConfigDialog.cpp
index 2a50539..92a0186 100644
--- a/src/frontend/qt_sdl/InputConfig/InputConfigDialog.cpp
+++ b/src/frontend/qt_sdl/InputConfig/InputConfigDialog.cpp
@@ -1,5 +1,5 @@
/*
- Copyright 2016-2021 Arisotura
+ Copyright 2016-2022 melonDS team
This file is part of melonDS.
@@ -24,6 +24,7 @@
#include <SDL2/SDL.h>
#include "types.h"
+#include "Platform.h"
#include "Config.h"
#include "MapButton.h"
@@ -123,6 +124,12 @@ InputConfigDialog::InputConfigDialog(QWidget* parent) : QDialog(parent), ui(new
}
setupKeypadPage();
+
+ int inst = Platform::InstanceID();
+ if (inst > 0)
+ ui->lblInstanceNum->setText(QString("Configuring mappings for instance %1").arg(inst+1));
+ else
+ ui->lblInstanceNum->hide();
}
InputConfigDialog::~InputConfigDialog()
diff --git a/src/frontend/qt_sdl/InputConfig/InputConfigDialog.h b/src/frontend/qt_sdl/InputConfig/InputConfigDialog.h
index 069dfcb..f1ea059 100644
--- a/src/frontend/qt_sdl/InputConfig/InputConfigDialog.h
+++ b/src/frontend/qt_sdl/InputConfig/InputConfigDialog.h
@@ -1,5 +1,5 @@
/*
- Copyright 2016-2021 Arisotura
+ Copyright 2016-2022 melonDS team
This file is part of melonDS.
diff --git a/src/frontend/qt_sdl/InputConfig/InputConfigDialog.ui b/src/frontend/qt_sdl/InputConfig/InputConfigDialog.ui
index 6f4bb5d..0db61b1 100644
--- a/src/frontend/qt_sdl/InputConfig/InputConfigDialog.ui
+++ b/src/frontend/qt_sdl/InputConfig/InputConfigDialog.ui
@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
- <width>710</width>
- <height>709</height>
+ <width>770</width>
+ <height>678</height>
</rect>
</property>
<property name="windowTitle">
@@ -20,7 +20,7 @@
<property name="sizeConstraint">
<enum>QLayout::SetFixedSize</enum>
</property>
- <item row="7" column="1">
+ <item row="8" column="1">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
@@ -30,49 +30,7 @@
</property>
</widget>
</item>
- <item row="6" column="0" colspan="2">
- <layout class="QHBoxLayout" name="horizontalLayout">
- <property name="leftMargin">
- <number>0</number>
- </property>
- <property name="topMargin">
- <number>0</number>
- </property>
- <property name="rightMargin">
- <number>0</number>
- </property>
- <property name="bottomMargin">
- <number>0</number>
- </property>
- <item>
- <widget class="QLabel" name="label">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>Joystick:</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QComboBox" name="cbxJoystick">
- <property name="sizePolicy">
- <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="whatsThis">
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Selects which joystick will be used for joystick input, if any is present.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item row="0" column="0" colspan="2">
+ <item row="1" column="0" colspan="2">
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>0</number>
@@ -82,13 +40,128 @@
<string>DS keypad</string>
</attribute>
<layout class="QGridLayout" name="gridLayout">
- <item row="1" column="0">
+ <item row="0" column="0">
<widget class="QStackedWidget" name="stackMapping">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="keyPage">
<layout class="QGridLayout" name="gridLayout_2">
+ <item row="4" column="1" colspan="3">
+ <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <item>
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="btnJoyMapSwitch">
+ <property name="text">
+ <string>Switch to Joystick mappings</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ <item row="1" column="3">
+ <widget class="QWidget" name="widget_4" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_15">
+ <item>
+ <widget class="QGroupBox" name="grp_R">
+ <property name="title">
+ <string/>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="label_8">
+ <property name="text">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;R&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="btnKeyR">
+ <property name="minimumSize">
+ <size>
+ <width>70</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string>R</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_8">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
<item row="1" column="1">
<widget class="QWidget" name="widget_3" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_14">
@@ -108,12 +181,12 @@
<item>
<widget class="QGroupBox" name="grp_L">
<property name="title">
- <string>L</string>
+ <string/>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
- <layout class="QHBoxLayout" name="horizontalLayout_5">
+ <layout class="QVBoxLayout" name="verticalLayout_4">
<property name="spacing">
<number>3</number>
</property>
@@ -130,10 +203,20 @@
<number>3</number>
</property>
<item>
+ <widget class="QLabel" name="label_9">
+ <property name="text">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;L&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
<widget class="QPushButton" name="btnKeyL">
<property name="minimumSize">
<size>
- <width>72</width>
+ <width>70</width>
<height>0</height>
</size>
</property>
@@ -157,32 +240,6 @@
</layout>
</widget>
</item>
- <item row="1" column="2">
- <widget class="QLabel" name="label_3">
- <property name="text">
- <string/>
- </property>
- <property name="pixmap">
- <pixmap resource="resources/ds.qrc">:/ds/ds_back.svg</pixmap>
- </property>
- <property name="scaledContents">
- <bool>false</bool>
- </property>
- </widget>
- </item>
- <item row="2" column="2">
- <widget class="QLabel" name="label_2">
- <property name="text">
- <string/>
- </property>
- <property name="pixmap">
- <pixmap resource="resources/ds.qrc">:/ds/ds_open.svg</pixmap>
- </property>
- <property name="scaledContents">
- <bool>false</bool>
- </property>
- </widget>
- </item>
<item row="2" column="3">
<widget class="QGroupBox" name="grp_ABXY">
<property name="sizePolicy">
@@ -250,7 +307,7 @@
<item alignment="Qt::AlignHCenter">
<widget class="QGroupBox" name="grp_X">
<property name="title">
- <string>X</string>
+ <string/>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
@@ -272,10 +329,20 @@
<number>3</number>
</property>
<item>
+ <widget class="QLabel" name="label_13">
+ <property name="text">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;X&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
<widget class="QPushButton" name="btnKeyX">
<property name="minimumSize">
<size>
- <width>72</width>
+ <width>70</width>
<height>0</height>
</size>
</property>
@@ -320,7 +387,7 @@
<item alignment="Qt::AlignHCenter">
<widget class="QGroupBox" name="grp_Y">
<property name="title">
- <string>Y</string>
+ <string/>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
@@ -342,10 +409,20 @@
<number>3</number>
</property>
<item>
+ <widget class="QLabel" name="label_15">
+ <property name="text">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Y&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
<widget class="QPushButton" name="btnKeyY">
<property name="minimumSize">
<size>
- <width>72</width>
+ <width>70</width>
<height>0</height>
</size>
</property>
@@ -369,7 +446,7 @@
<item alignment="Qt::AlignHCenter">
<widget class="QGroupBox" name="grp_A">
<property name="title">
- <string>A</string>
+ <string/>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
@@ -391,10 +468,20 @@
<number>3</number>
</property>
<item>
+ <widget class="QLabel" name="label_16">
+ <property name="text">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;A&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
<widget class="QPushButton" name="btnKeyA">
<property name="minimumSize">
<size>
- <width>72</width>
+ <width>70</width>
<height>0</height>
</size>
</property>
@@ -451,7 +538,7 @@
<item alignment="Qt::AlignHCenter">
<widget class="QGroupBox" name="grp_B">
<property name="title">
- <string>B</string>
+ <string/>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
@@ -473,10 +560,20 @@
<number>3</number>
</property>
<item>
+ <widget class="QLabel" name="label_17">
+ <property name="text">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;B&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
<widget class="QPushButton" name="btnKeyB">
<property name="minimumSize">
<size>
- <width>72</width>
+ <width>70</width>
<height>0</height>
</size>
</property>
@@ -516,6 +613,131 @@
</layout>
</widget>
</item>
+ <item row="3" column="2">
+ <layout class="QHBoxLayout" name="layout_SelectStart">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="grp_Select">
+ <property name="title">
+ <string/>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="buttonDpadLeftVerticalLayout_2">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="label_18">
+ <property name="text">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Select&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="btnKeySelect">
+ <property name="minimumSize">
+ <size>
+ <width>70</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string>Select</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignHCenter">
+ <widget class="QGroupBox" name="grp_Start">
+ <property name="title">
+ <string/>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="buttonDpadRightVerticalLayout_2">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="label_19">
+ <property name="text">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Start&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="btnKeyStart">
+ <property name="minimumSize">
+ <size>
+ <width>70</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>68</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">min-width: 68px;</string>
+ </property>
+ <property name="text">
+ <string>Start</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </item>
<item row="2" column="1">
<widget class="QGroupBox" name="grp_ControlPad">
<property name="sizePolicy">
@@ -583,7 +805,7 @@
<item alignment="Qt::AlignHCenter">
<widget class="QGroupBox" name="grp_Up">
<property name="title">
- <string>Up</string>
+ <string/>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
@@ -605,10 +827,20 @@
<number>3</number>
</property>
<item>
+ <widget class="QLabel" name="label_10">
+ <property name="text">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Up&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
<widget class="QPushButton" name="btnKeyUp">
<property name="minimumSize">
<size>
- <width>72</width>
+ <width>70</width>
<height>0</height>
</size>
</property>
@@ -653,7 +885,7 @@
<item alignment="Qt::AlignHCenter">
<widget class="QGroupBox" name="grp_Left">
<property name="title">
- <string>Left</string>
+ <string/>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
@@ -675,10 +907,20 @@
<number>3</number>
</property>
<item>
+ <widget class="QLabel" name="label_11">
+ <property name="text">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Left&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
<widget class="QPushButton" name="btnKeyLeft">
<property name="minimumSize">
<size>
- <width>72</width>
+ <width>70</width>
<height>0</height>
</size>
</property>
@@ -702,7 +944,7 @@
<item alignment="Qt::AlignHCenter">
<widget class="QGroupBox" name="grp_Right">
<property name="title">
- <string>Right</string>
+ <string/>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
@@ -724,10 +966,20 @@
<number>3</number>
</property>
<item>
+ <widget class="QLabel" name="label_12">
+ <property name="text">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Right&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
<widget class="QPushButton" name="btnKeyRight">
<property name="minimumSize">
<size>
- <width>72</width>
+ <width>70</width>
<height>0</height>
</size>
</property>
@@ -784,7 +1036,7 @@
<item alignment="Qt::AlignHCenter">
<widget class="QGroupBox" name="grp_Down">
<property name="title">
- <string>Down</string>
+ <string/>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
@@ -806,10 +1058,20 @@
<number>3</number>
</property>
<item>
+ <widget class="QLabel" name="label_14">
+ <property name="text">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Down&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
<widget class="QPushButton" name="btnKeyDown">
<property name="minimumSize">
<size>
- <width>72</width>
+ <width>70</width>
<height>0</height>
</size>
</property>
@@ -849,6 +1111,32 @@
</layout>
</widget>
</item>
+ <item row="2" column="2">
+ <widget class="QLabel" name="label_2">
+ <property name="text">
+ <string/>
+ </property>
+ <property name="pixmap">
+ <pixmap resource="resources/ds.qrc">:/ds/ds_open.svg</pixmap>
+ </property>
+ <property name="scaledContents">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="2">
+ <widget class="QLabel" name="label_3">
+ <property name="text">
+ <string/>
+ </property>
+ <property name="pixmap">
+ <pixmap resource="resources/ds.qrc">:/ds/ds_back.svg</pixmap>
+ </property>
+ <property name="scaledContents">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
<item row="0" column="2">
<widget class="QLabel" name="label_7">
<property name="font">
@@ -864,10 +1152,14 @@
</property>
</widget>
</item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="joyPage">
+ <layout class="QGridLayout" name="gridLayout_3">
<item row="4" column="1" colspan="3">
- <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <layout class="QHBoxLayout" name="horizontalLayout_12">
<item>
- <spacer name="horizontalSpacer">
+ <spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
@@ -880,14 +1172,14 @@
</spacer>
</item>
<item>
- <widget class="QPushButton" name="btnJoyMapSwitch">
+ <widget class="QPushButton" name="btnKeyMapSwitch">
<property name="text">
- <string>Switch to Joystick mappings</string>
+ <string>Switch to Keyboard mappings</string>
</property>
</widget>
</item>
<item>
- <spacer name="horizontalSpacer_2">
+ <spacer name="horizontalSpacer_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
@@ -901,183 +1193,6 @@
</item>
</layout>
</item>
- <item row="3" column="2">
- <layout class="QHBoxLayout" name="layout_SelectStart">
- <property name="spacing">
- <number>3</number>
- </property>
- <item alignment="Qt::AlignHCenter">
- <widget class="QGroupBox" name="grp_Select">
- <property name="title">
- <string>Select</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignCenter</set>
- </property>
- <layout class="QVBoxLayout" name="buttonDpadLeftVerticalLayout_2">
- <property name="spacing">
- <number>3</number>
- </property>
- <property name="leftMargin">
- <number>3</number>
- </property>
- <property name="topMargin">
- <number>3</number>
- </property>
- <property name="rightMargin">
- <number>3</number>
- </property>
- <property name="bottomMargin">
- <number>3</number>
- </property>
- <item>
- <widget class="QPushButton" name="btnKeySelect">
- <property name="minimumSize">
- <size>
- <width>72</width>
- <height>0</height>
- </size>
- </property>
- <property name="maximumSize">
- <size>
- <width>68</width>
- <height>16777215</height>
- </size>
- </property>
- <property name="styleSheet">
- <string notr="true">min-width: 68px;</string>
- </property>
- <property name="text">
- <string>Select</string>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- <item alignment="Qt::AlignHCenter">
- <widget class="QGroupBox" name="grp_Start">
- <property name="title">
- <string>Start</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignCenter</set>
- </property>
- <layout class="QVBoxLayout" name="buttonDpadRightVerticalLayout_2">
- <property name="spacing">
- <number>3</number>
- </property>
- <property name="leftMargin">
- <number>3</number>
- </property>
- <property name="topMargin">
- <number>3</number>
- </property>
- <property name="rightMargin">
- <number>3</number>
- </property>
- <property name="bottomMargin">
- <number>3</number>
- </property>
- <item>
- <widget class="QPushButton" name="btnKeyStart">
- <property name="minimumSize">
- <size>
- <width>72</width>
- <height>0</height>
- </size>
- </property>
- <property name="maximumSize">
- <size>
- <width>68</width>
- <height>16777215</height>
- </size>
- </property>
- <property name="styleSheet">
- <string notr="true">min-width: 68px;</string>
- </property>
- <property name="text">
- <string>Start</string>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- </layout>
- </item>
- <item row="1" column="3">
- <widget class="QWidget" name="widget_4" native="true">
- <layout class="QHBoxLayout" name="horizontalLayout_15">
- <item>
- <widget class="QGroupBox" name="grp_R">
- <property name="title">
- <string>R</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignCenter</set>
- </property>
- <layout class="QHBoxLayout" name="horizontalLayout_4">
- <property name="spacing">
- <number>3</number>
- </property>
- <property name="leftMargin">
- <number>3</number>
- </property>
- <property name="topMargin">
- <number>3</number>
- </property>
- <property name="rightMargin">
- <number>3</number>
- </property>
- <property name="bottomMargin">
- <number>3</number>
- </property>
- <item>
- <widget class="QPushButton" name="btnKeyR">
- <property name="minimumSize">
- <size>
- <width>72</width>
- <height>0</height>
- </size>
- </property>
- <property name="maximumSize">
- <size>
- <width>68</width>
- <height>16777215</height>
- </size>
- </property>
- <property name="styleSheet">
- <string notr="true">min-width: 68px;</string>
- </property>
- <property name="text">
- <string>R</string>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- <item>
- <spacer name="horizontalSpacer_8">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>40</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- </layout>
- </widget>
- </item>
- </layout>
- </widget>
- <widget class="QWidget" name="joyPage">
- <layout class="QGridLayout" name="gridLayout_3">
<item row="1" column="1">
<widget class="QWidget" name="widget" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_2">
@@ -1097,12 +1212,12 @@
<item>
<widget class="QGroupBox" name="grp_L_2">
<property name="title">
- <string>L</string>
+ <string/>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
- <layout class="QHBoxLayout" name="horizontalLayout_9">
+ <layout class="QVBoxLayout" name="verticalLayout_7">
<property name="spacing">
<number>3</number>
</property>
@@ -1119,10 +1234,20 @@
<number>3</number>
</property>
<item>
+ <widget class="QLabel" name="label_20">
+ <property name="text">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;L&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
<widget class="QPushButton" name="btnJoyL">
<property name="minimumSize">
<size>
- <width>72</width>
+ <width>70</width>
<height>0</height>
</size>
</property>
@@ -1146,8 +1271,34 @@
</layout>
</widget>
</item>
- <item row="2" column="3">
- <widget class="QGroupBox" name="grp_ABXY_2">
+ <item row="1" column="2">
+ <widget class="QLabel" name="label_4">
+ <property name="text">
+ <string/>
+ </property>
+ <property name="pixmap">
+ <pixmap resource="resources/ds.qrc">:/ds/ds_back.svg</pixmap>
+ </property>
+ <property name="scaledContents">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="2">
+ <widget class="QLabel" name="label_5">
+ <property name="text">
+ <string/>
+ </property>
+ <property name="pixmap">
+ <pixmap resource="resources/ds.qrc">:/ds/ds_open.svg</pixmap>
+ </property>
+ <property name="scaledContents">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QGroupBox" name="grp_ControlPad_2">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
@@ -1163,7 +1314,7 @@
<property name="checkable">
<bool>false</bool>
</property>
- <layout class="QVBoxLayout" name="verticalLayout_3">
+ <layout class="QVBoxLayout" name="verticalLayout_6">
<property name="spacing">
<number>0</number>
</property>
@@ -1180,8 +1331,8 @@
<number>3</number>
</property>
<item>
- <widget class="QWidget" name="widget_X_2" native="true">
- <layout class="QHBoxLayout" name="horizontalLayout_7">
+ <widget class="QWidget" name="widget_Up_2" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_25">
<property name="spacing">
<number>0</number>
</property>
@@ -1198,7 +1349,7 @@
<number>0</number>
</property>
<item>
- <spacer name="spacer_9">
+ <spacer name="spacer_13">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
@@ -1211,14 +1362,14 @@
</spacer>
</item>
<item alignment="Qt::AlignHCenter">
- <widget class="QGroupBox" name="grp_X_2">
+ <widget class="QGroupBox" name="grp_Up_2">
<property name="title">
- <string>X</string>
+ <string/>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
- <layout class="QVBoxLayout" name="buttonFaceButtonsXVerticalLayout_2">
+ <layout class="QVBoxLayout" name="buttonDpadUpVerticalLayout_2">
<property name="spacing">
<number>3</number>
</property>
@@ -1235,10 +1386,20 @@
<number>3</number>
</property>
<item>
- <widget class="QPushButton" name="btnJoyX">
+ <widget class="QLabel" name="label_23">
+ <property name="text">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Up&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="btnJoyUp">
<property name="minimumSize">
<size>
- <width>72</width>
+ <width>70</width>
<height>0</height>
</size>
</property>
@@ -1252,7 +1413,7 @@
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
- <string>X</string>
+ <string>Up</string>
</property>
</widget>
</item>
@@ -1260,7 +1421,7 @@
</widget>
</item>
<item>
- <spacer name="spacer_10">
+ <spacer name="spacer_14">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
@@ -1276,19 +1437,19 @@
</widget>
</item>
<item>
- <layout class="QHBoxLayout" name="layout_YA_2">
+ <layout class="QHBoxLayout" name="layout_LeftRight_2">
<property name="spacing">
<number>3</number>
</property>
<item alignment="Qt::AlignHCenter">
- <widget class="QGroupBox" name="grp_Y_2">
+ <widget class="QGroupBox" name="grp_Left_2">
<property name="title">
- <string>Y</string>
+ <string/>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
- <layout class="QVBoxLayout" name="buttonFaceButtonsYVerticalLayout_2">
+ <layout class="QVBoxLayout" name="buttonDpadLeftVerticalLayout_4">
<property name="spacing">
<number>3</number>
</property>
@@ -1305,10 +1466,20 @@
<number>3</number>
</property>
<item>
- <widget class="QPushButton" name="btnJoyY">
+ <widget class="QLabel" name="label_22">
+ <property name="text">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Left&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="btnJoyLeft">
<property name="minimumSize">
<size>
- <width>72</width>
+ <width>70</width>
<height>0</height>
</size>
</property>
@@ -1322,7 +1493,7 @@
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
- <string>Y</string>
+ <string>Left</string>
</property>
</widget>
</item>
@@ -1330,14 +1501,14 @@
</widget>
</item>
<item alignment="Qt::AlignHCenter">
- <widget class="QGroupBox" name="grp_A_2">
+ <widget class="QGroupBox" name="grp_Right_2">
<property name="title">
- <string>A</string>
+ <string/>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
- <layout class="QVBoxLayout" name="buttonFaceButtonsAVerticalLayout_2">
+ <layout class="QVBoxLayout" name="buttonDpadRightVerticalLayout_4">
<property name="spacing">
<number>3</number>
</property>
@@ -1354,10 +1525,20 @@
<number>3</number>
</property>
<item>
- <widget class="QPushButton" name="btnJoyA">
+ <widget class="QLabel" name="label_30">
+ <property name="text">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Right&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="btnJoyRight">
<property name="minimumSize">
<size>
- <width>72</width>
+ <width>70</width>
<height>0</height>
</size>
</property>
@@ -1371,7 +1552,7 @@
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
- <string>A</string>
+ <string>Right</string>
</property>
</widget>
</item>
@@ -1381,8 +1562,8 @@
</layout>
</item>
<item>
- <widget class="QWidget" name="widget_B_2" native="true">
- <layout class="QHBoxLayout" name="horizontalLayout_11">
+ <widget class="QWidget" name="widget_Down_2" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_26">
<property name="spacing">
<number>0</number>
</property>
@@ -1399,7 +1580,7 @@
<number>0</number>
</property>
<item>
- <spacer name="spacer_11">
+ <spacer name="spacer_15">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
@@ -1412,14 +1593,14 @@
</spacer>
</item>
<item alignment="Qt::AlignHCenter">
- <widget class="QGroupBox" name="grp_B_2">
+ <widget class="QGroupBox" name="grp_Down_2">
<property name="title">
- <string>B</string>
+ <string/>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
- <layout class="QVBoxLayout" name="buttonFaceButtonsBVerticalLayout_2">
+ <layout class="QVBoxLayout" name="buttonDpadDownVerticalLayout_2">
<property name="spacing">
<number>3</number>
</property>
@@ -1436,10 +1617,20 @@
<number>3</number>
</property>
<item>
- <widget class="QPushButton" name="btnJoyB">
+ <widget class="QLabel" name="label_31">
+ <property name="text">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Down&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="btnJoyDown">
<property name="minimumSize">
<size>
- <width>72</width>
+ <width>70</width>
<height>0</height>
</size>
</property>
@@ -1453,7 +1644,7 @@
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
- <string>B</string>
+ <string>Down</string>
</property>
</widget>
</item>
@@ -1461,7 +1652,7 @@
</widget>
</item>
<item>
- <spacer name="spacer_12">
+ <spacer name="spacer_16">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
@@ -1479,8 +1670,8 @@
</layout>
</widget>
</item>
- <item row="2" column="1">
- <widget class="QGroupBox" name="grp_ControlPad_2">
+ <item row="2" column="3">
+ <widget class="QGroupBox" name="grp_ABXY_2">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
@@ -1496,7 +1687,7 @@
<property name="checkable">
<bool>false</bool>
</property>
- <layout class="QVBoxLayout" name="verticalLayout_6">
+ <layout class="QVBoxLayout" name="verticalLayout_3">
<property name="spacing">
<number>0</number>
</property>
@@ -1513,8 +1704,8 @@
<number>3</number>
</property>
<item>
- <widget class="QWidget" name="widget_Up_2" native="true">
- <layout class="QHBoxLayout" name="horizontalLayout_25">
+ <widget class="QWidget" name="widget_X_2" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_7">
<property name="spacing">
<number>0</number>
</property>
@@ -1531,7 +1722,7 @@
<number>0</number>
</property>
<item>
- <spacer name="spacer_13">
+ <spacer name="spacer_9">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
@@ -1544,14 +1735,14 @@
</spacer>
</item>
<item alignment="Qt::AlignHCenter">
- <widget class="QGroupBox" name="grp_Up_2">
+ <widget class="QGroupBox" name="grp_X_2">
<property name="title">
- <string>Up</string>
+ <string/>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
- <layout class="QVBoxLayout" name="buttonDpadUpVerticalLayout_2">
+ <layout class="QVBoxLayout" name="buttonFaceButtonsXVerticalLayout_2">
<property name="spacing">
<number>3</number>
</property>
@@ -1568,10 +1759,20 @@
<number>3</number>
</property>
<item>
- <widget class="QPushButton" name="btnJoyUp">
+ <widget class="QLabel" name="label_24">
+ <property name="text">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;X&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="btnJoyX">
<property name="minimumSize">
<size>
- <width>72</width>
+ <width>70</width>
<height>0</height>
</size>
</property>
@@ -1585,7 +1786,7 @@
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
- <string>Up</string>
+ <string>X</string>
</property>
</widget>
</item>
@@ -1593,7 +1794,7 @@
</widget>
</item>
<item>
- <spacer name="spacer_14">
+ <spacer name="spacer_10">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
@@ -1609,19 +1810,19 @@
</widget>
</item>
<item>
- <layout class="QHBoxLayout" name="layout_LeftRight_2">
+ <layout class="QHBoxLayout" name="layout_YA_2">
<property name="spacing">
<number>3</number>
</property>
<item alignment="Qt::AlignHCenter">
- <widget class="QGroupBox" name="grp_Left_2">
+ <widget class="QGroupBox" name="grp_Y_2">
<property name="title">
- <string>Left</string>
+ <string/>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
- <layout class="QVBoxLayout" name="buttonDpadLeftVerticalLayout_4">
+ <layout class="QVBoxLayout" name="buttonFaceButtonsYVerticalLayout_2">
<property name="spacing">
<number>3</number>
</property>
@@ -1638,10 +1839,20 @@
<number>3</number>
</property>
<item>
- <widget class="QPushButton" name="btnJoyLeft">
+ <widget class="QLabel" name="label_25">
+ <property name="text">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Y&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="btnJoyY">
<property name="minimumSize">
<size>
- <width>72</width>
+ <width>70</width>
<height>0</height>
</size>
</property>
@@ -1655,7 +1866,7 @@
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
- <string>Left</string>
+ <string>Y</string>
</property>
</widget>
</item>
@@ -1663,14 +1874,14 @@
</widget>
</item>
<item alignment="Qt::AlignHCenter">
- <widget class="QGroupBox" name="grp_Right_2">
+ <widget class="QGroupBox" name="grp_A_2">
<property name="title">
- <string>Right</string>
+ <string/>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
- <layout class="QVBoxLayout" name="buttonDpadRightVerticalLayout_4">
+ <layout class="QVBoxLayout" name="buttonFaceButtonsAVerticalLayout_2">
<property name="spacing">
<number>3</number>
</property>
@@ -1687,10 +1898,20 @@
<number>3</number>
</property>
<item>
- <widget class="QPushButton" name="btnJoyRight">
+ <widget class="QLabel" name="label_26">
+ <property name="text">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;A&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="btnJoyA">
<property name="minimumSize">
<size>
- <width>72</width>
+ <width>70</width>
<height>0</height>
</size>
</property>
@@ -1704,7 +1925,7 @@
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
- <string>Right</string>
+ <string>A</string>
</property>
</widget>
</item>
@@ -1714,8 +1935,8 @@
</layout>
</item>
<item>
- <widget class="QWidget" name="widget_Down_2" native="true">
- <layout class="QHBoxLayout" name="horizontalLayout_26">
+ <widget class="QWidget" name="widget_B_2" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_11">
<property name="spacing">
<number>0</number>
</property>
@@ -1732,7 +1953,7 @@
<number>0</number>
</property>
<item>
- <spacer name="spacer_15">
+ <spacer name="spacer_11">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
@@ -1745,14 +1966,14 @@
</spacer>
</item>
<item alignment="Qt::AlignHCenter">
- <widget class="QGroupBox" name="grp_Down_2">
+ <widget class="QGroupBox" name="grp_B_2">
<property name="title">
- <string>Down</string>
+ <string/>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
- <layout class="QVBoxLayout" name="buttonDpadDownVerticalLayout_2">
+ <layout class="QVBoxLayout" name="buttonFaceButtonsBVerticalLayout_2">
<property name="spacing">
<number>3</number>
</property>
@@ -1769,10 +1990,20 @@
<number>3</number>
</property>
<item>
- <widget class="QPushButton" name="btnJoyDown">
+ <widget class="QLabel" name="label_27">
+ <property name="text">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;B&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="btnJoyB">
<property name="minimumSize">
<size>
- <width>72</width>
+ <width>70</width>
<height>0</height>
</size>
</property>
@@ -1786,7 +2017,7 @@
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
- <string>Down</string>
+ <string>B</string>
</property>
</widget>
</item>
@@ -1794,7 +2025,7 @@
</widget>
</item>
<item>
- <spacer name="spacer_16">
+ <spacer name="spacer_12">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
@@ -1812,84 +2043,6 @@
</layout>
</widget>
</item>
- <item row="2" column="2">
- <widget class="QLabel" name="label_5">
- <property name="text">
- <string/>
- </property>
- <property name="pixmap">
- <pixmap resource="resources/ds.qrc">:/ds/ds_open.svg</pixmap>
- </property>
- <property name="scaledContents">
- <bool>false</bool>
- </property>
- </widget>
- </item>
- <item row="0" column="2">
- <widget class="QLabel" name="label_6">
- <property name="font">
- <font>
- <pointsize>15</pointsize>
- </font>
- </property>
- <property name="text">
- <string>Joystick mappings</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignCenter</set>
- </property>
- </widget>
- </item>
- <item row="1" column="2">
- <widget class="QLabel" name="label_4">
- <property name="text">
- <string/>
- </property>
- <property name="pixmap">
- <pixmap resource="resources/ds.qrc">:/ds/ds_back.svg</pixmap>
- </property>
- <property name="scaledContents">
- <bool>false</bool>
- </property>
- </widget>
- </item>
- <item row="4" column="1" colspan="3">
- <layout class="QHBoxLayout" name="horizontalLayout_12">
- <item>
- <spacer name="horizontalSpacer_3">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>40</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- <item>
- <widget class="QPushButton" name="btnKeyMapSwitch">
- <property name="text">
- <string>Switch to Keyboard mappings</string>
- </property>
- </widget>
- </item>
- <item>
- <spacer name="horizontalSpacer_4">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>40</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- </layout>
- </item>
<item row="3" column="2">
<layout class="QHBoxLayout" name="layout_SelectStart_2">
<property name="spacing">
@@ -1898,7 +2051,7 @@
<item alignment="Qt::AlignHCenter">
<widget class="QGroupBox" name="grp_Select_2">
<property name="title">
- <string>Select</string>
+ <string/>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
@@ -1920,10 +2073,20 @@
<number>3</number>
</property>
<item>
+ <widget class="QLabel" name="label_28">
+ <property name="text">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Select&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
<widget class="QPushButton" name="btnJoySelect">
<property name="minimumSize">
<size>
- <width>72</width>
+ <width>70</width>
<height>0</height>
</size>
</property>
@@ -1947,7 +2110,7 @@
<item alignment="Qt::AlignHCenter">
<widget class="QGroupBox" name="grp_Start_2">
<property name="title">
- <string>Start</string>
+ <string/>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
@@ -1969,10 +2132,20 @@
<number>3</number>
</property>
<item>
+ <widget class="QLabel" name="label_29">
+ <property name="text">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Start&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
<widget class="QPushButton" name="btnJoyStart">
<property name="minimumSize">
<size>
- <width>72</width>
+ <width>70</width>
<height>0</height>
</size>
</property>
@@ -2001,12 +2174,12 @@
<item>
<widget class="QGroupBox" name="grp_R_2">
<property name="title">
- <string>R</string>
+ <string/>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
- <layout class="QHBoxLayout" name="horizontalLayout_8">
+ <layout class="QVBoxLayout" name="verticalLayout_8">
<property name="spacing">
<number>3</number>
</property>
@@ -2023,10 +2196,20 @@
<number>3</number>
</property>
<item>
+ <widget class="QLabel" name="label_21">
+ <property name="text">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;R&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
<widget class="QPushButton" name="btnJoyR">
<property name="minimumSize">
<size>
- <width>72</width>
+ <width>70</width>
<height>0</height>
</size>
</property>
@@ -2063,6 +2246,21 @@
</layout>
</widget>
</item>
+ <item row="0" column="2">
+ <widget class="QLabel" name="label_6">
+ <property name="font">
+ <font>
+ <pointsize>15</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string>Joystick mappings</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
</layout>
</widget>
</widget>
@@ -2081,6 +2279,55 @@
</widget>
</widget>
</item>
+ <item row="7" column="0" colspan="2">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="label">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Joystick:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="cbxJoystick">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="whatsThis">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Selects which joystick will be used for joystick input, if any is present.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="lblInstanceNum">
+ <property name="text">
+ <string>Configuring mappings for instance X</string>
+ </property>
+ </widget>
+ </item>
</layout>
</widget>
<resources>
diff --git a/src/frontend/qt_sdl/InputConfig/MapButton.h b/src/frontend/qt_sdl/InputConfig/MapButton.h
index a1b1a16..afefed7 100644
--- a/src/frontend/qt_sdl/InputConfig/MapButton.h
+++ b/src/frontend/qt_sdl/InputConfig/MapButton.h
@@ -1,5 +1,5 @@
/*
- Copyright 2016-2021 Arisotura
+ Copyright 2016-2022 melonDS team
This file is part of melonDS.
@@ -163,6 +163,7 @@ public:
setCheckable(true);
setText(mappingText());
+ setFocusPolicy(Qt::StrongFocus); //Fixes binding keys in macOS
connect(this, &JoyMapButton::clicked, this, &JoyMapButton::onClick);
@@ -245,19 +246,21 @@ protected:
Sint16 axisval = SDL_JoystickGetAxis(joy, i);
int diff = abs(axisval - axesRest[i]);
- if (axesRest[i] < -16384 && axisval >= 0)
- {
- *mapping = (oldmap & 0xFFFF) | 0x10000 | (2 << 20) | (i << 24);
- click();
- return;
- }
- else if (diff > 16384)
+ if (diff >= 16384)
{
- int axistype;
- if (axisval > 0) axistype = 0;
- else axistype = 1;
+ if (axesRest[i] < -16384) // Trigger
+ {
+ *mapping = (oldmap & 0xFFFF) | 0x10000 | (2 << 20) | (i << 24);
+ }
+ else // Analog stick
+ {
+ int axistype;
+ if (axisval > 0) axistype = 0;
+ else axistype = 1;
+
+ *mapping = (oldmap & 0xFFFF) | 0x10000 | (axistype << 20) | (i << 24);
+ }
- *mapping = (oldmap & 0xFFFF) | 0x10000 | (axistype << 20) | (i << 24);
click();
return;
}
@@ -352,4 +355,4 @@ private:
int axesRest[16];
};
-#endif // MAPBUTTON_H \ No newline at end of file
+#endif // MAPBUTTON_H
diff --git a/src/frontend/qt_sdl/InterfaceSettingsDialog.cpp b/src/frontend/qt_sdl/InterfaceSettingsDialog.cpp
index 788e596..7c5eae6 100644
--- a/src/frontend/qt_sdl/InterfaceSettingsDialog.cpp
+++ b/src/frontend/qt_sdl/InterfaceSettingsDialog.cpp
@@ -1,5 +1,5 @@
/*
- Copyright 2016-2021 Arisotura, WaluigiWare64
+ Copyright 2016-2022 melonDS team
This file is part of melonDS.
diff --git a/src/frontend/qt_sdl/InterfaceSettingsDialog.h b/src/frontend/qt_sdl/InterfaceSettingsDialog.h
index ae21e3f..114aa04 100644
--- a/src/frontend/qt_sdl/InterfaceSettingsDialog.h
+++ b/src/frontend/qt_sdl/InterfaceSettingsDialog.h
@@ -1,5 +1,5 @@
/*
- Copyright 2016-2021 Arisotura, WaluigiWare64
+ Copyright 2016-2022 melonDS team
This file is part of melonDS.
diff --git a/src/frontend/qt_sdl/LAN_PCap.cpp b/src/frontend/qt_sdl/LAN_PCap.cpp
index ed3eee9..86c218f 100644
--- a/src/frontend/qt_sdl/LAN_PCap.cpp
+++ b/src/frontend/qt_sdl/LAN_PCap.cpp
@@ -1,5 +1,5 @@
/*
- Copyright 2016-2021 Arisotura
+ Copyright 2016-2022 melonDS team
This file is part of melonDS.
@@ -114,6 +114,12 @@ bool TryLoadPCap(void* lib)
bool Init(bool open_adapter)
{
+ PCapAdapter = NULL;
+ PacketLen = 0;
+ RXNum = 0;
+
+ NumAdapters = 0;
+
// TODO: how to deal with cases where an adapter is unplugged or changes config??
if (!PCapLib)
{
@@ -142,12 +148,6 @@ bool Init(bool open_adapter)
}
}
- PCapAdapter = NULL;
- PacketLen = 0;
- RXNum = 0;
-
- NumAdapters = 0;
-
char errbuf[PCAP_ERRBUF_SIZE];
int ret;
@@ -318,7 +318,7 @@ bool Init(bool open_adapter)
PCapAdapterData = &Adapters[0];
for (int i = 0; i < NumAdapters; i++)
{
- if (!strncmp(Adapters[i].DeviceName, Config::LANDevice, 128))
+ if (!strncmp(Adapters[i].DeviceName, Config::LANDevice.c_str(), 128))
PCapAdapterData = &Adapters[i];
}
diff --git a/src/frontend/qt_sdl/LAN_PCap.h b/src/frontend/qt_sdl/LAN_PCap.h
index 8e9ad9f..610c0ae 100644
--- a/src/frontend/qt_sdl/LAN_PCap.h
+++ b/src/frontend/qt_sdl/LAN_PCap.h
@@ -1,5 +1,5 @@
/*
- Copyright 2016-2021 Arisotura
+ Copyright 2016-2022 melonDS team
This file is part of melonDS.
diff --git a/src/frontend/qt_sdl/LAN_Socket.cpp b/src/frontend/qt_sdl/LAN_Socket.cpp
index 83ddd99..6c00b58 100644
--- a/src/frontend/qt_sdl/LAN_Socket.cpp
+++ b/src/frontend/qt_sdl/LAN_Socket.cpp
@@ -1,5 +1,5 @@
/*
- Copyright 2016-2021 Arisotura
+ Copyright 2016-2022 melonDS team
This file is part of melonDS.
diff --git a/src/frontend/qt_sdl/LAN_Socket.h b/src/frontend/qt_sdl/LAN_Socket.h
index c6afc08..2073d1b 100644
--- a/src/frontend/qt_sdl/LAN_Socket.h
+++ b/src/frontend/qt_sdl/LAN_Socket.h
@@ -1,5 +1,5 @@
/*
- Copyright 2016-2021 Arisotura
+ Copyright 2016-2022 melonDS team
This file is part of melonDS.
diff --git a/src/frontend/qt_sdl/LocalMP.cpp b/src/frontend/qt_sdl/LocalMP.cpp
new file mode 100644
index 0000000..fb7ef7a
--- /dev/null
+++ b/src/frontend/qt_sdl/LocalMP.cpp
@@ -0,0 +1,634 @@
+/*
+ Copyright 2016-2022 melonDS team
+
+ This file is part of melonDS.
+
+ melonDS 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 3 of the License, or (at your option)
+ any later version.
+
+ melonDS 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 melonDS. If not, see http://www.gnu.org/licenses/.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#ifdef __WIN32__
+ #include <windows.h>
+#else
+ #include <fcntl.h>
+ #include <semaphore.h>
+ #include <time.h>
+ #ifdef __APPLE__
+ #include "sem_timedwait.h"
+ #endif
+#endif
+
+#include <string>
+#include <QSharedMemory>
+
+#include "Config.h"
+#include "LocalMP.h"
+
+
+namespace LocalMP
+{
+
+u32 MPUniqueID;
+u8 PacketBuffer[2048];
+
+struct MPQueueHeader
+{
+ u16 NumInstances;
+ u16 InstanceBitmask; // bitmask of all instances present
+ u16 ConnectedBitmask; // bitmask of which instances are ready to send/receive packets
+ u32 PacketWriteOffset;
+ u32 ReplyWriteOffset;
+ u16 MPHostInstanceID; // instance ID from which the last CMD frame was sent
+ u16 MPReplyBitmask; // bitmask of which clients replied in time
+};
+
+struct MPPacketHeader
+{
+ u32 Magic;
+ u32 SenderID;
+ u32 Type; // 0=regular 1=CMD 2=reply 3=ack
+ u32 Length;
+ u64 Timestamp;
+};
+
+struct MPSync
+{
+ u32 Magic;
+ u32 SenderID;
+ u16 ClientMask;
+ u16 Type;
+ u64 Timestamp;
+};
+
+QSharedMemory* MPQueue;
+int InstanceID;
+u32 PacketReadOffset;
+u32 ReplyReadOffset;
+
+const u32 kQueueSize = 0x20000;
+const u32 kMaxFrameSize = 0x800;
+const u32 kPacketStart = sizeof(MPQueueHeader);
+const u32 kReplyStart = kQueueSize / 2;
+const u32 kPacketEnd = kReplyStart;
+const u32 kReplyEnd = kQueueSize;
+
+int RecvTimeout;
+
+int LastHostID;
+
+
+// we need to come up with our own abstraction layer for named semaphores
+// because QSystemSemaphore doesn't support waiting with a timeout
+// and, as such, is unsuitable to our needs
+
+#ifdef __WIN32__
+
+bool SemInited[32];
+HANDLE SemPool[32];
+
+void SemPoolInit()
+{
+ for (int i = 0; i < 32; i++)
+ {
+ SemPool[i] = INVALID_HANDLE_VALUE;
+ SemInited[i] = false;
+ }
+}
+
+void SemDeinit(int num);
+
+void SemPoolDeinit()
+{
+ for (int i = 0; i < 32; i++)
+ SemDeinit(i);
+}
+
+bool SemInit(int num)
+{
+ if (SemInited[num])
+ return true;
+
+ char semname[64];
+ sprintf(semname, "Local\\melonNIFI_Sem%02d", num);
+
+ HANDLE sem = CreateSemaphore(nullptr, 0, 64, semname);
+ SemPool[num] = sem;
+ SemInited[num] = true;
+ return sem != INVALID_HANDLE_VALUE;
+}
+
+void SemDeinit(int num)
+{
+ if (SemPool[num] != INVALID_HANDLE_VALUE)
+ {
+ CloseHandle(SemPool[num]);
+ SemPool[num] = INVALID_HANDLE_VALUE;
+ }
+
+ SemInited[num] = false;
+}
+
+bool SemPost(int num)
+{
+ SemInit(num);
+ return ReleaseSemaphore(SemPool[num], 1, nullptr) != 0;
+}
+
+bool SemWait(int num, int timeout)
+{
+ return WaitForSingleObject(SemPool[num], timeout) == WAIT_OBJECT_0;
+}
+
+void SemReset(int num)
+{
+ while (WaitForSingleObject(SemPool[num], 0) == WAIT_OBJECT_0);
+}
+
+#else
+
+bool SemInited[32];
+sem_t* SemPool[32];
+
+void SemPoolInit()
+{
+ for (int i = 0; i < 32; i++)
+ {
+ SemPool[i] = SEM_FAILED;
+ SemInited[i] = false;
+ }
+}
+
+void SemDeinit(int num);
+
+void SemPoolDeinit()
+{
+ for (int i = 0; i < 32; i++)
+ SemDeinit(i);
+}
+
+bool SemInit(int num)
+{
+ if (SemInited[num])
+ return true;
+
+ char semname[64];
+ sprintf(semname, "/melonNIFI_Sem%02d", num);
+
+ sem_t* sem = sem_open(semname, O_CREAT, 0644, 0);
+ SemPool[num] = sem;
+ SemInited[num] = true;
+ return sem != SEM_FAILED;
+}
+
+void SemDeinit(int num)
+{
+ if (SemPool[num] != SEM_FAILED)
+ {
+ sem_close(SemPool[num]);
+ SemPool[num] = SEM_FAILED;
+ }
+
+ SemInited[num] = false;
+}
+
+bool SemPost(int num)
+{
+ SemInit(num);
+ return sem_post(SemPool[num]) == 0;
+}
+
+bool SemWait(int num, int timeout)
+{
+ if (!timeout)
+ return sem_trywait(SemPool[num]) == 0;
+
+ struct timespec ts;
+ clock_gettime(CLOCK_REALTIME, &ts);
+ ts.tv_nsec += timeout * 1000000;
+ long sec = ts.tv_nsec / 1000000000;
+ ts.tv_nsec -= sec * 1000000000;
+ ts.tv_sec += sec;
+
+ return sem_timedwait(SemPool[num], &ts) == 0;
+}
+
+void SemReset(int num)
+{
+ while (sem_trywait(SemPool[num]) == 0);
+}
+
+#endif
+
+
+bool Init()
+{
+ MPQueue = new QSharedMemory("melonNIFI");
+
+ if (!MPQueue->attach())
+ {
+ printf("MP sharedmem doesn't exist. creating\n");
+ if (!MPQueue->create(kQueueSize))
+ {
+ printf("MP sharedmem create failed :(\n");
+ return false;
+ }
+
+ MPQueue->lock();
+ memset(MPQueue->data(), 0, MPQueue->size());
+ MPQueueHeader* header = (MPQueueHeader*)MPQueue->data();
+ header->PacketWriteOffset = kPacketStart;
+ header->ReplyWriteOffset = kReplyStart;
+ MPQueue->unlock();
+ }
+
+ MPQueue->lock();
+ MPQueueHeader* header = (MPQueueHeader*)MPQueue->data();
+
+ u16 mask = header->InstanceBitmask;
+ for (int i = 0; i < 16; i++)
+ {
+ if (!(mask & (1<<i)))
+ {
+ InstanceID = i;
+ header->InstanceBitmask |= (1<<i);
+ //header->ConnectedBitmask |= (1 << i);
+ break;
+ }
+ }
+ header->NumInstances++;
+
+ PacketReadOffset = header->PacketWriteOffset;
+ ReplyReadOffset = header->ReplyWriteOffset;
+
+ MPQueue->unlock();
+
+ // prepare semaphores
+ // semaphores 0-15: regular frames; semaphore I is posted when instance I needs to process a new frame
+ // semaphores 16-31: MP replies; semaphore I is posted when instance I needs to process a new MP reply
+
+ SemPoolInit();
+ SemInit(InstanceID);
+ SemInit(16+InstanceID);
+
+ LastHostID = -1;
+
+ printf("MP comm init OK, instance ID %d\n", InstanceID);
+
+ RecvTimeout = 25;
+
+ return true;
+}
+
+void DeInit()
+{
+ MPQueue->lock();
+ MPQueueHeader* header = (MPQueueHeader*)MPQueue->data();
+ header->ConnectedBitmask &= ~(1 << InstanceID);
+ header->InstanceBitmask &= ~(1 << InstanceID);
+ header->NumInstances--;
+ MPQueue->unlock();
+
+ SemPoolDeinit();
+
+ MPQueue->detach();
+ delete MPQueue;
+}
+
+void SetRecvTimeout(int timeout)
+{
+ RecvTimeout = timeout;
+}
+
+void Begin()
+{
+ MPQueue->lock();
+ MPQueueHeader* header = (MPQueueHeader*)MPQueue->data();
+ PacketReadOffset = header->PacketWriteOffset;
+ ReplyReadOffset = header->ReplyWriteOffset;
+ SemReset(InstanceID);
+ SemReset(16+InstanceID);
+ header->ConnectedBitmask |= (1 << InstanceID);
+ MPQueue->unlock();
+}
+
+void End()
+{
+ MPQueue->lock();
+ MPQueueHeader* header = (MPQueueHeader*)MPQueue->data();
+ //SemReset(InstanceID);
+ //SemReset(16+InstanceID);
+ header->ConnectedBitmask &= ~(1 << InstanceID);
+ MPQueue->unlock();
+}
+
+void FIFORead(int fifo, void* buf, int len)
+{
+ u8* data = (u8*)MPQueue->data();
+
+ u32 offset, start, end;
+ if (fifo == 0)
+ {
+ offset = PacketReadOffset;
+ start = kPacketStart;
+ end = kPacketEnd;
+ }
+ else
+ {
+ offset = ReplyReadOffset;
+ start = kReplyStart;
+ end = kReplyEnd;
+ }
+
+ if ((offset + len) >= end)
+ {
+ u32 part1 = end - offset;
+ memcpy(buf, &data[offset], part1);
+ memcpy(&((u8*)buf)[part1], &data[start], len - part1);
+ offset = start + len - part1;
+ }
+ else
+ {
+ memcpy(buf, &data[offset], len);
+ offset += len;
+ }
+
+ if (fifo == 0) PacketReadOffset = offset;
+ else ReplyReadOffset = offset;
+}
+
+void FIFOWrite(int fifo, void* buf, int len)
+{
+ u8* data = (u8*)MPQueue->data();
+ MPQueueHeader* header = (MPQueueHeader*)&data[0];
+
+ u32 offset, start, end;
+ if (fifo == 0)
+ {
+ offset = header->PacketWriteOffset;
+ start = kPacketStart;
+ end = kPacketEnd;
+ }
+ else
+ {
+ offset = header->ReplyWriteOffset;
+ start = kReplyStart;
+ end = kReplyEnd;
+ }
+
+ if ((offset + len) >= end)
+ {
+ u32 part1 = end - offset;
+ memcpy(&data[offset], buf, part1);
+ memcpy(&data[start], &((u8*)buf)[part1], len - part1);
+ offset = start + len - part1;
+ }
+ else
+ {
+ memcpy(&data[offset], buf, len);
+ offset += len;
+ }
+
+ if (fifo == 0) header->PacketWriteOffset = offset;
+ else header->ReplyWriteOffset = offset;
+}
+
+int SendPacketGeneric(u32 type, u8* packet, int len, u64 timestamp)
+{
+ MPQueue->lock();
+ u8* data = (u8*)MPQueue->data();
+ MPQueueHeader* header = (MPQueueHeader*)&data[0];
+
+ u16 mask = header->ConnectedBitmask;
+
+ // TODO: check if the FIFO is full!
+
+ MPPacketHeader pktheader;
+ pktheader.Magic = 0x4946494E;
+ pktheader.SenderID = InstanceID;
+ pktheader.Type = type;
+ pktheader.Length = len;
+ pktheader.Timestamp = timestamp;
+
+ type &= 0xFFFF;
+ int nfifo = (type == 2) ? 1 : 0;
+ FIFOWrite(nfifo, &pktheader, sizeof(pktheader));
+ if (len)
+ FIFOWrite(nfifo, packet, len);
+
+ if (type == 1)
+ {
+ // NOTE: this is not guarded against, say, multiple multiplay games happening on the same machine
+ // we would need to pass the packet's SenderID through the wifi module for that
+ header->MPHostInstanceID = InstanceID;
+ header->MPReplyBitmask = 0;
+ ReplyReadOffset = header->ReplyWriteOffset;
+ SemReset(16 + InstanceID);
+ }
+ else if (type == 2)
+ {
+ header->MPReplyBitmask |= (1 << InstanceID);
+ }
+
+ MPQueue->unlock();
+
+ if (type == 2)
+ {
+ SemPost(16 + header->MPHostInstanceID);
+ }
+ else
+ {
+ for (int i = 0; i < 16; i++)
+ {
+ if (mask & (1<<i))
+ SemPost(i);
+ }
+ }
+
+ return len;
+}
+
+int RecvPacketGeneric(u8* packet, bool block, u64* timestamp)
+{
+ for (;;)
+ {
+ if (!SemWait(InstanceID, block ? RecvTimeout : 0))
+ {
+ return 0;
+ }
+
+ MPQueue->lock();
+ u8* data = (u8*)MPQueue->data();
+ MPQueueHeader* header = (MPQueueHeader*)&data[0];
+
+ MPPacketHeader pktheader;
+ FIFORead(0, &pktheader, sizeof(pktheader));
+
+ if (pktheader.Magic != 0x4946494E)
+ {
+ printf("PACKET FIFO OVERFLOW\n");
+ PacketReadOffset = header->PacketWriteOffset;
+ SemReset(InstanceID);
+ MPQueue->unlock();
+ return 0;
+ }
+
+ if (pktheader.SenderID == InstanceID)
+ {
+ // skip this packet
+ PacketReadOffset += pktheader.Length;
+ if (PacketReadOffset >= kPacketEnd)
+ PacketReadOffset += kPacketStart - kPacketEnd;
+
+ MPQueue->unlock();
+ continue;
+ }
+
+ if (pktheader.Length)
+ {
+ FIFORead(0, packet, pktheader.Length);
+
+ if (pktheader.Type == 1)
+ LastHostID = pktheader.SenderID;
+ }
+
+ if (timestamp) *timestamp = pktheader.Timestamp;
+ MPQueue->unlock();
+ return pktheader.Length;
+ }
+}
+
+int SendPacket(u8* packet, int len, u64 timestamp)
+{
+ return SendPacketGeneric(0, packet, len, timestamp);
+}
+
+int RecvPacket(u8* packet, u64* timestamp)
+{
+ return RecvPacketGeneric(packet, false, timestamp);
+}
+
+
+int SendCmd(u8* packet, int len, u64 timestamp)
+{
+ return SendPacketGeneric(1, packet, len, timestamp);
+}
+
+int SendReply(u8* packet, int len, u64 timestamp, u16 aid)
+{
+ return SendPacketGeneric(2 | (aid<<16), packet, len, timestamp);
+}
+
+int SendAck(u8* packet, int len, u64 timestamp)
+{
+ return SendPacketGeneric(3, packet, len, timestamp);
+}
+
+int RecvHostPacket(u8* packet, u64* timestamp)
+{
+ if (LastHostID != -1)
+ {
+ // check if the host is still connected
+
+ MPQueue->lock();
+ u8* data = (u8*)MPQueue->data();
+ MPQueueHeader* header = (MPQueueHeader*)&data[0];
+ u16 curinstmask = header->ConnectedBitmask;
+ MPQueue->unlock();
+
+ if (!(curinstmask & (1 << LastHostID)))
+ return -1;
+ }
+
+ return RecvPacketGeneric(packet, true, timestamp);
+}
+
+u16 RecvReplies(u8* packets, u64 timestamp, u16 aidmask)
+{
+ u16 ret = 0;
+ u16 myinstmask = (1 << InstanceID);
+ u16 curinstmask;
+
+ {
+ MPQueue->lock();
+ u8* data = (u8*)MPQueue->data();
+ MPQueueHeader* header = (MPQueueHeader*)&data[0];
+ curinstmask = header->ConnectedBitmask;
+ MPQueue->unlock();
+ }
+
+ // if all clients have left: return early
+ if ((myinstmask & curinstmask) == curinstmask)
+ return 0;
+
+ for (;;)
+ {
+ if (!SemWait(16+InstanceID, RecvTimeout))
+ {
+ // no more replies available
+ return ret;
+ }
+
+ MPQueue->lock();
+ u8* data = (u8*)MPQueue->data();
+ MPQueueHeader* header = (MPQueueHeader*)&data[0];
+
+ MPPacketHeader pktheader;
+ FIFORead(1, &pktheader, sizeof(pktheader));
+
+ if (pktheader.Magic != 0x4946494E)
+ {
+ printf("REPLY FIFO OVERFLOW\n");
+ ReplyReadOffset = header->ReplyWriteOffset;
+ SemReset(16+InstanceID);
+ MPQueue->unlock();
+ return 0;
+ }
+
+ if ((pktheader.SenderID == InstanceID) || // packet we sent out (shouldn't happen, but hey)
+ (pktheader.Timestamp < (timestamp - 32))) // stale packet
+ {
+ // skip this packet
+ ReplyReadOffset += pktheader.Length;
+ if (ReplyReadOffset >= kReplyEnd)
+ ReplyReadOffset += kReplyStart - kReplyEnd;
+
+ MPQueue->unlock();
+ continue;
+ }
+
+ if (pktheader.Length)
+ {
+ u32 aid = (pktheader.Type >> 16);
+ FIFORead(1, &packets[(aid-1)*1024], pktheader.Length);
+ ret |= (1 << aid);
+ }
+
+ myinstmask |= (1 << pktheader.SenderID);
+ if (((myinstmask & curinstmask) == curinstmask) ||
+ ((ret & aidmask) == aidmask))
+ {
+ // all the clients have sent their reply
+
+ MPQueue->unlock();
+ return ret;
+ }
+
+ MPQueue->unlock();
+ }
+}
+
+}
+
diff --git a/src/frontend/qt_sdl/LocalMP.h b/src/frontend/qt_sdl/LocalMP.h
new file mode 100644
index 0000000..51dfcb9
--- /dev/null
+++ b/src/frontend/qt_sdl/LocalMP.h
@@ -0,0 +1,45 @@
+/*
+ Copyright 2016-2022 melonDS team
+
+ This file is part of melonDS.
+
+ melonDS 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 3 of the License, or (at your option)
+ any later version.
+
+ melonDS 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 melonDS. If not, see http://www.gnu.org/licenses/.
+*/
+
+#ifndef LOCALMP_H
+#define LOCALMP_H
+
+#include "types.h"
+
+namespace LocalMP
+{
+
+bool Init();
+void DeInit();
+
+void SetRecvTimeout(int timeout);
+
+void Begin();
+void End();
+
+int SendPacket(u8* data, int len, u64 timestamp);
+int RecvPacket(u8* data, u64* timestamp);
+int SendCmd(u8* data, int len, u64 timestamp);
+int SendReply(u8* data, int len, u64 timestamp, u16 aid);
+int SendAck(u8* data, int len, u64 timestamp);
+int RecvHostPacket(u8* data, u64* timestamp);
+u16 RecvReplies(u8* data, u64 timestamp, u16 aidmask);
+
+}
+
+#endif // LOCALMP_H
diff --git a/src/frontend/qt_sdl/MPSettingsDialog.cpp b/src/frontend/qt_sdl/MPSettingsDialog.cpp
new file mode 100644
index 0000000..e311422
--- /dev/null
+++ b/src/frontend/qt_sdl/MPSettingsDialog.cpp
@@ -0,0 +1,73 @@
+/*
+ Copyright 2016-2022 melonDS team
+
+ This file is part of melonDS.
+
+ melonDS 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 3 of the License, or (at your option)
+ any later version.
+
+ melonDS 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 melonDS. If not, see http://www.gnu.org/licenses/.
+*/
+
+#include <stdio.h>
+#include <QMessageBox>
+
+#include "types.h"
+#include "Platform.h"
+#include "Config.h"
+
+#include "LAN_Socket.h"
+#include "LAN_PCap.h"
+#include "Wifi.h"
+
+#include "MPSettingsDialog.h"
+#include "ui_MPSettingsDialog.h"
+
+
+MPSettingsDialog* MPSettingsDialog::currentDlg = nullptr;
+
+extern bool RunningSomething;
+
+
+MPSettingsDialog::MPSettingsDialog(QWidget* parent) : QDialog(parent), ui(new Ui::MPSettingsDialog)
+{
+ ui->setupUi(this);
+ setAttribute(Qt::WA_DeleteOnClose);
+
+ grpAudioMode = new QButtonGroup(this);
+ grpAudioMode->addButton(ui->rbAudioAll, 0);
+ grpAudioMode->addButton(ui->rbAudioOneOnly, 1);
+ grpAudioMode->addButton(ui->rbAudioActiveOnly, 2);
+ grpAudioMode->button(Config::MPAudioMode)->setChecked(true);
+
+ ui->sbReceiveTimeout->setValue(Config::MPRecvTimeout);
+}
+
+MPSettingsDialog::~MPSettingsDialog()
+{
+ delete ui;
+}
+
+void MPSettingsDialog::done(int r)
+{
+ if (r == QDialog::Accepted)
+ {
+ Config::MPAudioMode = grpAudioMode->checkedId();
+ Config::MPRecvTimeout = ui->sbReceiveTimeout->value();
+
+ Config::Save();
+ }
+
+ QDialog::done(r);
+
+ closeDlg();
+}
+
+//
diff --git a/src/frontend/qt_sdl/MPSettingsDialog.h b/src/frontend/qt_sdl/MPSettingsDialog.h
new file mode 100644
index 0000000..fe917e8
--- /dev/null
+++ b/src/frontend/qt_sdl/MPSettingsDialog.h
@@ -0,0 +1,65 @@
+/*
+ Copyright 2016-2022 melonDS team
+
+ This file is part of melonDS.
+
+ melonDS 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 3 of the License, or (at your option)
+ any later version.
+
+ melonDS 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 melonDS. If not, see http://www.gnu.org/licenses/.
+*/
+
+#ifndef MPSETTINGSDIALOG_H
+#define MPSETTINGSDIALOG_H
+
+#include <QDialog>
+#include <QButtonGroup>
+
+namespace Ui { class MPSettingsDialog; }
+class MPSettingsDialog;
+
+class MPSettingsDialog : public QDialog
+{
+ Q_OBJECT
+
+public:
+ explicit MPSettingsDialog(QWidget* parent);
+ ~MPSettingsDialog();
+
+ static MPSettingsDialog* currentDlg;
+ static MPSettingsDialog* openDlg(QWidget* parent)
+ {
+ if (currentDlg)
+ {
+ currentDlg->activateWindow();
+ return currentDlg;
+ }
+
+ currentDlg = new MPSettingsDialog(parent);
+ currentDlg->open();
+ return currentDlg;
+ }
+ static void closeDlg()
+ {
+ currentDlg = nullptr;
+ }
+
+private slots:
+ void done(int r);
+
+ //
+
+private:
+ Ui::MPSettingsDialog* ui;
+
+ QButtonGroup* grpAudioMode;
+};
+
+#endif // MPSETTINGSDIALOG_H
diff --git a/src/frontend/qt_sdl/MPSettingsDialog.ui b/src/frontend/qt_sdl/MPSettingsDialog.ui
new file mode 100644
index 0000000..bce0fc9
--- /dev/null
+++ b/src/frontend/qt_sdl/MPSettingsDialog.ui
@@ -0,0 +1,142 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MPSettingsDialog</class>
+ <widget class="QDialog" name="MPSettingsDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>466</width>
+ <height>202</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Multiplayer settings - melonDS</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QGroupBox" name="groupBox">
+ <property name="title">
+ <string>Audio output</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_2">
+ <item row="1" column="0">
+ <widget class="QRadioButton" name="rbAudioOneOnly">
+ <property name="text">
+ <string>Instance 1 only</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QRadioButton" name="rbAudioAll">
+ <property name="text">
+ <string>All instances</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QRadioButton" name="rbAudioActiveOnly">
+ <property name="text">
+ <string>Active instance only</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="groupBox_2">
+ <property name="title">
+ <string>Network</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="1">
+ <widget class="QSpinBox" name="sbReceiveTimeout">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>50</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximum">
+ <number>1000</number>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Data reception timeout: </string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="2">
+ <widget class="QLabel" name="label_2">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>1</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>milliseconds</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>MPSettingsDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>MPSettingsDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/src/frontend/qt_sdl/OSD.cpp b/src/frontend/qt_sdl/OSD.cpp
index d9f75fd..6f060a9 100644
--- a/src/frontend/qt_sdl/OSD.cpp
+++ b/src/frontend/qt_sdl/OSD.cpp
@@ -1,5 +1,5 @@
/*
- Copyright 2016-2021 Arisotura
+ Copyright 2016-2022 melonDS team
This file is part of melonDS.
@@ -146,7 +146,7 @@ void LayoutText(const char* text, u32* width, u32* height, int* breaks)
u32 w = 0;
u32 h = 14;
u32 totalw = 0;
- u32 maxw = mainWindow->panel->width() - (kOSDMargin*2);
+ u32 maxw = mainWindow->panelWidget->width() - (kOSDMargin*2);
int lastbreak = -1;
int numbrk = 0;
u16* ptr;
@@ -236,7 +236,7 @@ void RenderText(u32 color, const char* text, Item* item)
memset(item->Bitmap, 0, w*h*sizeof(u32));
u32 x = 0, y = 1;
- u32 maxw = mainWindow->panel->width() - (kOSDMargin*2);
+ u32 maxw = mainWindow->panelWidget->width() - (kOSDMargin*2);
int curline = 0;
u16* ptr;
diff --git a/src/frontend/qt_sdl/OSD.h b/src/frontend/qt_sdl/OSD.h
index 8fbfc7b..d624fc6 100644
--- a/src/frontend/qt_sdl/OSD.h
+++ b/src/frontend/qt_sdl/OSD.h
@@ -1,5 +1,5 @@
/*
- Copyright 2016-2021 Arisotura
+ Copyright 2016-2022 melonDS team
This file is part of melonDS.
diff --git a/src/frontend/qt_sdl/OSD_shaders.h b/src/frontend/qt_sdl/OSD_shaders.h
index 3ff51a9..e224fd1 100644
--- a/src/frontend/qt_sdl/OSD_shaders.h
+++ b/src/frontend/qt_sdl/OSD_shaders.h
@@ -1,5 +1,5 @@
/*
- Copyright 2016-2021 Arisotura
+ Copyright 2016-2022 melonDS team
This file is part of melonDS.
diff --git a/src/frontend/qt_sdl/PathSettingsDialog.cpp b/src/frontend/qt_sdl/PathSettingsDialog.cpp
new file mode 100644
index 0000000..286032e
--- /dev/null
+++ b/src/frontend/qt_sdl/PathSettingsDialog.cpp
@@ -0,0 +1,126 @@
+/*
+ Copyright 2016-2022 melonDS team
+
+ This file is part of melonDS.
+
+ melonDS 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 3 of the License, or (at your option)
+ any later version.
+
+ melonDS 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 melonDS. If not, see http://www.gnu.org/licenses/.
+*/
+
+#include <stdio.h>
+#include <QFileDialog>
+#include <QMessageBox>
+
+#include "types.h"
+#include "Config.h"
+#include "Platform.h"
+
+#include "PathSettingsDialog.h"
+#include "ui_PathSettingsDialog.h"
+
+
+PathSettingsDialog* PathSettingsDialog::currentDlg = nullptr;
+
+extern std::string EmuDirectory;
+extern bool RunningSomething;
+
+bool PathSettingsDialog::needsReset = false;
+
+
+PathSettingsDialog::PathSettingsDialog(QWidget* parent) : QDialog(parent), ui(new Ui::PathSettingsDialog)
+{
+ ui->setupUi(this);
+ setAttribute(Qt::WA_DeleteOnClose);
+
+ ui->txtSaveFilePath->setText(QString::fromStdString(Config::SaveFilePath));
+ ui->txtSavestatePath->setText(QString::fromStdString(Config::SavestatePath));
+ ui->txtCheatFilePath->setText(QString::fromStdString(Config::CheatFilePath));
+
+ int inst = Platform::InstanceID();
+ if (inst > 0)
+ ui->lblInstanceNum->setText(QString("Configuring paths for instance %1").arg(inst+1));
+ else
+ ui->lblInstanceNum->hide();
+}
+
+PathSettingsDialog::~PathSettingsDialog()
+{
+ delete ui;
+}
+
+void PathSettingsDialog::done(int r)
+{
+ needsReset = false;
+
+ if (r == QDialog::Accepted)
+ {
+ std::string saveFilePath = ui->txtSaveFilePath->text().toStdString();
+ std::string savestatePath = ui->txtSavestatePath->text().toStdString();
+ std::string cheatFilePath = ui->txtCheatFilePath->text().toStdString();
+
+ if ( saveFilePath != Config::SaveFilePath
+ || savestatePath != Config::SavestatePath
+ || cheatFilePath != Config::CheatFilePath)
+ {
+ if (RunningSomething
+ && QMessageBox::warning(this, "Reset necessary to apply changes",
+ "The emulation will be reset for the changes to take place.",
+ QMessageBox::Ok, QMessageBox::Cancel) != QMessageBox::Ok)
+ return;
+
+ Config::SaveFilePath = saveFilePath;
+ Config::SavestatePath = savestatePath;
+ Config::CheatFilePath = cheatFilePath;
+
+ Config::Save();
+
+ needsReset = true;
+ }
+ }
+
+ QDialog::done(r);
+
+ closeDlg();
+}
+
+void PathSettingsDialog::on_btnSaveFileBrowse_clicked()
+{
+ QString dir = QFileDialog::getExistingDirectory(this,
+ "Select save files path...",
+ QString::fromStdString(EmuDirectory));
+
+ if (dir.isEmpty()) return;
+
+ ui->txtSaveFilePath->setText(dir);
+}
+
+void PathSettingsDialog::on_btnSavestateBrowse_clicked()
+{
+ QString dir = QFileDialog::getExistingDirectory(this,
+ "Select savestates path...",
+ QString::fromStdString(EmuDirectory));
+
+ if (dir.isEmpty()) return;
+
+ ui->txtSavestatePath->setText(dir);
+}
+
+void PathSettingsDialog::on_btnCheatFileBrowse_clicked()
+{
+ QString dir = QFileDialog::getExistingDirectory(this,
+ "Select cheat files path...",
+ QString::fromStdString(EmuDirectory));
+
+ if (dir.isEmpty()) return;
+
+ ui->txtCheatFilePath->setText(dir);
+}
diff --git a/src/frontend/qt_sdl/PathSettingsDialog.h b/src/frontend/qt_sdl/PathSettingsDialog.h
new file mode 100644
index 0000000..ef4fd2d
--- /dev/null
+++ b/src/frontend/qt_sdl/PathSettingsDialog.h
@@ -0,0 +1,67 @@
+
+/*
+ Copyright 2016-2022 melonDS team
+
+ This file is part of melonDS.
+
+ melonDS 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 3 of the License, or (at your option)
+ any later version.
+
+ melonDS 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 melonDS. If not, see http://www.gnu.org/licenses/.
+*/
+
+#ifndef PATHSETTINGSDIALOG_H
+#define PATHSETTINGSDIALOG_H
+
+#include <QDialog>
+
+namespace Ui { class PathSettingsDialog; }
+class PathSettingsDialog;
+
+class PathSettingsDialog : public QDialog
+{
+ Q_OBJECT
+
+public:
+ explicit PathSettingsDialog(QWidget* parent);
+ ~PathSettingsDialog();
+
+ static PathSettingsDialog* currentDlg;
+ static PathSettingsDialog* openDlg(QWidget* parent)
+ {
+ if (currentDlg)
+ {
+ currentDlg->activateWindow();
+ return currentDlg;
+ }
+
+ currentDlg = new PathSettingsDialog(parent);
+ currentDlg->open();
+ return currentDlg;
+ }
+ static void closeDlg()
+ {
+ currentDlg = nullptr;
+ }
+
+ static bool needsReset;
+
+private slots:
+ void done(int r);
+
+ void on_btnSaveFileBrowse_clicked();
+ void on_btnSavestateBrowse_clicked();
+ void on_btnCheatFileBrowse_clicked();
+
+private:
+ Ui::PathSettingsDialog* ui;
+};
+
+#endif // PATHSETTINGSDIALOG_H
diff --git a/src/frontend/qt_sdl/PathSettingsDialog.ui b/src/frontend/qt_sdl/PathSettingsDialog.ui
new file mode 100644
index 0000000..295b1c4
--- /dev/null
+++ b/src/frontend/qt_sdl/PathSettingsDialog.ui
@@ -0,0 +1,156 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>PathSettingsDialog</class>
+ <widget class="QDialog" name="PathSettingsDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>439</width>
+ <height>185</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Path settings - melonDS</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="3" column="0">
+ <widget class="QLabel" name="label_4">
+ <property name="text">
+ <string>Cheat files path:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="2">
+ <widget class="QPushButton" name="btnCheatFileBrowse">
+ <property name="text">
+ <string>Browse...</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="label_3">
+ <property name="text">
+ <string>Savestates path:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="0" colspan="3">
+ <widget class="QLabel" name="label_5">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QLineEdit" name="txtSavestatePath">
+ <property name="clearButtonEnabled">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="2">
+ <widget class="QPushButton" name="btnSaveFileBrowse">
+ <property name="text">
+ <string>Browse...</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1">
+ <widget class="QLineEdit" name="txtCheatFilePath">
+ <property name="clearButtonEnabled">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="6" column="0" colspan="3">
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="2">
+ <widget class="QPushButton" name="btnSavestateBrowse">
+ <property name="text">
+ <string>Browse...</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_2">
+ <property name="text">
+ <string>Save files path:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="0" colspan="3">
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Leave a path blank to use the current ROM's path.</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QLineEdit" name="txtSaveFilePath">
+ <property name="clearButtonEnabled">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0" colspan="3">
+ <widget class="QLabel" name="lblInstanceNum">
+ <property name="text">
+ <string>Configuring paths for instance X</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <tabstops>
+ <tabstop>txtSaveFilePath</tabstop>
+ <tabstop>btnSaveFileBrowse</tabstop>
+ <tabstop>txtSavestatePath</tabstop>
+ <tabstop>btnSavestateBrowse</tabstop>
+ <tabstop>txtCheatFilePath</tabstop>
+ <tabstop>btnCheatFileBrowse</tabstop>
+ </tabstops>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>PathSettingsDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>PathSettingsDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/src/frontend/qt_sdl/Platform.cpp b/src/frontend/qt_sdl/Platform.cpp
index 812c953..f9eaf42 100644
--- a/src/frontend/qt_sdl/Platform.cpp
+++ b/src/frontend/qt_sdl/Platform.cpp
@@ -1,5 +1,5 @@
/*
- Copyright 2016-2021 Arisotura
+ Copyright 2016-2022 melonDS team
This file is part of melonDS.
@@ -20,28 +20,7 @@
#include <stdlib.h>
#include <string.h>
-#ifdef __WIN32__
- #define NTDDI_VERSION 0x06000000 // GROSS FUCKING HACK
- #include <winsock2.h>
- #include <windows.h>
- //#include <knownfolders.h> // FUCK THAT SHIT
- #include <shlobj.h>
- #include <ws2tcpip.h>
- #include <io.h>
- #define dup _dup
- #define socket_t SOCKET
- #define sockaddr_t SOCKADDR
-#else
- #include <unistd.h>
- #include <netinet/in.h>
- #include <sys/select.h>
- #include <sys/socket.h>
-
- #define socket_t int
- #define sockaddr_t struct sockaddr
- #define closesocket close
-#endif
-
+#include <string>
#include <QStandardPaths>
#include <QString>
#include <QDir>
@@ -49,31 +28,83 @@
#include <QSemaphore>
#include <QMutex>
#include <QOpenGLContext>
+#include <QSharedMemory>
#include "Platform.h"
#include "Config.h"
+#include "ROMManager.h"
+#include "CameraManager.h"
#include "LAN_Socket.h"
#include "LAN_PCap.h"
-#include <string>
-
-#ifndef INVALID_SOCKET
- #define INVALID_SOCKET (socket_t)-1
-#endif
+#include "LocalMP.h"
std::string EmuDirectory;
+extern CameraManager* camManager[2];
+
void emuStop();
namespace Platform
{
-socket_t MPSocket;
-sockaddr_t MPSendAddr;
-u8 PacketBuffer[2048];
+QSharedMemory* IPCBuffer = nullptr;
+int IPCInstanceID;
+
+void IPCInit()
+{
+ IPCInstanceID = 0;
+
+ IPCBuffer = new QSharedMemory("melonIPC");
+
+ if (!IPCBuffer->attach())
+ {
+ printf("IPC sharedmem doesn't exist. creating\n");
+ if (!IPCBuffer->create(1024))
+ {
+ printf("IPC sharedmem create failed :(\n");
+ delete IPCBuffer;
+ IPCBuffer = nullptr;
+ return;
+ }
+
+ IPCBuffer->lock();
+ memset(IPCBuffer->data(), 0, IPCBuffer->size());
+ IPCBuffer->unlock();
+ }
+
+ IPCBuffer->lock();
+ u8* data = (u8*)IPCBuffer->data();
+ u16 mask = *(u16*)&data[0];
+ for (int i = 0; i < 16; i++)
+ {
+ if (!(mask & (1<<i)))
+ {
+ IPCInstanceID = i;
+ *(u16*)&data[0] |= (1<<i);
+ break;
+ }
+ }
+ IPCBuffer->unlock();
+
+ printf("IPC: instance ID %d\n", IPCInstanceID);
+}
+
+void IPCDeInit()
+{
+ if (IPCBuffer)
+ {
+ IPCBuffer->lock();
+ u8* data = (u8*)IPCBuffer->data();
+ *(u16*)&data[0] &= ~(1<<IPCInstanceID);
+ IPCBuffer->unlock();
-#define NIFI_VER 1
+ IPCBuffer->detach();
+ delete IPCBuffer;
+ }
+ IPCBuffer = nullptr;
+}
void Init(int argc, char** argv)
@@ -109,10 +140,13 @@ void Init(int argc, char** argv)
confdir = config.absolutePath() + "/melonDS/";
EmuDirectory = confdir.toStdString();
#endif
+
+ IPCInit();
}
void DeInit()
{
+ IPCDeInit();
}
@@ -122,6 +156,22 @@ void StopEmu()
}
+int InstanceID()
+{
+ return IPCInstanceID;
+}
+
+std::string InstanceFileSuffix()
+{
+ int inst = IPCInstanceID;
+ if (inst == 0) return "";
+
+ char suffix[16] = {0};
+ snprintf(suffix, 15, ".%d", inst+1);
+ return suffix;
+}
+
+
int GetConfigInt(ConfigEntry entry)
{
const int imgsizes[] = {0, 256, 512, 1024, 2048, 4096};
@@ -168,7 +218,6 @@ bool GetConfigBool(ConfigEntry entry)
case DSiSD_ReadOnly: return Config::DSiSDReadOnly != 0;
case DSiSD_FolderSync: return Config::DSiSDFolderSync != 0;
- case Firm_RandomizeMAC: return Config::RandomizeMAC != 0;
case Firm_OverrideSettings: return Config::FirmwareOverrideSettings != 0;
}
@@ -207,7 +256,7 @@ bool GetConfigArray(ConfigEntry entry, void* data)
{
case Firm_MAC:
{
- char* mac_in = Config::FirmwareMAC;
+ std::string& mac_in = Config::FirmwareMAC;
u8* mac_out = (u8*)data;
int o = 0;
@@ -371,140 +420,80 @@ bool Mutex_TryLock(Mutex* mutex)
return ((QMutex*) mutex)->try_lock();
}
-
-bool MP_Init()
+void Sleep(u64 usecs)
{
- int opt_true = 1;
- int res;
+ QThread::usleep(usecs);
+}
-#ifdef __WIN32__
- WSADATA wsadata;
- if (WSAStartup(MAKEWORD(2, 2), &wsadata) != 0)
- {
- return false;
- }
-#endif // __WIN32__
- MPSocket = socket(AF_INET, SOCK_DGRAM, 0);
- if (MPSocket < 0)
- {
- return false;
- }
-
- res = setsockopt(MPSocket, SOL_SOCKET, SO_REUSEADDR, (const char*)&opt_true, sizeof(int));
- if (res < 0)
- {
- closesocket(MPSocket);
- MPSocket = INVALID_SOCKET;
- return false;
- }
+void WriteNDSSave(const u8* savedata, u32 savelen, u32 writeoffset, u32 writelen)
+{
+ if (ROMManager::NDSSave)
+ ROMManager::NDSSave->RequestFlush(savedata, savelen, writeoffset, writelen);
+}
- sockaddr_t saddr;
- saddr.sa_family = AF_INET;
- *(u32*)&saddr.sa_data[2] = htonl(Config::SocketBindAnyAddr ? INADDR_ANY : INADDR_LOOPBACK);
- *(u16*)&saddr.sa_data[0] = htons(7064);
- res = bind(MPSocket, &saddr, sizeof(sockaddr_t));
- if (res < 0)
- {
- closesocket(MPSocket);
- MPSocket = INVALID_SOCKET;
- return false;
- }
+void WriteGBASave(const u8* savedata, u32 savelen, u32 writeoffset, u32 writelen)
+{
+ if (ROMManager::GBASave)
+ ROMManager::GBASave->RequestFlush(savedata, savelen, writeoffset, writelen);
+}
- res = setsockopt(MPSocket, SOL_SOCKET, SO_BROADCAST, (const char*)&opt_true, sizeof(int));
- if (res < 0)
- {
- closesocket(MPSocket);
- MPSocket = INVALID_SOCKET;
- return false;
- }
- MPSendAddr.sa_family = AF_INET;
- *(u32*)&MPSendAddr.sa_data[2] = htonl(INADDR_BROADCAST);
- *(u16*)&MPSendAddr.sa_data[0] = htons(7064);
- return true;
+bool MP_Init()
+{
+ return LocalMP::Init();
}
void MP_DeInit()
{
- if (MPSocket >= 0)
- closesocket(MPSocket);
-
-#ifdef __WIN32__
- WSACleanup();
-#endif // __WIN32__
+ return LocalMP::DeInit();
}
-int MP_SendPacket(u8* data, int len)
+void MP_Begin()
{
- if (MPSocket < 0)
- return 0;
-
- if (len > 2048-8)
- {
- printf("MP_SendPacket: error: packet too long (%d)\n", len);
- return 0;
- }
-
- *(u32*)&PacketBuffer[0] = htonl(0x4946494E); // NIFI
- PacketBuffer[4] = NIFI_VER;
- PacketBuffer[5] = 0;
- *(u16*)&PacketBuffer[6] = htons(len);
- memcpy(&PacketBuffer[8], data, len);
-
- int slen = sendto(MPSocket, (const char*)PacketBuffer, len+8, 0, &MPSendAddr, sizeof(sockaddr_t));
- if (slen < 8) return 0;
- return slen - 8;
+ return LocalMP::Begin();
}
-int MP_RecvPacket(u8* data, bool block)
+void MP_End()
{
- if (MPSocket < 0)
- return 0;
-
- fd_set fd;
- struct timeval tv;
-
- FD_ZERO(&fd);
- FD_SET(MPSocket, &fd);
- tv.tv_sec = 0;
- tv.tv_usec = block ? 5000 : 0;
-
- if (!select(MPSocket+1, &fd, 0, 0, &tv))
- {
- return 0;
- }
+ return LocalMP::End();
+}
- sockaddr_t fromAddr;
- socklen_t fromLen = sizeof(sockaddr_t);
- int rlen = recvfrom(MPSocket, (char*)PacketBuffer, 2048, 0, &fromAddr, &fromLen);
- if (rlen < 8+24)
- {
- return 0;
- }
- rlen -= 8;
+int MP_SendPacket(u8* data, int len, u64 timestamp)
+{
+ return LocalMP::SendPacket(data, len, timestamp);
+}
- if (ntohl(*(u32*)&PacketBuffer[0]) != 0x4946494E)
- {
- return 0;
- }
+int MP_RecvPacket(u8* data, u64* timestamp)
+{
+ return LocalMP::RecvPacket(data, timestamp);
+}
- if (PacketBuffer[4] != NIFI_VER)
- {
- return 0;
- }
+int MP_SendCmd(u8* data, int len, u64 timestamp)
+{
+ return LocalMP::SendCmd(data, len, timestamp);
+}
- if (ntohs(*(u16*)&PacketBuffer[6]) != rlen)
- {
- return 0;
- }
+int MP_SendReply(u8* data, int len, u64 timestamp, u16 aid)
+{
+ return LocalMP::SendReply(data, len, timestamp, aid);
+}
- memcpy(data, &PacketBuffer[8], rlen);
- return rlen;
+int MP_SendAck(u8* data, int len, u64 timestamp)
+{
+ return LocalMP::SendAck(data, len, timestamp);
}
+int MP_RecvHostPacket(u8* data, u64* timestamp)
+{
+ return LocalMP::RecvHostPacket(data, timestamp);
+}
+u16 MP_RecvReplies(u8* data, u64 timestamp, u16 aidmask)
+{
+ return LocalMP::RecvReplies(data, timestamp, aidmask);
+}
bool LAN_Init()
{
@@ -549,9 +538,20 @@ int LAN_RecvPacket(u8* data)
return LAN_Socket::RecvPacket(data);
}
-void Sleep(u64 usecs)
+
+void Camera_Start(int num)
{
- QThread::usleep(usecs);
+ return camManager[num]->start();
+}
+
+void Camera_Stop(int num)
+{
+ return camManager[num]->stop();
+}
+
+void Camera_CaptureFrame(int num, u32* frame, int width, int height, bool yuv)
+{
+ return camManager[num]->captureFrame(frame, width, height, yuv);
}
}
diff --git a/src/frontend/qt_sdl/PowerManagement/PowerManagementDialog.cpp b/src/frontend/qt_sdl/PowerManagement/PowerManagementDialog.cpp
new file mode 100644
index 0000000..89f74e5
--- /dev/null
+++ b/src/frontend/qt_sdl/PowerManagement/PowerManagementDialog.cpp
@@ -0,0 +1,154 @@
+/*
+ Copyright 2016-2022 melonDS team
+
+ This file is part of melonDS.
+
+ melonDS 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 3 of the License, or (at your option)
+ any later version.
+
+ melonDS 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 melonDS. If not, see http://www.gnu.org/licenses/.
+*/
+
+#include "PowerManagementDialog.h"
+#include "ui_PowerManagementDialog.h"
+
+#include "SPI.h"
+#include "DSi_I2C.h"
+#include "NDS.h"
+#include "Config.h"
+#include "Platform.h"
+
+#include "types.h"
+
+#include <QtDebug>
+
+PowerManagementDialog* PowerManagementDialog::currentDlg = nullptr;
+
+PowerManagementDialog::PowerManagementDialog(QWidget* parent) : QDialog(parent), ui(new Ui::PowerManagementDialog)
+{
+ inited = false;
+
+ ui->setupUi(this);
+ setAttribute(Qt::WA_DeleteOnClose);
+
+ if (NDS::ConsoleType == 1)
+ {
+ ui->grpDSBattery->setEnabled(false);
+
+ oldDSiBatteryLevel = DSi_BPTWL::GetBatteryLevel();
+ oldDSiBatteryCharging = DSi_BPTWL::GetBatteryCharging();
+ }
+ else
+ {
+ ui->grpDSiBattery->setEnabled(false);
+
+ oldDSBatteryLevel = SPI_Powerman::GetBatteryLevelOkay();
+ }
+
+ updateDSBatteryLevelControls();
+
+ ui->cbDSiBatteryCharging->setChecked(DSi_BPTWL::GetBatteryCharging());
+ int dsiBatterySliderPos;
+ switch (DSi_BPTWL::GetBatteryLevel())
+ {
+ case DSi_BPTWL::batteryLevel_AlmostEmpty: dsiBatterySliderPos = 0; break;
+ case DSi_BPTWL::batteryLevel_Low: dsiBatterySliderPos = 1; break;
+ case DSi_BPTWL::batteryLevel_Half: dsiBatterySliderPos = 2; break;
+ case DSi_BPTWL::batteryLevel_ThreeQuarters: dsiBatterySliderPos = 3; break;
+ case DSi_BPTWL::batteryLevel_Full: dsiBatterySliderPos = 4; break;
+ }
+ ui->sliderDSiBatteryLevel->setValue(dsiBatterySliderPos);
+
+ int inst = Platform::InstanceID();
+ if (inst > 0)
+ ui->lblInstanceNum->setText(QString("Setting battery levels for instance %1").arg(inst+1));
+ else
+ ui->lblInstanceNum->hide();
+
+ inited = true;
+}
+
+PowerManagementDialog::~PowerManagementDialog()
+{
+ delete ui;
+}
+
+void PowerManagementDialog::done(int r)
+{
+ if (r == QDialog::Accepted)
+ {
+ if (NDS::ConsoleType == 1)
+ {
+ Config::DSiBatteryLevel = DSi_BPTWL::GetBatteryLevel();
+ Config::DSiBatteryCharging = DSi_BPTWL::GetBatteryCharging();
+ }
+ else
+ {
+ Config::DSBatteryLevelOkay = SPI_Powerman::GetBatteryLevelOkay();
+ }
+ }
+ else
+ {
+ if (NDS::ConsoleType == 1)
+ {
+ DSi_BPTWL::SetBatteryLevel(oldDSiBatteryLevel);
+ DSi_BPTWL::SetBatteryCharging(oldDSiBatteryCharging);
+ }
+ else
+ {
+ SPI_Powerman::SetBatteryLevelOkay(oldDSBatteryLevel);
+ }
+ }
+
+ QDialog::done(r);
+
+ closeDlg();
+}
+
+void PowerManagementDialog::on_rbDSBatteryLow_clicked()
+{
+ SPI_Powerman::SetBatteryLevelOkay(false);
+}
+
+void PowerManagementDialog::on_rbDSBatteryOkay_clicked()
+{
+ SPI_Powerman::SetBatteryLevelOkay(true);
+}
+
+void PowerManagementDialog::updateDSBatteryLevelControls()
+{
+ if (SPI_Powerman::GetBatteryLevelOkay())
+ ui->rbDSBatteryOkay->setChecked(true);
+ else
+ ui->rbDSBatteryLow->setChecked(true);
+}
+
+void PowerManagementDialog::on_cbDSiBatteryCharging_toggled()
+{
+ DSi_BPTWL::SetBatteryCharging(ui->cbDSiBatteryCharging->isChecked());
+}
+
+void PowerManagementDialog::on_sliderDSiBatteryLevel_valueChanged(int value)
+{
+ if (!inited) return;
+
+ u8 newBatteryLevel;
+ switch (value)
+ {
+ case 0: newBatteryLevel = DSi_BPTWL::batteryLevel_AlmostEmpty; break;
+ case 1: newBatteryLevel = DSi_BPTWL::batteryLevel_Low; break;
+ case 2: newBatteryLevel = DSi_BPTWL::batteryLevel_Half; break;
+ case 3: newBatteryLevel = DSi_BPTWL::batteryLevel_ThreeQuarters; break;
+ case 4: newBatteryLevel = DSi_BPTWL::batteryLevel_Full; break;
+ }
+ DSi_BPTWL::SetBatteryLevel(newBatteryLevel);
+ updateDSBatteryLevelControls();
+}
+
diff --git a/src/frontend/qt_sdl/PowerManagement/PowerManagementDialog.h b/src/frontend/qt_sdl/PowerManagement/PowerManagementDialog.h
new file mode 100644
index 0000000..f335a5e
--- /dev/null
+++ b/src/frontend/qt_sdl/PowerManagement/PowerManagementDialog.h
@@ -0,0 +1,77 @@
+/*
+ Copyright 2016-2022 melonDS team
+
+ This file is part of melonDS.
+
+ melonDS 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 3 of the License, or (at your option)
+ any later version.
+
+ melonDS 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 melonDS. If not, see http://www.gnu.org/licenses/.
+*/
+
+#ifndef POWERMANAGEMENTDIALOG_H
+#define POWERMANAGEMENTDIALOG_H
+
+#include <QDialog>
+#include <QAbstractButton>
+
+#include "types.h"
+
+namespace Ui { class PowerManagementDialog; }
+class PowerManagementDialog;
+
+class PowerManagementDialog : public QDialog
+{
+ Q_OBJECT
+
+public:
+ explicit PowerManagementDialog(QWidget* parent);
+ ~PowerManagementDialog();
+
+ static PowerManagementDialog* currentDlg;
+ static PowerManagementDialog* openDlg(QWidget* parent)
+ {
+ if (currentDlg)
+ {
+ currentDlg->activateWindow();
+ return currentDlg;
+ }
+
+ currentDlg = new PowerManagementDialog(parent);
+ currentDlg->open();
+ return currentDlg;
+ }
+ static void closeDlg()
+ {
+ currentDlg = nullptr;
+ }
+
+private slots:
+ void done(int r);
+
+ void on_rbDSBatteryLow_clicked();
+ void on_rbDSBatteryOkay_clicked();
+
+ void on_cbDSiBatteryCharging_toggled();
+ void on_sliderDSiBatteryLevel_valueChanged(int value);
+
+private:
+ Ui::PowerManagementDialog* ui;
+
+ bool inited;
+ bool oldDSBatteryLevel;
+ u8 oldDSiBatteryLevel;
+ bool oldDSiBatteryCharging;
+
+ void updateDSBatteryLevelControls();
+};
+
+#endif // POWERMANAGEMENTDIALOG_H
+
diff --git a/src/frontend/qt_sdl/PowerManagement/PowerManagementDialog.ui b/src/frontend/qt_sdl/PowerManagement/PowerManagementDialog.ui
new file mode 100644
index 0000000..77af225
--- /dev/null
+++ b/src/frontend/qt_sdl/PowerManagement/PowerManagementDialog.ui
@@ -0,0 +1,274 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>PowerManagementDialog</class>
+ <widget class="QDialog" name="PowerManagementDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>562</width>
+ <height>288</height>
+ </rect>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="windowTitle">
+ <string>Power management - melonDS</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <property name="sizeConstraint">
+ <enum>QLayout::SetFixedSize</enum>
+ </property>
+ <item row="4" column="0">
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0">
+ <widget class="QGroupBox" name="grpDSiBattery">
+ <property name="title">
+ <string>DSi Battery</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_6">
+ <item row="1" column="2">
+ <widget class="QLabel" name="label_2">
+ <property name="text">
+ <string>Almost Empty</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>QSizePolicy::Preferred</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="0" column="0" colspan="5">
+ <widget class="QCheckBox" name="cbDSiBatteryCharging">
+ <property name="text">
+ <string>Charging</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="8">
+ <widget class="QLabel" name="label_3">
+ <property name="text">
+ <string>Full</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_1">
+ <property name="text">
+ <string>Battery Level</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="3">
+ <widget class="QLabel" name="label_4">
+ <property name="text">
+ <string/>
+ </property>
+ <property name="pixmap">
+ <pixmap resource="resources/battery.qrc">:/dsibattery/dsi_batteryalmostempty.svg</pixmap>
+ </property>
+ <property name="scaledContents">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="4">
+ <widget class="QLabel" name="label_5">
+ <property name="text">
+ <string/>
+ </property>
+ <property name="pixmap">
+ <pixmap resource="resources/battery.qrc">:/dsibattery/dsi_batterylow.svg</pixmap>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="5">
+ <widget class="QLabel" name="label_6">
+ <property name="text">
+ <string/>
+ </property>
+ <property name="pixmap">
+ <pixmap resource="resources/battery.qrc">:/dsibattery/dsi_battery2.svg</pixmap>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="6">
+ <widget class="QLabel" name="label_7">
+ <property name="text">
+ <string/>
+ </property>
+ <property name="pixmap">
+ <pixmap resource="resources/battery.qrc">:/dsibattery/dsi_battery3.svg</pixmap>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="7">
+ <widget class="QLabel" name="label_8">
+ <property name="text">
+ <string/>
+ </property>
+ <property name="pixmap">
+ <pixmap resource="resources/battery.qrc">:/dsibattery/dsi_batteryfull.svg</pixmap>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="3" colspan="5">
+ <widget class="QSlider" name="sliderDSiBatteryLevel">
+ <property name="maximum">
+ <number>4</number>
+ </property>
+ <property name="pageStep">
+ <number>1</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="tickPosition">
+ <enum>QSlider::TicksBothSides</enum>
+ </property>
+ <property name="tickInterval">
+ <number>1</number>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0" colspan="3">
+ <spacer name="horizontalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QGroupBox" name="grpDSBattery">
+ <property name="title">
+ <string>DS Battery</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_2">
+ <item row="1" column="1">
+ <widget class="QRadioButton" name="rbDSBatteryLow">
+ <property name="text">
+ <string>Low</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0" rowspan="2">
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Battery Level</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QRadioButton" name="rbDSBatteryOkay">
+ <property name="text">
+ <string>Okay</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="lblInstanceNum">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Configuring settings for instance X</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources>
+ <include location="resources/battery.qrc"/>
+ </resources>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>PowerManagementDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>PowerManagementDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/src/frontend/qt_sdl/PowerManagement/resources/battery.qrc b/src/frontend/qt_sdl/PowerManagement/resources/battery.qrc
new file mode 100644
index 0000000..7f9c95b
--- /dev/null
+++ b/src/frontend/qt_sdl/PowerManagement/resources/battery.qrc
@@ -0,0 +1,9 @@
+<RCC>
+ <qresource prefix="dsibattery">
+ <file>dsi_batteryalmostempty.svg</file>
+ <file>dsi_batterylow.svg</file>
+ <file>dsi_battery2.svg</file>
+ <file>dsi_battery3.svg</file>
+ <file>dsi_batteryfull.svg</file>
+ </qresource>
+</RCC>
diff --git a/src/frontend/qt_sdl/PowerManagement/resources/dsi_battery2.svg b/src/frontend/qt_sdl/PowerManagement/resources/dsi_battery2.svg
new file mode 100644
index 0000000..e9c4b75
--- /dev/null
+++ b/src/frontend/qt_sdl/PowerManagement/resources/dsi_battery2.svg
@@ -0,0 +1,171 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ width="12.236976mm"
+ height="6.2838535mm"
+ viewBox="0 0 12.236976 6.283854"
+ version="1.1"
+ id="svg5"
+ inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
+ sodipodi:docname="battery2.svg"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg">
+ <sodipodi:namedview
+ id="namedview7"
+ pagecolor="#ff8100"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageshadow="2"
+ inkscape:pageopacity="1"
+ inkscape:pagecheckerboard="false"
+ inkscape:document-units="mm"
+ showgrid="true"
+ height="200mm"
+ showborder="true"
+ inkscape:showpageshadow="false"
+ borderlayer="false"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ inkscape:zoom="10.24"
+ inkscape:cx="2.7832031"
+ inkscape:cy="2.0507813"
+ inkscape:window-width="1920"
+ inkscape:window-height="1011"
+ inkscape:window-x="0"
+ inkscape:window-y="32"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="layer1">
+ <inkscape:grid
+ type="xygrid"
+ id="grid11"
+ spacingx="0.26458333"
+ spacingy="0.26458333"
+ originx="-91.777345"
+ originy="-47.128916" />
+ </sodipodi:namedview>
+ <defs
+ id="defs2" />
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(-91.777344,-47.12891)">
+ <rect
+ style="fill:#000000;fill-rule:evenodd;stroke-width:0.0211535"
+ id="rect153"
+ width="1.3229166"
+ height="2.9765623"
+ x="91.777344"
+ y="48.782555" />
+ <rect
+ style="fill:#000000;stroke-width:0.0162915"
+ id="rect177"
+ width="10.914062"
+ height="0.66145831"
+ x="93.100258"
+ y="47.12891" />
+ <rect
+ style="fill:#000000;stroke-width:0.0165364"
+ id="rect179"
+ width="0.66145831"
+ height="4.9609375"
+ x="93.100258"
+ y="47.790367" />
+ <rect
+ style="fill:#000000;stroke-width:0.0162915"
+ id="rect181"
+ width="10.914062"
+ height="0.66145873"
+ x="93.100258"
+ y="52.751305" />
+ <rect
+ style="fill:#000000;stroke-width:0.0165364"
+ id="rect183"
+ width="0.66145831"
+ height="4.9609375"
+ x="103.35286"
+ y="47.790367" />
+ <rect
+ style="fill:#ffffff;stroke-width:0.0162585"
+ id="rect318"
+ width="9.5911455"
+ height="0.49609375"
+ x="93.761719"
+ y="47.790367" />
+ <rect
+ style="fill:#ffffff;stroke-width:0.0158876"
+ id="rect408"
+ width="0.49609372"
+ height="3.96875"
+ x="93.761719"
+ y="48.286461" />
+ <rect
+ style="fill:#ffffff;stroke-width:0.015706"
+ id="rect410"
+ width="9.5911455"
+ height="0.49609351"
+ x="93.761719"
+ y="52.255211" />
+ <rect
+ style="fill:#ffffff;stroke-width:0.0158876"
+ id="rect412"
+ width="0.4960939"
+ height="3.9687498"
+ x="102.85677"
+ y="48.286461" />
+ <rect
+ style="fill:#ffffff;stroke-width:0.0224686"
+ id="rect414"
+ width="0.66145849"
+ height="3.9687498"
+ x="95.911453"
+ y="48.286461" />
+ <rect
+ style="fill:#ffffff;stroke-width:0.0158876"
+ id="rect414-3"
+ width="0.66145885"
+ height="3.9687498"
+ x="98.226562"
+ y="48.286461" />
+ <rect
+ style="fill:#ffffff;stroke-width:0.0158876"
+ id="rect414-6"
+ width="0.66145819"
+ height="3.9687498"
+ x="100.54166"
+ y="48.286461" />
+ <rect
+ style="fill:#999999;stroke-width:0.0165364"
+ id="rect1736"
+ width="1.6536455"
+ height="3.9687498"
+ x="94.257812"
+ y="48.286461" />
+ <rect
+ style="fill:#999999;stroke-width:0.0165364"
+ id="rect1738"
+ width="1.6536458"
+ height="3.96875"
+ x="96.572914"
+ y="48.286461" />
+ <rect
+ style="fill:#00ccff;stroke-width:0.0165364"
+ id="rect1740"
+ width="1.6536458"
+ height="3.96875"
+ x="98.888016"
+ y="48.286461" />
+ <rect
+ style="fill:#00ccff;stroke-width:0.0165364"
+ id="rect1742"
+ width="1.6536458"
+ height="3.96875"
+ x="101.20312"
+ y="48.286461" />
+ </g>
+</svg>
diff --git a/src/frontend/qt_sdl/PowerManagement/resources/dsi_battery3.svg b/src/frontend/qt_sdl/PowerManagement/resources/dsi_battery3.svg
new file mode 100644
index 0000000..d464ef3
--- /dev/null
+++ b/src/frontend/qt_sdl/PowerManagement/resources/dsi_battery3.svg
@@ -0,0 +1,171 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ width="12.236976mm"
+ height="6.2838535mm"
+ viewBox="0 0 12.236976 6.283854"
+ version="1.1"
+ id="svg5"
+ inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
+ sodipodi:docname="battery3.svg"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg">
+ <sodipodi:namedview
+ id="namedview7"
+ pagecolor="#ff8100"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageshadow="2"
+ inkscape:pageopacity="1"
+ inkscape:pagecheckerboard="false"
+ inkscape:document-units="mm"
+ showgrid="true"
+ height="200mm"
+ showborder="true"
+ inkscape:showpageshadow="false"
+ borderlayer="false"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ inkscape:zoom="10.24"
+ inkscape:cx="2.7832031"
+ inkscape:cy="2.0507813"
+ inkscape:window-width="1920"
+ inkscape:window-height="1011"
+ inkscape:window-x="0"
+ inkscape:window-y="32"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="layer1">
+ <inkscape:grid
+ type="xygrid"
+ id="grid11"
+ spacingx="0.26458333"
+ spacingy="0.26458333"
+ originx="-91.777345"
+ originy="-47.128916" />
+ </sodipodi:namedview>
+ <defs
+ id="defs2" />
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(-91.777344,-47.12891)">
+ <rect
+ style="fill:#000000;fill-rule:evenodd;stroke-width:0.0211535"
+ id="rect153"
+ width="1.3229166"
+ height="2.9765623"
+ x="91.777344"
+ y="48.782555" />
+ <rect
+ style="fill:#000000;stroke-width:0.0162915"
+ id="rect177"
+ width="10.914062"
+ height="0.66145831"
+ x="93.100258"
+ y="47.12891" />
+ <rect
+ style="fill:#000000;stroke-width:0.0165364"
+ id="rect179"
+ width="0.66145831"
+ height="4.9609375"
+ x="93.100258"
+ y="47.790367" />
+ <rect
+ style="fill:#000000;stroke-width:0.0162915"
+ id="rect181"
+ width="10.914062"
+ height="0.66145873"
+ x="93.100258"
+ y="52.751305" />
+ <rect
+ style="fill:#000000;stroke-width:0.0165364"
+ id="rect183"
+ width="0.66145831"
+ height="4.9609375"
+ x="103.35286"
+ y="47.790367" />
+ <rect
+ style="fill:#ffffff;stroke-width:0.0162585"
+ id="rect318"
+ width="9.5911455"
+ height="0.49609375"
+ x="93.761719"
+ y="47.790367" />
+ <rect
+ style="fill:#ffffff;stroke-width:0.0158876"
+ id="rect408"
+ width="0.49609372"
+ height="3.96875"
+ x="93.761719"
+ y="48.286461" />
+ <rect
+ style="fill:#ffffff;stroke-width:0.015706"
+ id="rect410"
+ width="9.5911455"
+ height="0.49609351"
+ x="93.761719"
+ y="52.255211" />
+ <rect
+ style="fill:#ffffff;stroke-width:0.0158876"
+ id="rect412"
+ width="0.4960939"
+ height="3.9687498"
+ x="102.85677"
+ y="48.286461" />
+ <rect
+ style="fill:#ffffff;stroke-width:0.0224686"
+ id="rect414"
+ width="0.66145849"
+ height="3.9687498"
+ x="95.911453"
+ y="48.286461" />
+ <rect
+ style="fill:#ffffff;stroke-width:0.0158876"
+ id="rect414-3"
+ width="0.66145885"
+ height="3.9687498"
+ x="98.226562"
+ y="48.286461" />
+ <rect
+ style="fill:#ffffff;stroke-width:0.0158876"
+ id="rect414-6"
+ width="0.66145819"
+ height="3.9687498"
+ x="100.54166"
+ y="48.286461" />
+ <rect
+ style="fill:#999999;stroke-width:0.0165364"
+ id="rect1736"
+ width="1.6536455"
+ height="3.9687498"
+ x="94.257812"
+ y="48.286461" />
+ <rect
+ style="fill:#00ccff;stroke-width:0.0165364"
+ id="rect1738"
+ width="1.6536458"
+ height="3.96875"
+ x="96.572914"
+ y="48.286461" />
+ <rect
+ style="fill:#00ccff;stroke-width:0.0165364"
+ id="rect1740"
+ width="1.6536458"
+ height="3.96875"
+ x="98.888016"
+ y="48.286461" />
+ <rect
+ style="fill:#00ccff;stroke-width:0.0165364"
+ id="rect1742"
+ width="1.6536458"
+ height="3.96875"
+ x="101.20312"
+ y="48.286461" />
+ </g>
+</svg>
diff --git a/src/frontend/qt_sdl/PowerManagement/resources/dsi_batteryalmostempty.svg b/src/frontend/qt_sdl/PowerManagement/resources/dsi_batteryalmostempty.svg
new file mode 100644
index 0000000..4f598fa
--- /dev/null
+++ b/src/frontend/qt_sdl/PowerManagement/resources/dsi_batteryalmostempty.svg
@@ -0,0 +1,171 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ width="12.236976mm"
+ height="6.2838535mm"
+ viewBox="0 0 12.236976 6.283854"
+ version="1.1"
+ id="svg5"
+ inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
+ sodipodi:docname="dsi_batteryalmostempty.svg"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg">
+ <sodipodi:namedview
+ id="namedview7"
+ pagecolor="#ff8100"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageshadow="2"
+ inkscape:pageopacity="1"
+ inkscape:pagecheckerboard="false"
+ inkscape:document-units="mm"
+ showgrid="true"
+ height="200mm"
+ showborder="true"
+ inkscape:showpageshadow="false"
+ borderlayer="false"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ inkscape:zoom="14.481547"
+ inkscape:cx="35.389866"
+ inkscape:cy="16.503762"
+ inkscape:window-width="1920"
+ inkscape:window-height="1011"
+ inkscape:window-x="0"
+ inkscape:window-y="32"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="layer1">
+ <inkscape:grid
+ type="xygrid"
+ id="grid11"
+ spacingx="0.26458333"
+ spacingy="0.26458333"
+ originx="-91.77735"
+ originy="-47.128928" />
+ </sodipodi:namedview>
+ <defs
+ id="defs2" />
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(-91.777344,-47.12891)">
+ <rect
+ style="fill:#ff2a2a;fill-rule:evenodd;stroke-width:0.0211535"
+ id="rect153"
+ width="1.3229166"
+ height="2.9765623"
+ x="91.777344"
+ y="48.782555" />
+ <rect
+ style="fill:#ff2a2a;stroke-width:0.0162915"
+ id="rect177"
+ width="10.914062"
+ height="0.66145831"
+ x="93.100258"
+ y="47.12891" />
+ <rect
+ style="fill:#ff2a2a;stroke-width:0.0165364"
+ id="rect179"
+ width="0.66145831"
+ height="4.9609375"
+ x="93.100258"
+ y="47.790367" />
+ <rect
+ style="fill:#ff2a2a;stroke-width:0.0162915"
+ id="rect181"
+ width="10.914062"
+ height="0.66145873"
+ x="93.100258"
+ y="52.751305" />
+ <rect
+ style="fill:#ff2a2a;stroke-width:0.0165364"
+ id="rect183"
+ width="0.66145831"
+ height="4.9609375"
+ x="103.35286"
+ y="47.790367" />
+ <rect
+ style="fill:#ffffff;stroke-width:0.0162585"
+ id="rect318"
+ width="9.5911455"
+ height="0.49609375"
+ x="93.761719"
+ y="47.790367" />
+ <rect
+ style="fill:#ffffff;stroke-width:0.0158876"
+ id="rect408"
+ width="0.49609372"
+ height="3.96875"
+ x="93.761719"
+ y="48.286461" />
+ <rect
+ style="fill:#ffffff;stroke-width:0.015706"
+ id="rect410"
+ width="9.5911455"
+ height="0.49609351"
+ x="93.761719"
+ y="52.255211" />
+ <rect
+ style="fill:#ffffff;stroke-width:0.0158876"
+ id="rect412"
+ width="0.4960939"
+ height="3.9687498"
+ x="102.85677"
+ y="48.286461" />
+ <rect
+ style="fill:#ffffff;stroke-width:0.0224686"
+ id="rect414"
+ width="0.66145849"
+ height="3.9687498"
+ x="95.911453"
+ y="48.286461" />
+ <rect
+ style="fill:#ffffff;stroke-width:0.0158876"
+ id="rect414-3"
+ width="0.66145885"
+ height="3.9687498"
+ x="98.226562"
+ y="48.286461" />
+ <rect
+ style="fill:#ffffff;stroke-width:0.0158876"
+ id="rect414-6"
+ width="0.66145819"
+ height="3.9687498"
+ x="100.54166"
+ y="48.286461" />
+ <rect
+ style="fill:#999999;stroke-width:0.0165364"
+ id="rect1736"
+ width="1.6536455"
+ height="3.9687498"
+ x="94.257812"
+ y="48.286461" />
+ <rect
+ style="fill:#999999;stroke-width:0.0165364"
+ id="rect1738"
+ width="1.6536458"
+ height="3.96875"
+ x="96.572914"
+ y="48.286461" />
+ <rect
+ style="fill:#999999;stroke-width:0.0165364"
+ id="rect1740"
+ width="1.6536458"
+ height="3.96875"
+ x="98.888016"
+ y="48.286461" />
+ <rect
+ style="fill:#ff2a2a;stroke-width:0.0165364"
+ id="rect1742"
+ width="1.6536458"
+ height="3.96875"
+ x="101.20312"
+ y="48.286461" />
+ </g>
+</svg>
diff --git a/src/frontend/qt_sdl/PowerManagement/resources/dsi_batteryfull.svg b/src/frontend/qt_sdl/PowerManagement/resources/dsi_batteryfull.svg
new file mode 100644
index 0000000..dbf8499
--- /dev/null
+++ b/src/frontend/qt_sdl/PowerManagement/resources/dsi_batteryfull.svg
@@ -0,0 +1,171 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ width="12.236976mm"
+ height="6.2838535mm"
+ viewBox="0 0 12.236976 6.283854"
+ version="1.1"
+ id="svg5"
+ inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
+ sodipodi:docname="batteryfull.svg"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg">
+ <sodipodi:namedview
+ id="namedview7"
+ pagecolor="#ff8100"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageshadow="2"
+ inkscape:pageopacity="1"
+ inkscape:pagecheckerboard="false"
+ inkscape:document-units="mm"
+ showgrid="true"
+ height="200mm"
+ showborder="true"
+ inkscape:showpageshadow="false"
+ borderlayer="false"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ inkscape:zoom="10.24"
+ inkscape:cx="2.7832031"
+ inkscape:cy="2.0507813"
+ inkscape:window-width="1920"
+ inkscape:window-height="1011"
+ inkscape:window-x="0"
+ inkscape:window-y="32"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="layer1">
+ <inkscape:grid
+ type="xygrid"
+ id="grid11"
+ spacingx="0.26458333"
+ spacingy="0.26458333"
+ originx="-91.777345"
+ originy="-47.128916" />
+ </sodipodi:namedview>
+ <defs
+ id="defs2" />
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(-91.777344,-47.12891)">
+ <rect
+ style="fill:#000000;fill-rule:evenodd;stroke-width:0.0211535"
+ id="rect153"
+ width="1.3229166"
+ height="2.9765623"
+ x="91.777344"
+ y="48.782555" />
+ <rect
+ style="fill:#000000;stroke-width:0.0162915"
+ id="rect177"
+ width="10.914062"
+ height="0.66145831"
+ x="93.100258"
+ y="47.12891" />
+ <rect
+ style="fill:#000000;stroke-width:0.0165364"
+ id="rect179"
+ width="0.66145831"
+ height="4.9609375"
+ x="93.100258"
+ y="47.790367" />
+ <rect
+ style="fill:#000000;stroke-width:0.0162915"
+ id="rect181"
+ width="10.914062"
+ height="0.66145873"
+ x="93.100258"
+ y="52.751305" />
+ <rect
+ style="fill:#000000;stroke-width:0.0165364"
+ id="rect183"
+ width="0.66145831"
+ height="4.9609375"
+ x="103.35286"
+ y="47.790367" />
+ <rect
+ style="fill:#ffffff;stroke-width:0.0162585"
+ id="rect318"
+ width="9.5911455"
+ height="0.49609375"
+ x="93.761719"
+ y="47.790367" />
+ <rect
+ style="fill:#ffffff;stroke-width:0.0158876"
+ id="rect408"
+ width="0.49609372"
+ height="3.96875"
+ x="93.761719"
+ y="48.286461" />
+ <rect
+ style="fill:#ffffff;stroke-width:0.015706"
+ id="rect410"
+ width="9.5911455"
+ height="0.49609351"
+ x="93.761719"
+ y="52.255211" />
+ <rect
+ style="fill:#ffffff;stroke-width:0.0158876"
+ id="rect412"
+ width="0.4960939"
+ height="3.9687498"
+ x="102.85677"
+ y="48.286461" />
+ <rect
+ style="fill:#ffffff;stroke-width:0.0224686"
+ id="rect414"
+ width="0.66145849"
+ height="3.9687498"
+ x="95.911453"
+ y="48.286461" />
+ <rect
+ style="fill:#ffffff;stroke-width:0.0158876"
+ id="rect414-3"
+ width="0.66145885"
+ height="3.9687498"
+ x="98.226562"
+ y="48.286461" />
+ <rect
+ style="fill:#ffffff;stroke-width:0.0158876"
+ id="rect414-6"
+ width="0.66145819"
+ height="3.9687498"
+ x="100.54166"
+ y="48.286461" />
+ <rect
+ style="fill:#00ccff;stroke-width:0.0165364"
+ id="rect1736"
+ width="1.6536455"
+ height="3.9687498"
+ x="94.257812"
+ y="48.286461" />
+ <rect
+ style="fill:#00ccff;stroke-width:0.0165364"
+ id="rect1738"
+ width="1.6536458"
+ height="3.96875"
+ x="96.572914"
+ y="48.286461" />
+ <rect
+ style="fill:#00ccff;stroke-width:0.0165364"
+ id="rect1740"
+ width="1.6536458"
+ height="3.96875"
+ x="98.888016"
+ y="48.286461" />
+ <rect
+ style="fill:#00ccff;stroke-width:0.0165364"
+ id="rect1742"
+ width="1.6536458"
+ height="3.96875"
+ x="101.20312"
+ y="48.286461" />
+ </g>
+</svg>
diff --git a/src/frontend/qt_sdl/PowerManagement/resources/dsi_batterylow.svg b/src/frontend/qt_sdl/PowerManagement/resources/dsi_batterylow.svg
new file mode 100644
index 0000000..d337675
--- /dev/null
+++ b/src/frontend/qt_sdl/PowerManagement/resources/dsi_batterylow.svg
@@ -0,0 +1,171 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ width="12.236976mm"
+ height="6.2838535mm"
+ viewBox="0 0 12.236976 6.283854"
+ version="1.1"
+ id="svg5"
+ inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
+ sodipodi:docname="batterylow.svg"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg">
+ <sodipodi:namedview
+ id="namedview7"
+ pagecolor="#ff8100"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageshadow="2"
+ inkscape:pageopacity="1"
+ inkscape:pagecheckerboard="false"
+ inkscape:document-units="mm"
+ showgrid="true"
+ height="200mm"
+ showborder="true"
+ inkscape:showpageshadow="false"
+ borderlayer="false"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ inkscape:zoom="0.45254834"
+ inkscape:cx="325.93203"
+ inkscape:cy="232.01941"
+ inkscape:window-width="1920"
+ inkscape:window-height="1011"
+ inkscape:window-x="0"
+ inkscape:window-y="32"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="layer1">
+ <inkscape:grid
+ type="xygrid"
+ id="grid11"
+ spacingx="0.26458333"
+ spacingy="0.26458333"
+ originx="-91.777345"
+ originy="-47.128916" />
+ </sodipodi:namedview>
+ <defs
+ id="defs2" />
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(-91.777344,-47.12891)">
+ <rect
+ style="fill:#000000;fill-rule:evenodd;stroke-width:0.0211535"
+ id="rect153"
+ width="1.3229166"
+ height="2.9765623"
+ x="91.777344"
+ y="48.782555" />
+ <rect
+ style="fill:#000000;stroke-width:0.0162915"
+ id="rect177"
+ width="10.914062"
+ height="0.66145831"
+ x="93.100258"
+ y="47.12891" />
+ <rect
+ style="fill:#000000;stroke-width:0.0165364"
+ id="rect179"
+ width="0.66145831"
+ height="4.9609375"
+ x="93.100258"
+ y="47.790367" />
+ <rect
+ style="fill:#000000;stroke-width:0.0162915"
+ id="rect181"
+ width="10.914062"
+ height="0.66145873"
+ x="93.100258"
+ y="52.751305" />
+ <rect
+ style="fill:#000000;stroke-width:0.0165364"
+ id="rect183"
+ width="0.66145831"
+ height="4.9609375"
+ x="103.35286"
+ y="47.790367" />
+ <rect
+ style="fill:#ffffff;stroke-width:0.0162585"
+ id="rect318"
+ width="9.5911455"
+ height="0.49609375"
+ x="93.761719"
+ y="47.790367" />
+ <rect
+ style="fill:#ffffff;stroke-width:0.0158876"
+ id="rect408"
+ width="0.49609372"
+ height="3.96875"
+ x="93.761719"
+ y="48.286461" />
+ <rect
+ style="fill:#ffffff;stroke-width:0.015706"
+ id="rect410"
+ width="9.5911455"
+ height="0.49609351"
+ x="93.761719"
+ y="52.255211" />
+ <rect
+ style="fill:#ffffff;stroke-width:0.0158876"
+ id="rect412"
+ width="0.4960939"
+ height="3.9687498"
+ x="102.85677"
+ y="48.286461" />
+ <rect
+ style="fill:#ffffff;stroke-width:0.0224686"
+ id="rect414"
+ width="0.66145849"
+ height="3.9687498"
+ x="95.911453"
+ y="48.286461" />
+ <rect
+ style="fill:#ffffff;stroke-width:0.0158876"
+ id="rect414-3"
+ width="0.66145885"
+ height="3.9687498"
+ x="98.226562"
+ y="48.286461" />
+ <rect
+ style="fill:#ffffff;stroke-width:0.0158876"
+ id="rect414-6"
+ width="0.66145819"
+ height="3.9687498"
+ x="100.54166"
+ y="48.286461" />
+ <rect
+ style="fill:#999999;stroke-width:0.0165364"
+ id="rect1736"
+ width="1.6536455"
+ height="3.9687498"
+ x="94.257812"
+ y="48.286461" />
+ <rect
+ style="fill:#999999;stroke-width:0.0165364"
+ id="rect1738"
+ width="1.6536458"
+ height="3.96875"
+ x="96.572914"
+ y="48.286461" />
+ <rect
+ style="fill:#999999;stroke-width:0.0165364"
+ id="rect1740"
+ width="1.6536458"
+ height="3.96875"
+ x="98.888016"
+ y="48.286461" />
+ <rect
+ style="fill:#ff2a2a;stroke-width:0.0165364"
+ id="rect1742"
+ width="1.6536458"
+ height="3.96875"
+ x="101.20312"
+ y="48.286461" />
+ </g>
+</svg>
diff --git a/src/frontend/qt_sdl/QPathInput.h b/src/frontend/qt_sdl/QPathInput.h
index 1cb1f7c..beb618c 100644
--- a/src/frontend/qt_sdl/QPathInput.h
+++ b/src/frontend/qt_sdl/QPathInput.h
@@ -1,5 +1,5 @@
/*
- Copyright 2016-2021 Arisotura
+ Copyright 2016-2022 melonDS team
This file is part of melonDS.
diff --git a/src/frontend/qt_sdl/RAMInfoDialog.cpp b/src/frontend/qt_sdl/RAMInfoDialog.cpp
new file mode 100644
index 0000000..b13ff02
--- /dev/null
+++ b/src/frontend/qt_sdl/RAMInfoDialog.cpp
@@ -0,0 +1,302 @@
+/*
+ Copyright 2016-2021 Arisotura
+
+ This file is part of melonDS.
+
+ melonDS 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 3 of the License, or (at your option)
+ any later version.
+
+ melonDS 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 melonDS. If not, see http://www.gnu.org/licenses/.
+*/
+
+#include "RAMInfoDialog.h"
+#include "ui_RAMInfoDialog.h"
+
+#include "main.h"
+
+extern EmuThread* emuThread;
+
+s32 GetMainRAMValue(const u32& addr, const ramInfo_ByteType& byteType)
+{
+ switch (byteType)
+ {
+ case ramInfo_OneByte:
+ return *(s8*)(NDS::MainRAM + (addr&NDS::MainRAMMask));
+ case ramInfo_TwoBytes:
+ return *(s16*)(NDS::MainRAM + (addr&NDS::MainRAMMask));
+ case ramInfo_FourBytes:
+ return *(s32*)(NDS::MainRAM + (addr&NDS::MainRAMMask));
+ default:
+ return 0;
+ }
+}
+
+RAMInfoDialog* RAMInfoDialog::currentDlg = nullptr;
+
+RAMInfoDialog::RAMInfoDialog(QWidget* parent) : QDialog(parent), ui(new Ui::RAMInfoDialog)
+{
+ ui->setupUi(this);
+ setAttribute(Qt::WA_DeleteOnClose);
+
+ qRegisterMetaType<QVector<int>>("QVector<int>");
+ qRegisterMetaType<u32>("u32");
+ qRegisterMetaType<s32>("s32");
+ qRegisterMetaType<s16>("s16");
+ qRegisterMetaType<s8>("s8");
+
+ SearchThread = new RAMSearchThread(this);
+ connect(SearchThread, &RAMSearchThread::SetProgressbarValue, this, &RAMInfoDialog::SetProgressbarValue);
+ connect(SearchThread, &RAMSearchThread::finished, this, &RAMInfoDialog::OnSearchFinished);
+ // First search (Show everything in main ram)
+ SearchThread->Start(ramInfoSTh_SearchAll);
+
+ TableUpdater = new QTimer(this);
+ TableUpdater->setInterval(100);
+ connect(TableUpdater, &QTimer::timeout, this, &RAMInfoDialog::ShowRowsInTable);
+ TableUpdater->start();
+}
+
+RAMInfoDialog::~RAMInfoDialog()
+{
+ delete SearchThread;
+ if (TableUpdater->isActive())
+ TableUpdater->stop();
+ delete TableUpdater;
+ delete ui;
+}
+
+void RAMInfoDialog::OnSearchFinished()
+{
+ SearchThread->wait();
+ ui->btnSearch->setEnabled(true);
+ ui->ramTable->clearContents();
+ ui->ramTable->setRowCount(SearchThread->GetResults()->size());
+ ui->ramTable->verticalScrollBar()->setSliderPosition(0);
+ ui->txtFound->setText(QString("Found: %1").arg(SearchThread->GetResults()->size()));
+}
+
+void RAMInfoDialog::ShowRowsInTable()
+{
+ const u32& scrollValue = ui->ramTable->verticalScrollBar()->sliderPosition();
+ std::vector<ramInfo_RowData>* RowDataVector = SearchThread->GetResults();
+
+ for (u32 row = scrollValue; row < std::min<u32>(scrollValue+25, RowDataVector->size()); row++)
+ {
+ ramInfo_RowData& rowData = RowDataVector->at(row);
+ rowData.Update(SearchThread->GetSearchByteType());
+
+ if (ui->ramTable->item(row, ramInfo_Address) == nullptr)
+ {
+ // A new row
+ QTableWidgetItem* addressItem = new QTableWidgetItem(QString("%1").arg(rowData.Address, 8, 16));
+ QTableWidgetItem* valueItem = new QTableWidgetItem(QString("%1").arg(rowData.Value));
+ QTableWidgetItem* previousItem = new QTableWidgetItem(QString("%1").arg(rowData.Previous));
+
+ addressItem->setFlags(addressItem->flags() & ~Qt::ItemIsEditable);
+ valueItem->setFlags(valueItem->flags() | Qt::ItemIsEditable);
+ previousItem->setFlags(previousItem->flags() & ~Qt::ItemIsEditable);
+
+ ui->ramTable->setItem(row, ramInfo_Address, addressItem);
+ ui->ramTable->setItem(row, ramInfo_Value, valueItem);
+ ui->ramTable->setItem(row, ramInfo_Previous, previousItem);
+ }
+ else
+ {
+ // A row that exists
+ ui->ramTable->item(row, ramInfo_Address)->setText(QString("%1").arg(rowData.Address, 8, 16));
+ ui->ramTable->item(row, ramInfo_Value)->setText(QString("%1").arg(rowData.Value));
+ ui->ramTable->item(row, ramInfo_Previous)->setText(QString("%1").arg(rowData.Previous));
+ if (rowData.Value != rowData.Previous)
+ ui->ramTable->item(row, ramInfo_Previous)->setForeground(Qt::red);
+ }
+ }
+}
+
+void RAMInfoDialog::ClearTableContents()
+{
+ ui->ramTable->clearContents();
+ ui->ramTable->setRowCount(0);
+}
+
+void RAMInfoDialog::SetProgressbarValue(const u32& value)
+{
+ ui->progressBar->setValue(value);
+}
+
+void RAMInfoDialog::done(int r)
+{
+ QDialog::done(r);
+ closeDlg();
+}
+
+void RAMInfoDialog::on_btnSearch_clicked()
+{
+ ui->btnSearch->setEnabled(false);
+ ui->radiobtn1byte->setEnabled(false);
+ ui->radiobtn2bytes->setEnabled(false);
+ ui->radiobtn4bytes->setEnabled(false);
+
+ if (ui->txtSearch->text().isEmpty())
+ SearchThread->Start(ramInfoSTh_SearchAll);
+ else
+ SearchThread->Start(ui->txtSearch->text().toInt());
+
+ if (!TableUpdater->isActive())
+ TableUpdater->start();
+}
+
+void RAMInfoDialog::on_btnClear_clicked()
+{
+ SearchThread->Stop();
+ TableUpdater->stop();
+
+ ui->radiobtn1byte->setEnabled(true);
+ ui->radiobtn2bytes->setEnabled(true);
+ ui->radiobtn4bytes->setEnabled(true);
+
+ OnSearchFinished();
+}
+
+void RAMInfoDialog::on_radiobtn1byte_clicked()
+{
+ SearchThread->SetSearchByteType(ramInfo_OneByte);
+}
+
+void RAMInfoDialog::on_radiobtn2bytes_clicked()
+{
+ SearchThread->SetSearchByteType(ramInfo_TwoBytes);
+}
+
+void RAMInfoDialog::on_radiobtn4bytes_clicked()
+{
+ SearchThread->SetSearchByteType(ramInfo_FourBytes);
+}
+
+void RAMInfoDialog::on_ramTable_itemChanged(QTableWidgetItem *item)
+{
+ ramInfo_RowData& rowData = SearchThread->GetResults()->at(item->row());
+ s32 itemValue = item->text().toInt();
+
+ if (rowData.Value != itemValue)
+ rowData.SetValue(itemValue);
+}
+
+/**
+ * RAMSearchThread
+ */
+
+RAMSearchThread::RAMSearchThread(RAMInfoDialog* dialog) : Dialog(dialog)
+{
+ RowDataVector = new std::vector<ramInfo_RowData>();
+}
+
+RAMSearchThread::~RAMSearchThread()
+{
+ Stop();
+ if (RowDataVector)
+ {
+ delete RowDataVector;
+ RowDataVector = nullptr;
+ }
+}
+
+void RAMSearchThread::Start(const s32& searchValue, const ramInfoSTh_SearchMode& searchMode)
+{
+ SearchValue = searchValue;
+ SearchMode = searchMode;
+ start();
+}
+
+void RAMSearchThread::Start(const ramInfoSTh_SearchMode& searchMode)
+{
+ SearchMode = searchMode;
+ start();
+}
+
+void RAMSearchThread::Stop()
+{
+ SearchRunning = false;
+ RowDataVector->clear();
+ quit();
+ wait();
+}
+
+void RAMSearchThread::run()
+{
+ SearchRunning = true;
+ u32 progress = 0;
+
+ // Pause game running
+ emuThread->emuPause();
+
+ // For following search modes below, RowDataVector must be filled.
+ if (SearchMode == ramInfoSTh_SearchAll || RowDataVector->size() == 0)
+ {
+ // First search mode
+ for (u32 addr = 0x02000000; SearchRunning && addr < 0x02000000+NDS::MainRAMMaxSize; addr += SearchByteType)
+ {
+ const s32& value = GetMainRAMValue(addr, SearchByteType);
+
+ RowDataVector->push_back({ addr, value, value });
+
+ // A solution to prevent to call too many slot.
+ u32 newProgress = (int)((addr-0x02000000) / (NDS::MainRAMMaxSize-1.0f) * 100);
+ if (progress < newProgress)
+ {
+ progress = newProgress;
+ emit SetProgressbarValue(progress);
+ }
+ }
+ }
+
+ if (SearchMode == ramInfoSTh_Default)
+ {
+ // Next search mode
+ std::vector<ramInfo_RowData>* newRowDataVector = new std::vector<ramInfo_RowData>();
+ for (u32 row = 0; SearchRunning && row < RowDataVector->size(); row++)
+ {
+ const u32& addr = RowDataVector->at(row).Address;
+ const s32& value = GetMainRAMValue(addr, SearchByteType);
+
+ if (SearchValue == value)
+ newRowDataVector->push_back({ addr, value, value });
+
+ // A solution to prevent to call too many slot.
+ u32 newProgress = (int)(row / (RowDataVector->size()-1.0f) * 100);
+ if (progress < newProgress)
+ {
+ progress = newProgress;
+ emit SetProgressbarValue(progress);
+ }
+ }
+ delete RowDataVector;
+ RowDataVector = newRowDataVector;
+ }
+
+ // Unpause game running
+ emuThread->emuUnpause();
+
+ SearchRunning = false;
+}
+
+void RAMSearchThread::SetSearchByteType(const ramInfo_ByteType& bytetype)
+{
+ SearchByteType = bytetype;
+}
+
+ramInfo_ByteType RAMSearchThread::GetSearchByteType() const
+{
+ return SearchByteType;
+}
+
+std::vector<ramInfo_RowData>* RAMSearchThread::GetResults()
+{
+ return RowDataVector;
+}
diff --git a/src/frontend/qt_sdl/RAMInfoDialog.h b/src/frontend/qt_sdl/RAMInfoDialog.h
new file mode 100644
index 0000000..f44ae93
--- /dev/null
+++ b/src/frontend/qt_sdl/RAMInfoDialog.h
@@ -0,0 +1,161 @@
+/*
+ Copyright 2016-2021 Arisotura
+
+ This file is part of melonDS.
+
+ melonDS 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 3 of the License, or (at your option)
+ any later version.
+
+ melonDS 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 melonDS. If not, see http://www.gnu.org/licenses/.
+*/
+
+#ifndef RAMINFODIALOG_H
+#define RAMINFODIALOG_H
+
+#include <QDialog>
+#include <QTableWidget>
+#include <QScrollBar>
+#include <QThread>
+#include <QTimer>
+
+#include "types.h"
+#include "NDS.h"
+
+namespace Ui { class RAMInfoDialog; }
+class RAMInfoDialog;
+class RAMSearchThread;
+class RAMUpdateThread;
+
+enum ramInfo_ByteType
+{
+ ramInfo_OneByte = 1,
+ ramInfo_TwoBytes = 2,
+ ramInfo_FourBytes = 4
+};
+
+enum ramInfoSTh_SearchMode
+{
+ ramInfoSTh_Default,
+ ramInfoSTh_SearchAll
+};
+
+enum
+{
+ ramInfo_Address,
+ ramInfo_Value,
+ ramInfo_Previous
+};
+
+s32 GetMainRAMValue(const u32& addr, const ramInfo_ByteType& byteType);
+
+struct ramInfo_RowData
+{
+ u32 Address;
+ s32 Value;
+ s32 Previous;
+
+ void Update(const ramInfo_ByteType& byteType)
+ {
+ Value = GetMainRAMValue(Address, byteType);
+ }
+
+ void SetValue(const s32& value)
+ {
+ NDS::MainRAM[Address&NDS::MainRAMMask] = (u32)value;
+ Value = value;
+ }
+};
+
+class RAMInfoDialog : public QDialog
+{
+ Q_OBJECT
+
+public:
+ explicit RAMInfoDialog(QWidget* parent);
+ ~RAMInfoDialog();
+
+ static RAMInfoDialog* currentDlg;
+ static RAMInfoDialog* openDlg(QWidget* parent)
+ {
+ if (currentDlg)
+ {
+ currentDlg->activateWindow();
+ return currentDlg;
+ }
+
+ currentDlg = new RAMInfoDialog(parent);
+ currentDlg->show();
+ return currentDlg;
+ }
+ static void closeDlg()
+ {
+ currentDlg = nullptr;
+ }
+
+ s32 SearchValue = 0;
+
+ void ClearTableContents();
+
+private slots:
+ void done(int r);
+
+ void on_btnSearch_clicked();
+ void on_btnClear_clicked();
+ void on_radiobtn1byte_clicked();
+ void on_radiobtn2bytes_clicked();
+ void on_radiobtn4bytes_clicked();
+ void on_ramTable_itemChanged(QTableWidgetItem *item);
+
+ void OnSearchFinished();
+ void ShowRowsInTable();
+ void SetProgressbarValue(const u32& value);
+
+private:
+ Ui::RAMInfoDialog* ui;
+
+ RAMSearchThread* SearchThread;
+ QTimer* TableUpdater;
+};
+
+class RAMSearchThread : public QThread
+{
+ Q_OBJECT
+
+public:
+ explicit RAMSearchThread(RAMInfoDialog* dialog);
+ ~RAMSearchThread() override;
+
+ void Start(const s32& searchValue, const ramInfoSTh_SearchMode& searchMode = ramInfoSTh_Default);
+ void Start(const ramInfoSTh_SearchMode& searchMode);
+
+ void SetSearchByteType(const ramInfo_ByteType& bytetype);
+ ramInfo_ByteType GetSearchByteType() const;
+ std::vector<ramInfo_RowData>* GetResults();
+
+ void Stop();
+
+private:
+ void run();
+
+ RAMInfoDialog* Dialog;
+ bool SearchRunning = false;
+
+ ramInfoSTh_SearchMode SearchMode;
+ s32 SearchValue;
+ ramInfo_ByteType SearchByteType = ramInfo_OneByte;
+ std::vector<ramInfo_RowData>* RowDataVector = nullptr;
+
+ void ClearTableContents();
+
+signals:
+ void SetProgressbarValue(const u32& value);
+};
+
+#endif // RAMINFODIALOG_H
diff --git a/src/frontend/qt_sdl/RAMInfoDialog.ui b/src/frontend/qt_sdl/RAMInfoDialog.ui
new file mode 100644
index 0000000..46beaa5
--- /dev/null
+++ b/src/frontend/qt_sdl/RAMInfoDialog.ui
@@ -0,0 +1,237 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>RAMInfoDialog</class>
+ <widget class="QDialog" name="RAMInfoDialog">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>550</width>
+ <height>411</height>
+ </rect>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>550</width>
+ <height>411</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>550</width>
+ <height>411</height>
+ </size>
+ </property>
+ <property name="windowTitle">
+ <string>RAM info - melonDS</string>
+ </property>
+ <property name="modal">
+ <bool>false</bool>
+ </property>
+ <widget class="QGroupBox" name="groupBox">
+ <property name="geometry">
+ <rect>
+ <x>340</x>
+ <y>10</y>
+ <width>201</width>
+ <height>111</height>
+ </rect>
+ </property>
+ <property name="title">
+ <string>Search</string>
+ </property>
+ <widget class="QPushButton" name="btnSearch">
+ <property name="geometry">
+ <rect>
+ <x>130</x>
+ <y>20</y>
+ <width>61</width>
+ <height>23</height>
+ </rect>
+ </property>
+ <property name="text">
+ <string>Search</string>
+ </property>
+ </widget>
+ <widget class="QLineEdit" name="txtSearch">
+ <property name="geometry">
+ <rect>
+ <x>50</x>
+ <y>20</y>
+ <width>71</width>
+ <height>21</height>
+ </rect>
+ </property>
+ <property name="maxLength">
+ <number>5</number>
+ </property>
+ </widget>
+ <widget class="QPushButton" name="btnClear">
+ <property name="geometry">
+ <rect>
+ <x>120</x>
+ <y>80</y>
+ <width>71</width>
+ <height>23</height>
+ </rect>
+ </property>
+ <property name="text">
+ <string>Clear</string>
+ </property>
+ </widget>
+ <widget class="QLabel" name="labelValue">
+ <property name="geometry">
+ <rect>
+ <x>10</x>
+ <y>20</y>
+ <width>41</width>
+ <height>21</height>
+ </rect>
+ </property>
+ <property name="text">
+ <string>Value:</string>
+ </property>
+ </widget>
+ <widget class="QRadioButton" name="radiobtn1byte">
+ <property name="geometry">
+ <rect>
+ <x>10</x>
+ <y>50</y>
+ <width>90</width>
+ <height>16</height>
+ </rect>
+ </property>
+ <property name="text">
+ <string>1byte</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ <widget class="QRadioButton" name="radiobtn2bytes">
+ <property name="geometry">
+ <rect>
+ <x>10</x>
+ <y>70</y>
+ <width>90</width>
+ <height>16</height>
+ </rect>
+ </property>
+ <property name="text">
+ <string>2bytes</string>
+ </property>
+ </widget>
+ <widget class="QRadioButton" name="radiobtn4bytes">
+ <property name="geometry">
+ <rect>
+ <x>10</x>
+ <y>90</y>
+ <width>90</width>
+ <height>16</height>
+ </rect>
+ </property>
+ <property name="text">
+ <string>4bytes</string>
+ </property>
+ </widget>
+ </widget>
+ <widget class="QProgressBar" name="progressBar">
+ <property name="geometry">
+ <rect>
+ <x>10</x>
+ <y>380</y>
+ <width>321</width>
+ <height>23</height>
+ </rect>
+ </property>
+ <property name="maximum">
+ <number>100</number>
+ </property>
+ <property name="value">
+ <number>0</number>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <property name="textVisible">
+ <bool>true</bool>
+ </property>
+ <property name="format">
+ <string>%p%</string>
+ </property>
+ </widget>
+ <widget class="QTableWidget" name="ramTable">
+ <property name="geometry">
+ <rect>
+ <x>10</x>
+ <y>30</y>
+ <width>321</width>
+ <height>341</height>
+ </rect>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>321</width>
+ <height>341</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>321</width>
+ <height>341</height>
+ </size>
+ </property>
+ <property name="editTriggers">
+ <set>QAbstractItemView::AnyKeyPressed|QAbstractItemView::DoubleClicked</set>
+ </property>
+ <property name="alternatingRowColors">
+ <bool>true</bool>
+ </property>
+ <property name="selectionBehavior">
+ <enum>QAbstractItemView::SelectRows</enum>
+ </property>
+ <attribute name="verticalHeaderVisible">
+ <bool>false</bool>
+ </attribute>
+ <attribute name="verticalHeaderMinimumSectionSize">
+ <number>16</number>
+ </attribute>
+ <attribute name="verticalHeaderDefaultSectionSize">
+ <number>16</number>
+ </attribute>
+ <column>
+ <property name="text">
+ <string>Address</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Value</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Previous</string>
+ </property>
+ </column>
+ </widget>
+ <widget class="QLabel" name="txtFound">
+ <property name="geometry">
+ <rect>
+ <x>10</x>
+ <y>10</y>
+ <width>101</width>
+ <height>16</height>
+ </rect>
+ </property>
+ <property name="text">
+ <string>Found:</string>
+ </property>
+ </widget>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src/frontend/qt_sdl/ROMInfoDialog.cpp b/src/frontend/qt_sdl/ROMInfoDialog.cpp
index 9166efe..e82ec4b 100644
--- a/src/frontend/qt_sdl/ROMInfoDialog.cpp
+++ b/src/frontend/qt_sdl/ROMInfoDialog.cpp
@@ -1,5 +1,5 @@
/*
- Copyright 2016-2021 Arisotura, WaluigiWare64
+ Copyright 2016-2022 melonDS team
This file is part of melonDS.
@@ -45,14 +45,14 @@ ROMInfoDialog::ROMInfoDialog(QWidget* parent) : QDialog(parent), ui(new Ui::ROMI
u32 iconData[32 * 32];
- Frontend::ROMIcon(NDSCart::Banner.Icon, NDSCart::Banner.Palette, iconData);
+ ROMManager::ROMIcon(NDSCart::Banner.Icon, NDSCart::Banner.Palette, iconData);
iconImage = QImage(reinterpret_cast<unsigned char*>(iconData), 32, 32, QImage::Format_ARGB32).copy();
ui->iconImage->setPixmap(QPixmap::fromImage(iconImage));
if (NDSCart::Banner.Version == 0x103)
{
u32 animatedIconData[32 * 32 * 64] = {0};
- Frontend::AnimatedROMIcon(NDSCart::Banner.DSiIcon, NDSCart::Banner.DSiPalette, NDSCart::Banner.DSiSequence, animatedIconData, animatedSequence);
+ ROMManager::AnimatedROMIcon(NDSCart::Banner.DSiIcon, NDSCart::Banner.DSiPalette, NDSCart::Banner.DSiSequence, animatedIconData, animatedSequence);
for (int i = 0; i < 64; i++)
{
@@ -75,12 +75,12 @@ ROMInfoDialog::ROMInfoDialog(QWidget* parent) : QDialog(parent), ui(new Ui::ROMI
ui->iconTitle->setText(QString::fromUtf16(NDSCart::Banner.EnglishTitle));
- ui->japaneseTitle->setText(QString::fromUtf16(NDSCart::Banner.JapaneseTitle, 128));
- ui->englishTitle->setText(QString::fromUtf16(NDSCart::Banner.EnglishTitle, 128));
- ui->frenchTitle->setText(QString::fromUtf16(NDSCart::Banner.FrenchTitle, 128));
- ui->germanTitle->setText(QString::fromUtf16(NDSCart::Banner.GermanTitle, 128));
- ui->italianTitle->setText(QString::fromUtf16(NDSCart::Banner.ItalianTitle, 128));
- ui->spanishTitle->setText(QString::fromUtf16(NDSCart::Banner.SpanishTitle, 128));
+ ui->japaneseTitle->setText(QString::fromUtf16(NDSCart::Banner.JapaneseTitle));
+ ui->englishTitle->setText(QString::fromUtf16(NDSCart::Banner.EnglishTitle));
+ ui->frenchTitle->setText(QString::fromUtf16(NDSCart::Banner.FrenchTitle));
+ ui->germanTitle->setText(QString::fromUtf16(NDSCart::Banner.GermanTitle));
+ ui->italianTitle->setText(QString::fromUtf16(NDSCart::Banner.ItalianTitle));
+ ui->spanishTitle->setText(QString::fromUtf16(NDSCart::Banner.SpanishTitle));
if (NDSCart::Banner.Version > 1)
ui->chineseTitle->setText(QString::fromUtf16(NDSCart::Banner.ChineseTitle));
@@ -130,7 +130,7 @@ void ROMInfoDialog::on_saveIconButton_clicked()
{
QString filename = QFileDialog::getSaveFileName(this,
"Save Icon",
- Config::LastROMFolder,
+ QString::fromStdString(Config::LastROMFolder),
"PNG Images (*.png)");
if (filename.isEmpty())
return;
diff --git a/src/frontend/qt_sdl/ROMInfoDialog.h b/src/frontend/qt_sdl/ROMInfoDialog.h
index 5193554..fd036a0 100644
--- a/src/frontend/qt_sdl/ROMInfoDialog.h
+++ b/src/frontend/qt_sdl/ROMInfoDialog.h
@@ -1,5 +1,5 @@
/*
- Copyright 2016-2021 Arisotura, WaluigiWare64
+ Copyright 2016-2022 melonDS team
This file is part of melonDS.
@@ -25,7 +25,7 @@
#include <QImage>
#include "types.h"
-#include "FrontendUtil.h"
+#include "ROMManager.h"
namespace Ui { class ROMInfoDialog; }
class ROMInfoDialog;
@@ -58,7 +58,7 @@ public:
private slots:
void done(int r);
-
+
void on_saveIconButton_clicked();
void iconSetFrame(int frame);
diff --git a/src/frontend/qt_sdl/ROMInfoDialog.ui b/src/frontend/qt_sdl/ROMInfoDialog.ui
index 0c65cab..1c9d844 100644
--- a/src/frontend/qt_sdl/ROMInfoDialog.ui
+++ b/src/frontend/qt_sdl/ROMInfoDialog.ui
@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
- <width>427</width>
- <height>434</height>
+ <width>559</width>
+ <height>532</height>
</rect>
</property>
<property name="sizePolicy">
@@ -22,12 +22,6 @@
<layout class="QGridLayout" name="gridLayout">
<item row="2" column="0">
<widget class="QGroupBox" name="titles">
- <property name="sizePolicy">
- <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
<property name="title">
<string>Titles</string>
</property>
@@ -350,12 +344,6 @@
</item>
<item row="3" column="1">
<widget class="QGroupBox" name="filesystem">
- <property name="sizePolicy">
- <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
<property name="title">
<string>Filesystem</string>
</property>
@@ -441,12 +429,6 @@
</item>
<item row="3" column="0">
<widget class="QGroupBox" name="generalInfo">
- <property name="sizePolicy">
- <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
<property name="title">
<string>General info</string>
</property>
@@ -668,7 +650,7 @@
</layout>
</widget>
</item>
- <item row="0" column="3">
+ <item row="0" column="2">
<widget class="QGroupBox" name="dsiIconBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
@@ -742,43 +724,11 @@
</layout>
</widget>
</item>
- <item row="0" column="0">
- <spacer name="horizontalSpacer">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>55</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- <item row="0" column="4">
+ <item row="0" column="3">
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>40</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- <item row="0" column="2">
- <spacer name="horizontalSpacer_2">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>40</width>
- <height>20</height>
- </size>
- </property>
</spacer>
</item>
<item row="1" column="1">
@@ -788,6 +738,13 @@
</property>
</widget>
</item>
+ <item row="0" column="0">
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </spacer>
+ </item>
</layout>
</widget>
</item>
diff --git a/src/frontend/qt_sdl/ROMManager.cpp b/src/frontend/qt_sdl/ROMManager.cpp
new file mode 100644
index 0000000..716a454
--- /dev/null
+++ b/src/frontend/qt_sdl/ROMManager.cpp
@@ -0,0 +1,875 @@
+/*
+ Copyright 2016-2022 melonDS team
+
+ This file is part of melonDS.
+
+ melonDS 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 3 of the License, or (at your option)
+ any later version.
+
+ melonDS 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 melonDS. If not, see http://www.gnu.org/licenses/.
+*/
+
+#include <stdio.h>
+#include <string.h>
+
+#include <string>
+#include <utility>
+
+#ifdef ARCHIVE_SUPPORT_ENABLED
+#include "ArchiveUtil.h"
+#endif
+#include "ROMManager.h"
+#include "Config.h"
+#include "Platform.h"
+
+#include "NDS.h"
+#include "DSi.h"
+#include "SPI.h"
+#include "DSi_I2C.h"
+
+
+namespace ROMManager
+{
+
+int CartType = -1;
+std::string BaseROMDir = "";
+std::string BaseROMName = "";
+std::string BaseAssetName = "";
+
+int GBACartType = -1;
+std::string BaseGBAROMDir = "";
+std::string BaseGBAROMName = "";
+std::string BaseGBAAssetName = "";
+
+SaveManager* NDSSave = nullptr;
+SaveManager* GBASave = nullptr;
+
+bool SavestateLoaded = false;
+std::string PreviousSaveFile = "";
+
+ARCodeFile* CheatFile = nullptr;
+bool CheatsOn = false;
+
+
+int LastSep(std::string path)
+{
+ int i = path.length() - 1;
+ while (i >= 0)
+ {
+ if (path[i] == '/' || path[i] == '\\')
+ return i;
+
+ i--;
+ }
+
+ return -1;
+}
+
+std::string GetAssetPath(bool gba, std::string configpath, std::string ext, std::string file="")
+{
+ if (configpath.empty())
+ configpath = gba ? BaseGBAROMDir : BaseROMDir;
+
+ if (file.empty())
+ {
+ file = gba ? BaseGBAAssetName : BaseAssetName;
+ if (file.empty())
+ file = "firmware";
+ }
+
+ for (;;)
+ {
+ int i = configpath.length() - 1;
+ if (i < 0) break;
+ if (configpath[i] == '/' || configpath[i] == '\\')
+ configpath = configpath.substr(0, i);
+ else
+ break;
+ }
+
+ if (!configpath.empty())
+ configpath += "/";
+
+ return configpath + file + ext;
+}
+
+
+QString VerifyDSBIOS()
+{
+ FILE* f;
+ long len;
+
+ f = Platform::OpenLocalFile(Config::BIOS9Path, "rb");
+ if (!f) return "DS ARM9 BIOS was not found or could not be accessed. Check your emu settings.";
+
+ fseek(f, 0, SEEK_END);
+ len = ftell(f);
+ if (len != 0x1000)
+ {
+ fclose(f);
+ return "DS ARM9 BIOS is not a valid BIOS dump.";
+ }
+
+ fclose(f);
+
+ f = Platform::OpenLocalFile(Config::BIOS7Path, "rb");
+ if (!f) return "DS ARM7 BIOS was not found or could not be accessed. Check your emu settings.";
+
+ fseek(f, 0, SEEK_END);
+ len = ftell(f);
+ if (len != 0x4000)
+ {
+ fclose(f);
+ return "DS ARM7 BIOS is not a valid BIOS dump.";
+ }
+
+ fclose(f);
+
+ return "";
+}
+
+QString VerifyDSiBIOS()
+{
+ FILE* f;
+ long len;
+
+ // TODO: check the first 32 bytes
+
+ f = Platform::OpenLocalFile(Config::DSiBIOS9Path, "rb");
+ if (!f) return "DSi ARM9 BIOS was not found or could not be accessed. Check your emu settings.";
+
+ fseek(f, 0, SEEK_END);
+ len = ftell(f);
+ if (len != 0x10000)
+ {
+ fclose(f);
+ return "DSi ARM9 BIOS is not a valid BIOS dump.";
+ }
+
+ fclose(f);
+
+ f = Platform::OpenLocalFile(Config::DSiBIOS7Path, "rb");
+ if (!f) return "DSi ARM7 BIOS was not found or could not be accessed. Check your emu settings.";
+
+ fseek(f, 0, SEEK_END);
+ len = ftell(f);
+ if (len != 0x10000)
+ {
+ fclose(f);
+ return "DSi ARM7 BIOS is not a valid BIOS dump.";
+ }
+
+ fclose(f);
+
+ return "";
+}
+
+QString VerifyDSFirmware()
+{
+ FILE* f;
+ long len;
+
+ f = Platform::OpenLocalFile(Config::FirmwarePath, "rb");
+ if (!f) return "DS firmware was not found or could not be accessed. Check your emu settings.";
+
+ fseek(f, 0, SEEK_END);
+ len = ftell(f);
+ if (len == 0x20000)
+ {
+ // 128KB firmware, not bootable
+ fclose(f);
+ // TODO report it somehow? detect in core?
+ return "";
+ }
+ else if (len != 0x40000 && len != 0x80000)
+ {
+ fclose(f);
+ return "DS firmware is not a valid firmware dump.";
+ }
+
+ fclose(f);
+
+ return "";
+}
+
+QString VerifyDSiFirmware()
+{
+ FILE* f;
+ long len;
+
+ f = Platform::OpenLocalFile(Config::DSiFirmwarePath, "rb");
+ if (!f) return "DSi firmware was not found or could not be accessed. Check your emu settings.";
+
+ fseek(f, 0, SEEK_END);
+ len = ftell(f);
+ if (len != 0x20000)
+ {
+ // not 128KB
+ // TODO: check whether those work
+ fclose(f);
+ return "DSi firmware is not a valid firmware dump.";
+ }
+
+ fclose(f);
+
+ return "";
+}
+
+QString VerifyDSiNAND()
+{
+ FILE* f;
+ long len;
+
+ f = Platform::OpenLocalFile(Config::DSiNANDPath, "r+b");
+ if (!f) return "DSi NAND was not found or could not be accessed. Check your emu settings.";
+
+ // TODO: some basic checks
+ // check that it has the nocash footer, and all
+
+ fclose(f);
+
+ return "";
+}
+
+QString VerifySetup()
+{
+ QString res;
+
+ if (Config::ExternalBIOSEnable)
+ {
+ res = VerifyDSBIOS();
+ if (!res.isEmpty()) return res;
+ }
+
+ if (Config::ConsoleType == 1)
+ {
+ res = VerifyDSiBIOS();
+ if (!res.isEmpty()) return res;
+
+ if (Config::ExternalBIOSEnable)
+ {
+ res = VerifyDSiFirmware();
+ if (!res.isEmpty()) return res;
+ }
+
+ res = VerifyDSiNAND();
+ if (!res.isEmpty()) return res;
+ }
+ else
+ {
+ if (Config::ExternalBIOSEnable)
+ {
+ res = VerifyDSFirmware();
+ if (!res.isEmpty()) return res;
+ }
+ }
+
+ return "";
+}
+
+
+std::string GetSavestateName(int slot)
+{
+ std::string ext = ".ml";
+ ext += (char)('0'+slot);
+ return GetAssetPath(false, Config::SavestatePath, ext);
+}
+
+bool SavestateExists(int slot)
+{
+ std::string ssfile = GetSavestateName(slot);
+ return Platform::FileExists(ssfile);
+}
+
+bool LoadState(std::string filename)
+{
+ // backup
+ Savestate* backup = new Savestate("timewarp.mln", true);
+ NDS::DoSavestate(backup);
+ delete backup;
+
+ bool failed = false;
+
+ Savestate* state = new Savestate(filename, false);
+ if (state->Error)
+ {
+ delete state;
+
+ // current state might be crapoed, so restore from sane backup
+ state = new Savestate("timewarp.mln", false);
+ failed = true;
+ }
+
+ bool res = NDS::DoSavestate(state);
+ delete state;
+
+ if (!res)
+ {
+ failed = true;
+ state = new Savestate("timewarp.mln", false);
+ NDS::DoSavestate(state);
+ delete state;
+ }
+
+ if (failed) return false;
+
+ if (Config::SavestateRelocSRAM && NDSSave)
+ {
+ PreviousSaveFile = NDSSave->GetPath();
+
+ std::string savefile = filename.substr(LastSep(filename)+1);
+ savefile = GetAssetPath(false, Config::SaveFilePath, ".sav", savefile);
+ savefile += Platform::InstanceFileSuffix();
+ NDSSave->SetPath(savefile, true);
+ }
+
+ SavestateLoaded = true;
+
+ return true;
+}
+
+bool SaveState(std::string filename)
+{
+ Savestate* state = new Savestate(filename, true);
+ if (state->Error)
+ {
+ delete state;
+ return false;
+ }
+
+ NDS::DoSavestate(state);
+ delete state;
+
+ if (Config::SavestateRelocSRAM && NDSSave)
+ {
+ std::string savefile = filename.substr(LastSep(filename)+1);
+ savefile = GetAssetPath(false, Config::SaveFilePath, ".sav", savefile);
+ savefile += Platform::InstanceFileSuffix();
+ NDSSave->SetPath(savefile, false);
+ }
+
+ return true;
+}
+
+void UndoStateLoad()
+{
+ if (!SavestateLoaded) return;
+
+ // pray that this works
+ // what do we do if it doesn't???
+ // but it should work.
+ Savestate* backup = new Savestate("timewarp.mln", false);
+ NDS::DoSavestate(backup);
+ delete backup;
+
+ if (NDSSave && (!PreviousSaveFile.empty()))
+ {
+ NDSSave->SetPath(PreviousSaveFile, true);
+ }
+}
+
+
+void UnloadCheats()
+{
+ if (CheatFile)
+ {
+ delete CheatFile;
+ CheatFile = nullptr;
+ }
+}
+
+void LoadCheats()
+{
+ UnloadCheats();
+
+ std::string filename = GetAssetPath(false, Config::CheatFilePath, ".mch");
+
+ // TODO: check for error (malformed cheat file, ...)
+ CheatFile = new ARCodeFile(filename);
+
+ AREngine::SetCodeFile(CheatsOn ? CheatFile : nullptr);
+}
+
+void EnableCheats(bool enable)
+{
+ CheatsOn = enable;
+ if (CheatFile)
+ AREngine::SetCodeFile(CheatsOn ? CheatFile : nullptr);
+}
+
+ARCodeFile* GetCheatFile()
+{
+ return CheatFile;
+}
+
+
+void SetBatteryLevels()
+{
+ if (NDS::ConsoleType == 1)
+ {
+ DSi_BPTWL::SetBatteryLevel(Config::DSiBatteryLevel);
+ DSi_BPTWL::SetBatteryCharging(Config::DSiBatteryCharging);
+ }
+ else
+ {
+ SPI_Powerman::SetBatteryLevelOkay(Config::DSBatteryLevelOkay);
+ }
+}
+
+void Reset()
+{
+ NDS::SetConsoleType(Config::ConsoleType);
+ if (Config::ConsoleType == 1) EjectGBACart();
+ NDS::Reset();
+ SetBatteryLevels();
+
+ if ((CartType != -1) && NDSSave)
+ {
+ std::string oldsave = NDSSave->GetPath();
+ std::string newsave = GetAssetPath(false, Config::SaveFilePath, ".sav");
+ newsave += Platform::InstanceFileSuffix();
+ if (oldsave != newsave)
+ NDSSave->SetPath(newsave, false);
+ }
+
+ if ((GBACartType != -1) && GBASave)
+ {
+ std::string oldsave = GBASave->GetPath();
+ std::string newsave = GetAssetPath(true, Config::SaveFilePath, ".sav");
+ newsave += Platform::InstanceFileSuffix();
+ if (oldsave != newsave)
+ GBASave->SetPath(newsave, false);
+ }
+
+ if (!BaseROMName.empty())
+ {
+ if (Config::DirectBoot || NDS::NeedsDirectBoot())
+ {
+ NDS::SetupDirectBoot(BaseROMName);
+ }
+ }
+}
+
+
+bool LoadBIOS()
+{
+ NDS::SetConsoleType(Config::ConsoleType);
+
+ if (NDS::NeedsDirectBoot())
+ return false;
+
+ /*if (NDSSave) delete NDSSave;
+ NDSSave = nullptr;
+
+ CartType = -1;
+ BaseROMDir = "";
+ BaseROMName = "";
+ BaseAssetName = "";*/
+
+ NDS::Reset();
+ SetBatteryLevels();
+ return true;
+}
+
+
+bool LoadROM(QStringList filepath, bool reset)
+{
+ if (filepath.empty()) return false;
+
+ u8* filedata;
+ u32 filelen;
+
+ std::string basepath;
+ std::string romname;
+
+ int num = filepath.count();
+ if (num == 1)
+ {
+ // regular file
+
+ std::string filename = filepath.at(0).toStdString();
+ FILE* f = Platform::OpenFile(filename, "rb", true);
+ if (!f) return false;
+
+ fseek(f, 0, SEEK_END);
+ long len = ftell(f);
+ if (len > 0x40000000)
+ {
+ fclose(f);
+ return false;
+ }
+
+ fseek(f, 0, SEEK_SET);
+ filedata = new u8[len];
+ size_t nread = fread(filedata, (size_t)len, 1, f);
+ if (nread != 1)
+ {
+ fclose(f);
+ delete[] filedata;
+ return false;
+ }
+
+ fclose(f);
+ filelen = (u32)len;
+
+ int pos = LastSep(filename);
+ basepath = filename.substr(0, pos);
+ romname = filename.substr(pos+1);
+ }
+#ifdef ARCHIVE_SUPPORT_ENABLED
+ else if (num == 2)
+ {
+ // file inside archive
+
+ u32 lenread = Archive::ExtractFileFromArchive(filepath.at(0), filepath.at(1), &filedata, &filelen);
+ if (lenread < 0) return false;
+ if (!filedata) return false;
+ if (lenread != filelen)
+ {
+ delete[] filedata;
+ return false;
+ }
+
+ std::string std_archivepath = filepath.at(0).toStdString();
+ basepath = std_archivepath.substr(0, LastSep(std_archivepath));
+
+ std::string std_romname = filepath.at(1).toStdString();
+ romname = std_romname.substr(LastSep(std_romname)+1);
+ }
+#endif
+ else
+ return false;
+
+ if (NDSSave) delete NDSSave;
+ NDSSave = nullptr;
+
+ BaseROMDir = basepath;
+ BaseROMName = romname;
+ BaseAssetName = romname.substr(0, romname.rfind('.'));
+
+ if (reset)
+ {
+ NDS::SetConsoleType(Config::ConsoleType);
+ NDS::EjectCart();
+ NDS::Reset();
+ SetBatteryLevels();
+ }
+
+ u32 savelen = 0;
+ u8* savedata = nullptr;
+
+ std::string savname = GetAssetPath(false, Config::SaveFilePath, ".sav");
+ std::string origsav = savname;
+ savname += Platform::InstanceFileSuffix();
+
+ FILE* sav = Platform::OpenFile(savname, "rb", true);
+ if (!sav) sav = Platform::OpenFile(origsav, "rb", true);
+ if (sav)
+ {
+ fseek(sav, 0, SEEK_END);
+ savelen = (u32)ftell(sav);
+
+ fseek(sav, 0, SEEK_SET);
+ savedata = new u8[savelen];
+ fread(savedata, savelen, 1, sav);
+ fclose(sav);
+ }
+
+ bool res = NDS::LoadCart(filedata, filelen, savedata, savelen);
+ if (res && reset)
+ {
+ if (Config::DirectBoot || NDS::NeedsDirectBoot())
+ {
+ NDS::SetupDirectBoot(romname);
+ }
+ }
+
+ if (res)
+ {
+ CartType = 0;
+ NDSSave = new SaveManager(savname);
+
+ LoadCheats();
+ }
+
+ if (savedata) delete[] savedata;
+ delete[] filedata;
+ return res;
+}
+
+void EjectCart()
+{
+ if (NDSSave) delete NDSSave;
+ NDSSave = nullptr;
+
+ UnloadCheats();
+
+ NDS::EjectCart();
+
+ CartType = -1;
+ BaseROMDir = "";
+ BaseROMName = "";
+ BaseAssetName = "";
+}
+
+bool CartInserted()
+{
+ return CartType != -1;
+}
+
+QString CartLabel()
+{
+ if (CartType == -1)
+ return "(none)";
+
+ QString ret = QString::fromStdString(BaseROMName);
+
+ int maxlen = 32;
+ if (ret.length() > maxlen)
+ ret = ret.left(maxlen-6) + "..." + ret.right(3);
+
+ return ret;
+}
+
+
+bool LoadGBAROM(QStringList filepath)
+{
+ if (Config::ConsoleType == 1) return false;
+ if (filepath.empty()) return false;
+
+ u8* filedata;
+ u32 filelen;
+
+ std::string basepath;
+ std::string romname;
+
+ int num = filepath.count();
+ if (num == 1)
+ {
+ // regular file
+
+ std::string filename = filepath.at(0).toStdString();
+ FILE* f = Platform::OpenFile(filename, "rb", true);
+ if (!f) return false;
+
+ fseek(f, 0, SEEK_END);
+ long len = ftell(f);
+ if (len > 0x40000000)
+ {
+ fclose(f);
+ return false;
+ }
+
+ fseek(f, 0, SEEK_SET);
+ filedata = new u8[len];
+ size_t nread = fread(filedata, (size_t)len, 1, f);
+ if (nread != 1)
+ {
+ fclose(f);
+ delete[] filedata;
+ return false;
+ }
+
+ fclose(f);
+ filelen = (u32)len;
+
+ int pos = LastSep(filename);
+ basepath = filename.substr(0, pos);
+ romname = filename.substr(pos+1);
+ }
+#ifdef ARCHIVE_SUPPORT_ENABLED
+ else if (num == 2)
+ {
+ // file inside archive
+
+ u32 lenread = Archive::ExtractFileFromArchive(filepath.at(0), filepath.at(1), &filedata, &filelen);
+ if (lenread < 0) return false;
+ if (!filedata) return false;
+ if (lenread != filelen)
+ {
+ delete[] filedata;
+ return false;
+ }
+
+ std::string std_archivepath = filepath.at(0).toStdString();
+ basepath = std_archivepath.substr(0, LastSep(std_archivepath));
+
+ std::string std_romname = filepath.at(1).toStdString();
+ romname = std_romname.substr(LastSep(std_romname)+1);
+ }
+#endif
+ else
+ return false;
+
+ if (GBASave) delete GBASave;
+ GBASave = nullptr;
+
+ BaseGBAROMDir = basepath;
+ BaseGBAROMName = romname;
+ BaseGBAAssetName = romname.substr(0, romname.rfind('.'));
+
+ u32 savelen = 0;
+ u8* savedata = nullptr;
+
+ std::string savname = GetAssetPath(true, Config::SaveFilePath, ".sav");
+ std::string origsav = savname;
+ savname += Platform::InstanceFileSuffix();
+
+ FILE* sav = Platform::OpenFile(savname, "rb", true);
+ if (!sav) sav = Platform::OpenFile(origsav, "rb", true);
+ if (sav)
+ {
+ fseek(sav, 0, SEEK_END);
+ savelen = (u32)ftell(sav);
+
+ fseek(sav, 0, SEEK_SET);
+ savedata = new u8[savelen];
+ fread(savedata, savelen, 1, sav);
+ fclose(sav);
+ }
+
+ bool res = NDS::LoadGBACart(filedata, filelen, savedata, savelen);
+
+ if (res)
+ {
+ GBACartType = 0;
+ GBASave = new SaveManager(savname);
+ }
+
+ if (savedata) delete[] savedata;
+ delete[] filedata;
+ return res;
+}
+
+void LoadGBAAddon(int type)
+{
+ if (Config::ConsoleType == 1) return;
+
+ if (GBASave) delete GBASave;
+ GBASave = nullptr;
+
+ NDS::LoadGBAAddon(type);
+
+ GBACartType = type;
+ BaseGBAROMDir = "";
+ BaseGBAROMName = "";
+ BaseGBAAssetName = "";
+}
+
+void EjectGBACart()
+{
+ if (GBASave) delete GBASave;
+ GBASave = nullptr;
+
+ NDS::EjectGBACart();
+
+ GBACartType = -1;
+ BaseGBAROMDir = "";
+ BaseGBAROMName = "";
+ BaseGBAAssetName = "";
+}
+
+bool GBACartInserted()
+{
+ return GBACartType != -1;
+}
+
+QString GBACartLabel()
+{
+ if (Config::ConsoleType == 1) return "none (DSi)";
+
+ switch (GBACartType)
+ {
+ case 0:
+ {
+ QString ret = QString::fromStdString(BaseGBAROMName);
+
+ int maxlen = 32;
+ if (ret.length() > maxlen)
+ ret = ret.left(maxlen-6) + "..." + ret.right(3);
+
+ return ret;
+ }
+
+ case NDS::GBAAddon_RAMExpansion:
+ return "Memory expansion";
+ }
+
+ return "(none)";
+}
+
+
+void ROMIcon(u8 (&data)[512], u16 (&palette)[16], u32* iconRef)
+{
+ int index = 0;
+ for (int i = 0; i < 4; i++)
+ {
+ for (int j = 0; j < 4; j++)
+ {
+ for (int k = 0; k < 8; k++)
+ {
+ for (int l = 0; l < 8; l++)
+ {
+ u8 pal_index = index % 2 ? data[index/2] >> 4 : data[index/2] & 0x0F;
+ u8 r = ((palette[pal_index] >> 0) & 0x1F) * 255 / 31;
+ u8 g = ((palette[pal_index] >> 5) & 0x1F) * 255 / 31;
+ u8 b = ((palette[pal_index] >> 10) & 0x1F) * 255 / 31;
+ u8 a = pal_index ? 255: 0;
+ u32* row = &iconRef[256 * i + 32 * k + 8 * j];
+ row[l] = (a << 24) | (r << 16) | (g << 8) | b;
+ index++;
+ }
+ }
+ }
+ }
+}
+
+#define SEQ_FLIPV(i) ((i & 0b1000000000000000) >> 15)
+#define SEQ_FLIPH(i) ((i & 0b0100000000000000) >> 14)
+#define SEQ_PAL(i) ((i & 0b0011100000000000) >> 11)
+#define SEQ_BMP(i) ((i & 0b0000011100000000) >> 8)
+#define SEQ_DUR(i) ((i & 0b0000000011111111) >> 0)
+
+void AnimatedROMIcon(u8 (&data)[8][512], u16 (&palette)[8][16], u16 (&sequence)[64], u32 (&animatedTexRef)[32 * 32 * 64], std::vector<int> &animatedSequenceRef)
+{
+ for (int i = 0; i < 64; i++)
+ {
+ if (!sequence[i])
+ break;
+ u32* frame = &animatedTexRef[32 * 32 * i];
+ ROMIcon(data[SEQ_BMP(sequence[i])], palette[SEQ_PAL(sequence[i])], frame);
+
+ if (SEQ_FLIPH(sequence[i]))
+ {
+ for (int x = 0; x < 32; x++)
+ {
+ for (int y = 0; y < 32/2; y++)
+ {
+ std::swap(frame[x * 32 + y], frame[x * 32 + (32 - 1 - y)]);
+ }
+ }
+ }
+ if (SEQ_FLIPV(sequence[i]))
+ {
+ for (int x = 0; x < 32/2; x++)
+ {
+ for (int y = 0; y < 32; y++)
+ {
+ std::swap(frame[x * 32 + y], frame[(32 - 1 - x) * 32 + y]);
+ }
+ }
+ }
+
+ for (int j = 0; j < SEQ_DUR(sequence[i]); j++)
+ animatedSequenceRef.push_back(i);
+ }
+}
+
+}
diff --git a/src/frontend/qt_sdl/ROMManager.h b/src/frontend/qt_sdl/ROMManager.h
new file mode 100644
index 0000000..9e82a5b
--- /dev/null
+++ b/src/frontend/qt_sdl/ROMManager.h
@@ -0,0 +1,66 @@
+/*
+ Copyright 2016-2022 melonDS team
+
+ This file is part of melonDS.
+
+ melonDS 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 3 of the License, or (at your option)
+ any later version.
+
+ melonDS 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 melonDS. If not, see http://www.gnu.org/licenses/.
+*/
+
+#ifndef ROMMANAGER_H
+#define ROMMANAGER_H
+
+#include "types.h"
+#include "SaveManager.h"
+#include "AREngine.h"
+
+#include <string>
+#include <vector>
+
+namespace ROMManager
+{
+
+extern SaveManager* NDSSave;
+extern SaveManager* GBASave;
+
+QString VerifySetup();
+void Reset();
+bool LoadBIOS();
+
+bool LoadROM(QStringList filepath, bool reset);
+void EjectCart();
+bool CartInserted();
+QString CartLabel();
+
+bool LoadGBAROM(QStringList filepath);
+void LoadGBAAddon(int type);
+void EjectGBACart();
+bool GBACartInserted();
+QString GBACartLabel();
+
+std::string GetSavestateName(int slot);
+bool SavestateExists(int slot);
+bool LoadState(std::string filename);
+bool SaveState(std::string filename);
+void UndoStateLoad();
+
+void EnableCheats(bool enable);
+ARCodeFile* GetCheatFile();
+
+void ROMIcon(u8 (&data)[512], u16 (&palette)[16], u32* iconRef);
+void AnimatedROMIcon(u8 (&data)[8][512], u16 (&palette)[8][16],
+ u16 (&sequence)[64], u32 (&animatedTexRef)[32 * 32 * 64],
+ std::vector<int> &animatedSequenceRef);
+
+}
+
+#endif // ROMMANAGER_H
diff --git a/src/frontend/qt_sdl/SaveManager.cpp b/src/frontend/qt_sdl/SaveManager.cpp
new file mode 100644
index 0000000..d9e2138
--- /dev/null
+++ b/src/frontend/qt_sdl/SaveManager.cpp
@@ -0,0 +1,194 @@
+/*
+ Copyright 2016-2022 melonDS team
+
+ This file is part of melonDS.
+
+ melonDS 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 3 of the License, or (at your option)
+ any later version.
+
+ melonDS 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 melonDS. If not, see http://www.gnu.org/licenses/.
+*/
+
+#include <stdio.h>
+#include <string.h>
+
+#include "SaveManager.h"
+#include "Platform.h"
+
+
+SaveManager::SaveManager(std::string path) : QThread()
+{
+ SecondaryBuffer = nullptr;
+ SecondaryBufferLength = 0;
+ SecondaryBufferLock = new QMutex();
+
+ Running = false;
+
+ Path = path;
+
+ Buffer = nullptr;
+ Length = 0;
+ FlushRequested = false;
+
+ FlushVersion = 0;
+ PreviousFlushVersion = 0;
+ TimeAtLastFlushRequest = 0;
+
+ if (!path.empty())
+ {
+ Running = true;
+ start();
+ }
+}
+
+SaveManager::~SaveManager()
+{
+ if (Running)
+ {
+ Running = false;
+ wait();
+ FlushSecondaryBuffer();
+ }
+
+ if (SecondaryBuffer) delete[] SecondaryBuffer;
+
+ delete SecondaryBufferLock;
+
+ if (Buffer) delete[] Buffer;
+}
+
+std::string SaveManager::GetPath()
+{
+ return Path;
+}
+
+void SaveManager::SetPath(std::string path, bool reload)
+{
+ Path = path;
+
+ if (reload)
+ {
+ FILE* f = Platform::OpenFile(Path, "rb", true);
+ if (f)
+ {
+ fread(Buffer, 1, Length, f);
+ fclose(f);
+ }
+ }
+ else
+ FlushRequested = true;
+}
+
+void SaveManager::RequestFlush(const u8* savedata, u32 savelen, u32 writeoffset, u32 writelen)
+{
+ if (Length != savelen)
+ {
+ if (Buffer) delete[] Buffer;
+
+ Length = savelen;
+ Buffer = new u8[Length];
+
+ memcpy(Buffer, savedata, Length);
+ }
+ else
+ {
+ if ((writeoffset+writelen) > savelen)
+ {
+ u32 len = savelen - writeoffset;
+ memcpy(&Buffer[writeoffset], &savedata[writeoffset], len);
+ len = writelen - len;
+ if (len > savelen) len = savelen;
+ memcpy(&Buffer[0], &savedata[0], len);
+ }
+ else
+ {
+ memcpy(&Buffer[writeoffset], &savedata[writeoffset], writelen);
+ }
+ }
+
+ FlushRequested = true;
+}
+
+void SaveManager::CheckFlush()
+{
+ if (!FlushRequested) return;
+
+ SecondaryBufferLock->lock();
+
+ printf("SaveManager: Flush requested\n");
+
+ if (SecondaryBufferLength != Length)
+ {
+ if (SecondaryBuffer) delete[] SecondaryBuffer;
+
+ SecondaryBufferLength = Length;
+ SecondaryBuffer = new u8[SecondaryBufferLength];
+ }
+
+ memcpy(SecondaryBuffer, Buffer, Length);
+
+ FlushRequested = false;
+ FlushVersion++;
+ TimeAtLastFlushRequest = time(nullptr);
+
+ SecondaryBufferLock->unlock();
+}
+
+void SaveManager::run()
+{
+ for (;;)
+ {
+ QThread::msleep(100);
+
+ if (!Running) return;
+
+ // We debounce for two seconds after last flush request to ensure that writing has finished.
+ if (TimeAtLastFlushRequest == 0 || difftime(time(nullptr), TimeAtLastFlushRequest) < 2)
+ {
+ continue;
+ }
+
+ FlushSecondaryBuffer();
+ }
+}
+
+void SaveManager::FlushSecondaryBuffer(u8* dst, u32 dstLength)
+{
+ if (!SecondaryBuffer) return;
+
+ // When flushing to a file, there's no point in re-writing the exact same data.
+ if (!dst && !NeedsFlush()) return;
+ // When flushing to memory, we don't know if dst already has any data so we only check that we CAN flush.
+ if (dst && dstLength < SecondaryBufferLength) return;
+
+ SecondaryBufferLock->lock();
+ if (dst)
+ {
+ memcpy(dst, SecondaryBuffer, SecondaryBufferLength);
+ }
+ else
+ {
+ FILE* f = Platform::OpenFile(Path, "wb");
+ if (f)
+ {
+ printf("SaveManager: Written\n");
+ fwrite(SecondaryBuffer, SecondaryBufferLength, 1, f);
+ fclose(f);
+ }
+ }
+ PreviousFlushVersion = FlushVersion;
+ TimeAtLastFlushRequest = 0;
+ SecondaryBufferLock->unlock();
+}
+
+bool SaveManager::NeedsFlush()
+{
+ return FlushVersion != PreviousFlushVersion;
+}
diff --git a/src/frontend/qt_sdl/SaveManager.h b/src/frontend/qt_sdl/SaveManager.h
new file mode 100644
index 0000000..0d38f4b
--- /dev/null
+++ b/src/frontend/qt_sdl/SaveManager.h
@@ -0,0 +1,70 @@
+/*
+ Copyright 2016-2022 melonDS team
+
+ This file is part of melonDS.
+
+ melonDS 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 3 of the License, or (at your option)
+ any later version.
+
+ melonDS 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 melonDS. If not, see http://www.gnu.org/licenses/.
+*/
+
+#ifndef SAVEMANAGER_H
+#define SAVEMANAGER_H
+
+#include <string>
+#include <unistd.h>
+#include <time.h>
+#include <atomic>
+#include <QThread>
+#include <QMutex>
+
+#include "types.h"
+
+class SaveManager : public QThread
+{
+ Q_OBJECT
+ void run() override;
+
+public:
+ SaveManager(std::string path);
+ ~SaveManager();
+
+ std::string GetPath();
+ void SetPath(std::string path, bool reload);
+
+ void RequestFlush(const u8* savedata, u32 savelen, u32 writeoffset, u32 writelen);
+ void CheckFlush();
+
+ bool NeedsFlush();
+ void FlushSecondaryBuffer(u8* dst = nullptr, u32 dstLength = 0);
+
+private:
+ std::string Path;
+
+ std::atomic_bool Running;
+
+ u8* Buffer;
+ u32 Length;
+ bool FlushRequested;
+
+ QMutex* SecondaryBufferLock;
+ u8* SecondaryBuffer;
+ u32 SecondaryBufferLength;
+
+ time_t TimeAtLastFlushRequest;
+
+ // We keep versions in case the user closes the application before
+ // a flush cycle is finished.
+ u32 PreviousFlushVersion;
+ u32 FlushVersion;
+};
+
+#endif // SAVEMANAGER_H
diff --git a/src/frontend/qt_sdl/TitleManagerDialog.cpp b/src/frontend/qt_sdl/TitleManagerDialog.cpp
index 0a5e65d..8087ee6 100644
--- a/src/frontend/qt_sdl/TitleManagerDialog.cpp
+++ b/src/frontend/qt_sdl/TitleManagerDialog.cpp
@@ -1,5 +1,5 @@
/*
- Copyright 2016-2021 Arisotura
+ Copyright 2016-2022 melonDS team
This file is part of melonDS.
@@ -23,7 +23,7 @@
#include "types.h"
#include "Platform.h"
#include "Config.h"
-#include "FrontendUtil.h"
+#include "ROMManager.h"
#include "DSi_NAND.h"
#include "TitleManagerDialog.h"
@@ -31,7 +31,7 @@
#include "ui_TitleImportDialog.h"
-FILE* TitleManagerDialog::curNAND = nullptr;
+bool TitleManagerDialog::NANDInited = false;
TitleManagerDialog* TitleManagerDialog::currentDlg = nullptr;
extern std::string EmuDirectory;
@@ -111,7 +111,7 @@ void TitleManagerDialog::createTitleItem(u32 category, u32 titleid)
DSi_NAND::GetTitleInfo(category, titleid, version, &header, &banner);
u32 icondata[32*32];
- Frontend::ROMIcon(banner.Icon, banner.Palette, icondata);
+ ROMManager::ROMIcon(banner.Icon, banner.Palette, icondata);
QImage iconimg((const uchar*)icondata, 32, 32, QImage::Format_ARGB32);
QIcon icon(QPixmap::fromImage(iconimg.copy()));
@@ -136,6 +136,8 @@ void TitleManagerDialog::createTitleItem(u32 category, u32 titleid)
bool TitleManagerDialog::openNAND()
{
+ NANDInited = false;
+
FILE* bios7i = Platform::OpenLocalFile(Config::DSiBIOS7Path, "rb");
if (!bios7i)
return false;
@@ -145,28 +147,21 @@ bool TitleManagerDialog::openNAND()
fread(es_keyY, 16, 1, bios7i);
fclose(bios7i);
- curNAND = Platform::OpenLocalFile(Config::DSiNANDPath, "r+b");
- if (!curNAND)
- return false;
-
- if (!DSi_NAND::Init(curNAND, es_keyY))
+ if (!DSi_NAND::Init(es_keyY))
{
- fclose(curNAND);
- curNAND = nullptr;
return false;
}
+ NANDInited = true;
return true;
}
void TitleManagerDialog::closeNAND()
{
- if (curNAND)
+ if (NANDInited)
{
DSi_NAND::DeInit();
-
- fclose(curNAND);
- curNAND = nullptr;
+ NANDInited = false;
}
}
diff --git a/src/frontend/qt_sdl/TitleManagerDialog.h b/src/frontend/qt_sdl/TitleManagerDialog.h
index 682362a..cba7047 100644
--- a/src/frontend/qt_sdl/TitleManagerDialog.h
+++ b/src/frontend/qt_sdl/TitleManagerDialog.h
@@ -1,5 +1,5 @@
/*
- Copyright 2016-2021 Arisotura
+ Copyright 2016-2022 melonDS team
This file is part of melonDS.
@@ -45,7 +45,7 @@ public:
explicit TitleManagerDialog(QWidget* parent);
~TitleManagerDialog();
- static FILE* curNAND;
+ static bool NANDInited;
static bool openNAND();
static void closeNAND();
diff --git a/src/frontend/qt_sdl/VideoSettingsDialog.cpp b/src/frontend/qt_sdl/VideoSettingsDialog.cpp
index 0c3f13e..87a796d 100644
--- a/src/frontend/qt_sdl/VideoSettingsDialog.cpp
+++ b/src/frontend/qt_sdl/VideoSettingsDialog.cpp
@@ -1,5 +1,5 @@
/*
- Copyright 2016-2021 Arisotura
+ Copyright 2016-2022 melonDS team
This file is part of melonDS.
diff --git a/src/frontend/qt_sdl/VideoSettingsDialog.h b/src/frontend/qt_sdl/VideoSettingsDialog.h
index 4503012..527cc93 100644
--- a/src/frontend/qt_sdl/VideoSettingsDialog.h
+++ b/src/frontend/qt_sdl/VideoSettingsDialog.h
@@ -1,5 +1,5 @@
/*
- Copyright 2016-2021 Arisotura
+ Copyright 2016-2022 melonDS team
This file is part of melonDS.
diff --git a/src/frontend/qt_sdl/WifiSettingsDialog.cpp b/src/frontend/qt_sdl/WifiSettingsDialog.cpp
index d438179..9bf265e 100644
--- a/src/frontend/qt_sdl/WifiSettingsDialog.cpp
+++ b/src/frontend/qt_sdl/WifiSettingsDialog.cpp
@@ -1,5 +1,5 @@
/*
- Copyright 2016-2021 Arisotura
+ Copyright 2016-2022 melonDS team
This file is part of melonDS.
@@ -50,12 +50,12 @@ WifiSettingsDialog::WifiSettingsDialog(QWidget* parent) : QDialog(parent), ui(ne
ui->setupUi(this);
setAttribute(Qt::WA_DeleteOnClose);
- LAN_Socket::Init();
haspcap = LAN_PCap::Init(false);
ui->rbDirectMode->setText("Direct mode (requires " PCAP_NAME " and ethernet connection)");
- ui->cbBindAnyAddr->setChecked(Config::SocketBindAnyAddr != 0);
+ ui->lblAdapterMAC->setText("(none)");
+ ui->lblAdapterIP->setText("(none)");
int sel = 0;
for (int i = 0; i < LAN_PCap::NumAdapters; i++)
@@ -64,13 +64,14 @@ WifiSettingsDialog::WifiSettingsDialog(QWidget* parent) : QDialog(parent), ui(ne
ui->cbxDirectAdapter->addItem(QString(adapter->FriendlyName));
- if (!strncmp(adapter->DeviceName, Config::LANDevice, 128))
+ if (!strncmp(adapter->DeviceName, Config::LANDevice.c_str(), 128))
sel = i;
}
ui->cbxDirectAdapter->setCurrentIndex(sel);
- ui->rbDirectMode->setChecked(Config::DirectLAN != 0);
- ui->rbIndirectMode->setChecked(Config::DirectLAN == 0);
+ // errrr???
+ ui->rbDirectMode->setChecked(Config::DirectLAN);
+ ui->rbIndirectMode->setChecked(!Config::DirectLAN);
if (!haspcap) ui->rbDirectMode->setEnabled(false);
updateAdapterControls();
@@ -87,19 +88,17 @@ void WifiSettingsDialog::done(int r)
if (r == QDialog::Accepted)
{
- Config::SocketBindAnyAddr = ui->cbBindAnyAddr->isChecked() ? 1:0;
- Config::DirectLAN = ui->rbDirectMode->isChecked() ? 1:0;
+ Config::DirectLAN = ui->rbDirectMode->isChecked();
int sel = ui->cbxDirectAdapter->currentIndex();
if (sel < 0 || sel >= LAN_PCap::NumAdapters) sel = 0;
if (LAN_PCap::NumAdapters < 1)
{
- Config::LANDevice[0] = '\0';
+ Config::LANDevice = "";
}
else
{
- strncpy(Config::LANDevice, LAN_PCap::Adapters[sel].DeviceName, 127);
- Config::LANDevice[127] = '\0';
+ Config::LANDevice = LAN_PCap::Adapters[sel].DeviceName;
}
Config::Save();
diff --git a/src/frontend/qt_sdl/WifiSettingsDialog.h b/src/frontend/qt_sdl/WifiSettingsDialog.h
index a7cf538..da94924 100644
--- a/src/frontend/qt_sdl/WifiSettingsDialog.h
+++ b/src/frontend/qt_sdl/WifiSettingsDialog.h
@@ -1,5 +1,5 @@
/*
- Copyright 2016-2021 Arisotura
+ Copyright 2016-2022 melonDS team
This file is part of melonDS.
diff --git a/src/frontend/qt_sdl/WifiSettingsDialog.ui b/src/frontend/qt_sdl/WifiSettingsDialog.ui
index 0897059..444e1d5 100644
--- a/src/frontend/qt_sdl/WifiSettingsDialog.ui
+++ b/src/frontend/qt_sdl/WifiSettingsDialog.ui
@@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>572</width>
- <height>273</height>
+ <height>217</height>
</rect>
</property>
<property name="sizePolicy">
@@ -26,16 +26,26 @@
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
- <string>Local</string>
+ <string>Network mode</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
- <widget class="QCheckBox" name="cbBindAnyAddr">
+ <widget class="QRadioButton" name="rbIndirectMode">
+ <property name="whatsThis">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Indirect mode uses libslirp. It requires no extra setup and is easy to use.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="text">
+ <string>Indirect mode (uses libslirp, recommended)</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QRadioButton" name="rbDirectMode">
<property name="whatsThis">
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Enabling this allows (theoretically) playing local multiplayer games over a local network. It may or may not help make for a better connection in general.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Direct mode directly routes network traffic to the host network. It is the most reliable, but requires an ethernet connection.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Non-direct mode uses a layer of emulation to get around this, but is more prone to problems.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
- <string>Bind socket to any address</string>
+ <string>Direct mode [TEXT PLACEHOLDER]</string>
</property>
</widget>
</item>
@@ -43,91 +53,62 @@
</widget>
</item>
<item>
- <widget class="QGroupBox" name="groupBox_2">
+ <widget class="QGroupBox" name="groupBox_3">
<property name="title">
- <string>Online</string>
+ <string>Direct mode settings</string>
</property>
- <layout class="QGridLayout" name="gridLayout_2">
- <item row="3" column="0" rowspan="3" colspan="2">
- <widget class="QGroupBox" name="groupBox_3">
- <property name="title">
- <string>Direct mode settings</string>
+ <layout class="QGridLayout" name="gridLayout_3">
+ <item row="0" column="0">
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Network adapter:</string>
</property>
- <layout class="QGridLayout" name="gridLayout_3">
- <item row="0" column="0">
- <widget class="QLabel" name="label">
- <property name="text">
- <string>Network adapter:</string>
- </property>
- </widget>
- </item>
- <item row="0" column="1">
- <widget class="QComboBox" name="cbxDirectAdapter">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>300</width>
- <height>0</height>
- </size>
- </property>
- <property name="whatsThis">
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Selects the network adapter through which to route network traffic under direct mode.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
- </property>
- </widget>
- </item>
- <item row="1" column="0">
- <widget class="QLabel" name="label_2">
- <property name="text">
- <string>MAC address:</string>
- </property>
- </widget>
- </item>
- <item row="1" column="1">
- <widget class="QLabel" name="lblAdapterMAC">
- <property name="text">
- <string>[PLACEHOLDER]</string>
- </property>
- </widget>
- </item>
- <item row="2" column="0">
- <widget class="QLabel" name="label_3">
- <property name="text">
- <string>IP address:</string>
- </property>
- </widget>
- </item>
- <item row="2" column="1">
- <widget class="QLabel" name="lblAdapterIP">
- <property name="text">
- <string>[PLACEHOLDER]</string>
- </property>
- </widget>
- </item>
- </layout>
</widget>
</item>
- <item row="1" column="0">
- <widget class="QRadioButton" name="rbIndirectMode">
+ <item row="0" column="1">
+ <widget class="QComboBox" name="cbxDirectAdapter">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>300</width>
+ <height>0</height>
+ </size>
+ </property>
<property name="whatsThis">
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Indirect mode uses libslirp. It requires no extra setup and is easy to use.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Selects the network adapter through which to route network traffic under direct mode.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_2">
<property name="text">
- <string>Indirect mode (uses libslirp, recommended)</string>
+ <string>MAC address:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QLabel" name="lblAdapterMAC">
+ <property name="text">
+ <string>[PLACEHOLDER]</string>
</property>
</widget>
</item>
<item row="2" column="0">
- <widget class="QRadioButton" name="rbDirectMode">
- <property name="whatsThis">
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Direct mode directly routes network traffic to the host network. It is the most reliable, but requires an ethernet connection.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Non-direct mode uses a layer of emulation to get around this, but is more prone to problems.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ <widget class="QLabel" name="label_3">
+ <property name="text">
+ <string>IP address:</string>
</property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QLabel" name="lblAdapterIP">
<property name="text">
- <string>Direct mode [TEXT PLACEHOLDER]</string>
+ <string>[PLACEHOLDER]</string>
</property>
</widget>
</item>
diff --git a/src/frontend/qt_sdl/font.h b/src/frontend/qt_sdl/font.h
index 59720ab..01e3bd2 100644
--- a/src/frontend/qt_sdl/font.h
+++ b/src/frontend/qt_sdl/font.h
@@ -1,5 +1,5 @@
/*
- Copyright 2016-2021 Arisotura
+ Copyright 2016-2022 melonDS team
This file is part of melonDS.
diff --git a/src/frontend/qt_sdl/main.cpp b/src/frontend/qt_sdl/main.cpp
index 31bb3c0..88704b6 100644
--- a/src/frontend/qt_sdl/main.cpp
+++ b/src/frontend/qt_sdl/main.cpp
@@ -1,5 +1,5 @@
/*
- Copyright 2016-2021 Arisotura
+ Copyright 2016-2022 melonDS team
This file is part of melonDS.
@@ -25,6 +25,7 @@
#include <string>
#include <algorithm>
+#include <QProcess>
#include <QApplication>
#include <QMessageBox>
#include <QMenuBar>
@@ -54,12 +55,17 @@
#include "EmuSettingsDialog.h"
#include "InputConfig/InputConfigDialog.h"
#include "VideoSettingsDialog.h"
+#include "CameraSettingsDialog.h"
#include "AudioSettingsDialog.h"
#include "FirmwareSettingsDialog.h"
+#include "PathSettingsDialog.h"
+#include "MPSettingsDialog.h"
#include "WifiSettingsDialog.h"
#include "InterfaceSettingsDialog.h"
#include "ROMInfoDialog.h"
+#include "RAMInfoDialog.h"
#include "TitleManagerDialog.h"
+#include "PowerManagement/PowerManagementDialog.h"
#include "types.h"
#include "version.h"
@@ -74,13 +80,16 @@
#include "SPU.h"
#include "Wifi.h"
#include "Platform.h"
+#include "LocalMP.h"
#include "Config.h"
#include "Savestate.h"
#include "main_shaders.h"
+#include "ROMManager.h"
#include "ArchiveUtil.h"
+#include "CameraManager.h"
// TODO: uniform variable spelling
@@ -97,6 +106,7 @@ bool videoSettingsDirty;
SDL_AudioDeviceID audioDevice;
int audioFreq;
+bool audioMuted;
SDL_cond* audioSync;
SDL_mutex* audioSyncLock;
@@ -107,9 +117,22 @@ u32 micExtBufferWritePos;
u32 micWavLength;
s16* micWavBuffer;
+CameraManager* camManager[2];
+bool camStarted[2];
+
+const struct { int id; float ratio; const char* label; } aspectRatios[] =
+{
+ { 0, 1, "4:3 (native)" },
+ { 4, (5.f / 3) / (4.f / 3), "5:3 (3DS)"},
+ { 1, (16.f / 9) / (4.f / 3), "16:9" },
+ { 2, (21.f / 9) / (4.f / 3), "21:9" },
+ { 3, 0, "window" }
+};
+
void micCallback(void* data, Uint8* stream, int len);
+
void audioCallback(void* data, Uint8* stream, int len)
{
len /= (sizeof(s16) * 2);
@@ -125,7 +148,7 @@ void audioCallback(void* data, Uint8* stream, int len)
SDL_CondSignal(audioSync);
SDL_UnlockMutex(audioSyncLock);
- if (num_in < 1)
+ if ((num_in < 1) || audioMuted)
{
memset(stream, 0, len*sizeof(s16)*2);
return;
@@ -145,6 +168,23 @@ void audioCallback(void* data, Uint8* stream, int len)
Frontend::AudioOut_Resample(buf_in, num_in, (s16*)stream, len, Config::AudioVolume);
}
+void audioMute()
+{
+ int inst = Platform::InstanceID();
+ audioMuted = false;
+
+ switch (Config::MPAudioMode)
+ {
+ case 1: // only instance 1
+ if (inst > 0) audioMuted = true;
+ break;
+
+ case 2: // only currently focused instance
+ if (!mainWindow->isActiveWindow()) audioMuted = true;
+ break;
+ }
+}
+
void micOpen()
{
@@ -180,7 +220,7 @@ void micClose()
micDevice = 0;
}
-void micLoadWav(const char* name)
+void micLoadWav(std::string name)
{
SDL_AudioSpec format;
memset(&format, 0, sizeof(SDL_AudioSpec));
@@ -191,7 +231,7 @@ void micLoadWav(const char* name)
u8* buf;
u32 len;
- if (!SDL_LoadWAV(name, &format, &buf, &len))
+ if (!SDL_LoadWAV(name.c_str(), &format, &buf, &len))
return;
const u64 dstfreq = 44100;
@@ -317,7 +357,7 @@ EmuThread::EmuThread(QObject* parent) : QThread(parent)
EmuPause = 0;
RunningSomething = false;
- connect(this, SIGNAL(windowUpdate()), mainWindow->panel, SLOT(repaint()));
+ connect(this, SIGNAL(windowUpdate()), mainWindow->panelWidget, SLOT(repaint()));
connect(this, SIGNAL(windowTitleChange(QString)), mainWindow, SLOT(onTitleUpdate(QString)));
connect(this, SIGNAL(windowEmuStart()), mainWindow, SLOT(onEmuStart()));
connect(this, SIGNAL(windowEmuStop()), mainWindow, SLOT(onEmuStop()));
@@ -325,7 +365,7 @@ EmuThread::EmuThread(QObject* parent) : QThread(parent)
connect(this, SIGNAL(windowEmuReset()), mainWindow->actReset, SLOT(trigger()));
connect(this, SIGNAL(windowEmuFrameStep()), mainWindow->actFrameStep, SLOT(trigger()));
connect(this, SIGNAL(windowLimitFPSChange()), mainWindow->actLimitFramerate, SLOT(trigger()));
- connect(this, SIGNAL(screenLayoutChange()), mainWindow->panel, SLOT(onScreenLayoutChanged()));
+ connect(this, SIGNAL(screenLayoutChange()), mainWindow->panelWidget, SLOT(onScreenLayoutChanged()));
connect(this, SIGNAL(windowFullscreenToggle()), mainWindow, SLOT(onFullscreenToggled()));
connect(this, SIGNAL(swapScreensToggle()), mainWindow->actScreenSwap, SLOT(trigger()));
@@ -413,6 +453,8 @@ void EmuThread::run()
double frameLimitError = 0.0;
double lastMeasureTime = lastTime;
+ u32 winUpdateCount = 0, winUpdateFreq = 1;
+
char melontitle[100];
while (EmuRunning != 0)
@@ -499,7 +541,7 @@ void EmuThread::run()
micProcess();
// auto screen layout
- if (Config::ScreenSizing == 3)
+ if (Config::ScreenSizing == screenSizing_Auto)
{
mainScreenPos[2] = mainScreenPos[1];
mainScreenPos[1] = mainScreenPos[0];
@@ -511,14 +553,14 @@ void EmuThread::run()
{
// constant flickering, likely displaying 3D on both screens
// TODO: when both screens are used for 2D only...???
- guess = 0;
+ guess = screenSizing_Even;
}
else
{
if (mainScreenPos[0] == 1)
- guess = 1;
+ guess = screenSizing_EmphTop;
else
- guess = 2;
+ guess = screenSizing_EmphBot;
}
if (guess != autoScreenSizing)
@@ -541,6 +583,12 @@ void EmuThread::run()
// emulate
u32 nlines = NDS::RunFrame();
+ if (ROMManager::NDSSave)
+ ROMManager::NDSSave->CheckFlush();
+
+ if (ROMManager::GBASave)
+ ROMManager::GBASave->CheckFlush();
+
FrontBufferLock.lock();
FrontBuffer = GPU::FrontBuffer;
#ifdef OGLRENDERER_ENABLED
@@ -563,11 +611,16 @@ void EmuThread::run()
if (EmuRunning == 0) break;
- emit windowUpdate();
+ winUpdateCount++;
+ if (winUpdateCount >= winUpdateFreq)
+ {
+ emit windowUpdate();
+ winUpdateCount = 0;
+ }
bool fastforward = Input::HotkeyDown(HK_FastForward);
- if (Config::AudioSync && (!fastforward) && audioDevice)
+ if (Config::AudioSync && !fastforward && audioDevice)
{
SDL_LockMutex(audioSyncLock);
while (SPU::GetOutputSize() > 1024)
@@ -616,7 +669,15 @@ void EmuThread::run()
float fpstarget = 1.0/frametimeStep;
- sprintf(melontitle, "[%d/%.0f] melonDS " MELONDS_VERSION, fps, fpstarget);
+ winUpdateFreq = fps / (u32)round(fpstarget);
+ if (winUpdateFreq < 1)
+ winUpdateFreq = 1;
+
+ int inst = Platform::InstanceID();
+ if (inst == 0)
+ sprintf(melontitle, "[%d/%.0f] melonDS " MELONDS_VERSION, fps, fpstarget);
+ else
+ sprintf(melontitle, "[%d/%.0f] melonDS (%d)", fps, fpstarget, inst+1);
changeWindowTitle(melontitle);
}
}
@@ -631,7 +692,11 @@ void EmuThread::run()
EmuStatus = EmuRunning;
- sprintf(melontitle, "melonDS " MELONDS_VERSION);
+ int inst = Platform::InstanceID();
+ if (inst == 0)
+ sprintf(melontitle, "melonDS " MELONDS_VERSION);
+ else
+ sprintf(melontitle, "melonDS (%d)", inst+1);
changeWindowTitle(melontitle);
SDL_Delay(75);
@@ -719,19 +784,39 @@ bool EmuThread::emuIsActive()
return (RunningSomething == 1);
}
+ScreenHandler::ScreenHandler(QWidget* widget)
+{
+ widget->setMouseTracking(true);
+ widget->setAttribute(Qt::WA_AcceptTouchEvents);
+ QTimer* mouseTimer = setupMouseTimer();
+ widget->connect(mouseTimer, &QTimer::timeout, [=] { if (Config::MouseHide) widget->setCursor(Qt::BlankCursor);});
+}
+
+ScreenHandler::~ScreenHandler()
+{
+ mouseTimer->stop();
+}
void ScreenHandler::screenSetupLayout(int w, int h)
{
int sizing = Config::ScreenSizing;
if (sizing == 3) sizing = autoScreenSizing;
- float aspectRatios[] =
+ float aspectTop, aspectBot;
+
+ for (auto ratio : aspectRatios)
{
- 1.f,
- (16.f/9)/(4.f/3),
- (21.f/9)/(4.f/3),
- ((float)w/h)/(4.f/3)
- };
+ if (ratio.id == Config::ScreenAspectTop)
+ aspectTop = ratio.ratio;
+ if (ratio.id == Config::ScreenAspectBot)
+ aspectBot = ratio.ratio;
+ }
+
+ if (aspectTop == 0)
+ aspectTop = (float) w / h;
+
+ if (aspectBot == 0)
+ aspectBot = (float) w / h;
Frontend::SetupScreenLayout(w, h,
Config::ScreenLayout,
@@ -740,8 +825,8 @@ void ScreenHandler::screenSetupLayout(int w, int h)
Config::ScreenGap,
Config::IntegerScaling != 0,
Config::ScreenSwap != 0,
- aspectRatios[Config::ScreenAspectTop],
- aspectRatios[Config::ScreenAspectBot]);
+ aspectTop,
+ aspectBot);
numScreens = Frontend::GetScreenTransforms(screenMatrix[0], screenKind);
}
@@ -754,6 +839,11 @@ QSize ScreenHandler::screenGetMinSize(int factor = 1)
int w = 256 * factor;
int h = 192 * factor;
+ if (Config::ScreenSizing == 4 || Config::ScreenSizing == 5)
+ {
+ return QSize(w, h);
+ }
+
if (Config::ScreenLayout == 0) // natural
{
if (isHori)
@@ -889,7 +979,7 @@ void ScreenHandler::screenHandleTouch(QTouchEvent* event)
void ScreenHandler::showCursor()
{
- mainWindow->panel->setCursor(Qt::ArrowCursor);
+ mainWindow->panelWidget->setCursor(Qt::ArrowCursor);
mouseTimer->start();
}
@@ -903,7 +993,7 @@ QTimer* ScreenHandler::setupMouseTimer()
return mouseTimer;
}
-ScreenPanelNative::ScreenPanelNative(QWidget* parent) : QWidget(parent)
+ScreenPanelNative::ScreenPanelNative(QWidget* parent) : QWidget(parent), ScreenHandler(this)
{
screen[0] = QImage(256, 192, QImage::Format_RGB32);
screen[1] = QImage(256, 192, QImage::Format_RGB32);
@@ -911,17 +1001,12 @@ ScreenPanelNative::ScreenPanelNative(QWidget* parent) : QWidget(parent)
screenTrans[0].reset();
screenTrans[1].reset();
- touching = false;
-
- setAttribute(Qt::WA_AcceptTouchEvents);
-
OSD::Init(nullptr);
}
ScreenPanelNative::~ScreenPanelNative()
{
OSD::DeInit(nullptr);
- mouseTimer->stop();
}
void ScreenPanelNative::setupScreenLayout()
@@ -1020,17 +1105,11 @@ void ScreenPanelNative::onScreenLayoutChanged()
}
-ScreenPanelGL::ScreenPanelGL(QWidget* parent) : QOpenGLWidget(parent)
-{
- touching = false;
-
- setAttribute(Qt::WA_AcceptTouchEvents);
-}
+ScreenPanelGL::ScreenPanelGL(QWidget* parent) : QOpenGLWidget(parent), ScreenHandler(this)
+{}
ScreenPanelGL::~ScreenPanelGL()
{
- mouseTimer->stop();
-
makeCurrent();
OSD::DeInit(this);
@@ -1281,11 +1360,14 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent)
oldW = Config::WindowWidth;
oldH = Config::WindowHeight;
- oldMax = Config::WindowMaximized!=0;
+ oldMax = Config::WindowMaximized;
setWindowTitle("melonDS " MELONDS_VERSION);
setAttribute(Qt::WA_DeleteOnClose);
setAcceptDrops(true);
+ setFocusPolicy(Qt::ClickFocus);
+
+ int inst = Platform::InstanceID();
QMenuBar* menubar = new QMenuBar();
{
@@ -1295,16 +1377,16 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent)
connect(actOpenROM, &QAction::triggered, this, &MainWindow::onOpenFile);
actOpenROM->setShortcut(QKeySequence(QKeySequence::StandardKey::Open));
- actOpenROMArchive = menu->addAction("Open ROM inside archive...");
+ /*actOpenROMArchive = menu->addAction("Open ROM inside archive...");
connect(actOpenROMArchive, &QAction::triggered, this, &MainWindow::onOpenFileArchive);
- actOpenROMArchive->setShortcut(QKeySequence(Qt::Key_O | Qt::CTRL | Qt::SHIFT));
+ actOpenROMArchive->setShortcut(QKeySequence(Qt::Key_O | Qt::CTRL | Qt::SHIFT));*/
recentMenu = menu->addMenu("Open recent");
for (int i = 0; i < 10; ++i)
{
- char* item = Config::RecentROMList[i];
- if (strlen(item) > 0)
- recentFileList.push_back(item);
+ std::string item = Config::RecentROMList[i];
+ if (!item.empty())
+ recentFileList.push_back(QString::fromStdString(item));
}
updateRecentFilesMenu();
@@ -1314,6 +1396,41 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent)
menu->addSeparator();
+ actCurrentCart = menu->addAction("DS slot: " + ROMManager::CartLabel());
+ actCurrentCart->setEnabled(false);
+
+ actInsertCart = menu->addAction("Insert cart...");
+ connect(actInsertCart, &QAction::triggered, this, &MainWindow::onInsertCart);
+
+ actEjectCart = menu->addAction("Eject cart");
+ connect(actEjectCart, &QAction::triggered, this, &MainWindow::onEjectCart);
+
+ menu->addSeparator();
+
+ actCurrentGBACart = menu->addAction("GBA slot: " + ROMManager::GBACartLabel());
+ actCurrentGBACart->setEnabled(false);
+
+ actInsertGBACart = menu->addAction("Insert ROM cart...");
+ connect(actInsertGBACart, &QAction::triggered, this, &MainWindow::onInsertGBACart);
+
+ {
+ QMenu* submenu = menu->addMenu("Insert add-on cart");
+
+ actInsertGBAAddon[0] = submenu->addAction("Memory expansion");
+ actInsertGBAAddon[0]->setData(QVariant(NDS::GBAAddon_RAMExpansion));
+ connect(actInsertGBAAddon[0], &QAction::triggered, this, &MainWindow::onInsertGBAAddon);
+ }
+
+ actEjectGBACart = menu->addAction("Eject cart");
+ connect(actEjectGBACart, &QAction::triggered, this, &MainWindow::onEjectGBACart);
+
+ menu->addSeparator();
+
+ actImportSavefile = menu->addAction("Import savefile");
+ connect(actImportSavefile, &QAction::triggered, this, &MainWindow::onImportSavefile);
+
+ menu->addSeparator();
+
{
QMenu* submenu = menu->addMenu("Save state");
@@ -1351,9 +1468,6 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent)
actUndoStateLoad->setShortcut(QKeySequence(Qt::Key_F12));
connect(actUndoStateLoad, &QAction::triggered, this, &MainWindow::onUndoStateLoad);
- actImportSavefile = menu->addAction("Import savefile");
- connect(actImportSavefile, &QAction::triggered, this, &MainWindow::onImportSavefile);
-
menu->addSeparator();
actQuit = menu->addAction("Quit");
@@ -1377,20 +1491,39 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent)
menu->addSeparator();
+ actPowerManagement = menu->addAction("Power management");
+ connect(actPowerManagement, &QAction::triggered, this, &MainWindow::onOpenPowerManagement);
+
+ menu->addSeparator();
+
actEnableCheats = menu->addAction("Enable cheats");
actEnableCheats->setCheckable(true);
connect(actEnableCheats, &QAction::triggered, this, &MainWindow::onEnableCheats);
- actSetupCheats = menu->addAction("Setup cheat codes");
- actSetupCheats->setMenuRole(QAction::NoRole);
- connect(actSetupCheats, &QAction::triggered, this, &MainWindow::onSetupCheats);
+ //if (inst == 0)
+ {
+ actSetupCheats = menu->addAction("Setup cheat codes");
+ actSetupCheats->setMenuRole(QAction::NoRole);
+ connect(actSetupCheats, &QAction::triggered, this, &MainWindow::onSetupCheats);
+
+ menu->addSeparator();
+ actROMInfo = menu->addAction("ROM info");
+ connect(actROMInfo, &QAction::triggered, this, &MainWindow::onROMInfo);
- menu->addSeparator();
- actROMInfo = menu->addAction("ROM info");
- connect(actROMInfo, &QAction::triggered, this, &MainWindow::onROMInfo);
+ actRAMInfo = menu->addAction("RAM search");
+ connect(actRAMInfo, &QAction::triggered, this, &MainWindow::onRAMInfo);
+
+ actTitleManager = menu->addAction("Manage DSi titles");
+ connect(actTitleManager, &QAction::triggered, this, &MainWindow::onOpenTitleManager);
+ }
- actTitleManager = menu->addAction("Manage DSi titles");
- connect(actTitleManager, &QAction::triggered, this, &MainWindow::onOpenTitleManager);
+ {
+ menu->addSeparator();
+ QMenu* submenu = menu->addMenu("Multiplayer");
+
+ actMPNewInstance = submenu->addAction("Launch new instance");
+ connect(actMPNewInstance, &QAction::triggered, this, &MainWindow::onMPNewInstance);
+ }
}
{
QMenu* menu = menubar->addMenu("Config");
@@ -1399,7 +1532,7 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent)
connect(actEmuSettings, &QAction::triggered, this, &MainWindow::onOpenEmuSettings);
#ifdef __APPLE__
- QAction* actPreferences = menu->addAction("Preferences...");
+ actPreferences = menu->addAction("Preferences...");
connect(actPreferences, &QAction::triggered, this, &MainWindow::onOpenEmuSettings);
actPreferences->setMenuRole(QAction::PreferencesRole);
#endif
@@ -1410,17 +1543,26 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent)
actVideoSettings = menu->addAction("Video settings");
connect(actVideoSettings, &QAction::triggered, this, &MainWindow::onOpenVideoSettings);
+ actCameraSettings = menu->addAction("Camera settings");
+ connect(actCameraSettings, &QAction::triggered, this, &MainWindow::onOpenCameraSettings);
+
actAudioSettings = menu->addAction("Audio settings");
connect(actAudioSettings, &QAction::triggered, this, &MainWindow::onOpenAudioSettings);
+ actMPSettings = menu->addAction("Multiplayer settings");
+ connect(actMPSettings, &QAction::triggered, this, &MainWindow::onOpenMPSettings);
+
actWifiSettings = menu->addAction("Wifi settings");
connect(actWifiSettings, &QAction::triggered, this, &MainWindow::onOpenWifiSettings);
+ actFirmwareSettings = menu->addAction("Firmware settings");
+ connect(actFirmwareSettings, &QAction::triggered, this, &MainWindow::onOpenFirmwareSettings);
+
actInterfaceSettings = menu->addAction("Interface settings");
connect(actInterfaceSettings, &QAction::triggered, this, &MainWindow::onOpenInterfaceSettings);
- actFirmwareSettings = menu->addAction("Firmware settings");
- connect(actFirmwareSettings, &QAction::triggered, this, &MainWindow::onOpenFirmwareSettings);
+ actPathSettings = menu->addAction("Path settings");
+ connect(actPathSettings, &QAction::triggered, this, &MainWindow::onOpenPathSettings);
{
QMenu* submenu = menu->addMenu("Savestate settings");
@@ -1503,7 +1645,7 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent)
const char* screensizing[] = {"Even", "Emphasize top", "Emphasize bottom", "Auto", "Top only", "Bottom only"};
- for (int i = 0; i < 6; i++)
+ for (int i = 0; i < screenSizing_MAX; i++)
{
actScreenSizing[i] = submenu->addAction(QString(screensizing[i]));
actScreenSizing[i]->setActionGroup(grpScreenSizing);
@@ -1522,34 +1664,34 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent)
{
QMenu* submenu = menu->addMenu("Aspect ratio");
grpScreenAspectTop = new QActionGroup(submenu);
+ grpScreenAspectBot = new QActionGroup(submenu);
+ actScreenAspectTop = new QAction*[sizeof(aspectRatios) / sizeof(aspectRatios[0])];
+ actScreenAspectBot = new QAction*[sizeof(aspectRatios) / sizeof(aspectRatios[0])];
- const char* aspectRatiosTop[] = {"Top 4:3 (native)", "Top 16:9", "Top 21:9", "Top window"};
-
- for (int i = 0; i < 4; i++)
+ for (int i = 0; i < 2; i++)
{
- actScreenAspectTop[i] = submenu->addAction(QString(aspectRatiosTop[i]));
- actScreenAspectTop[i]->setActionGroup(grpScreenAspectTop);
- actScreenAspectTop[i]->setData(QVariant(i));
- actScreenAspectTop[i]->setCheckable(true);
- }
-
- connect(grpScreenAspectTop, &QActionGroup::triggered, this, &MainWindow::onChangeScreenAspectTop);
+ QActionGroup* group = grpScreenAspectTop;
+ QAction** actions = actScreenAspectTop;
- submenu->addSeparator();
-
- grpScreenAspectBot = new QActionGroup(submenu);
+ if (i == 1)
+ {
+ group = grpScreenAspectBot;
+ submenu->addSeparator();
+ actions = actScreenAspectBot;
+ }
- const char* aspectRatiosBot[] = {"Bottom 4:3 (native)", "Bottom 16:9", "Bottom 21:9", "Bottom window"};
+ for (int j = 0; j < sizeof(aspectRatios) / sizeof(aspectRatios[0]); j++)
+ {
+ auto ratio = aspectRatios[j];
+ QString label = QString("%1 %2").arg(i ? "Bottom" : "Top", ratio.label);
+ actions[j] = submenu->addAction(label);
+ actions[j]->setActionGroup(group);
+ actions[j]->setData(QVariant(ratio.id));
+ actions[j]->setCheckable(true);
+ }
- for (int i = 0; i < 4; i++)
- {
- actScreenAspectBot[i] = submenu->addAction(QString(aspectRatiosBot[i]));
- actScreenAspectBot[i]->setActionGroup(grpScreenAspectBot);
- actScreenAspectBot[i]->setData(QVariant(i));
- actScreenAspectBot[i]->setCheckable(true);
+ connect(group, &QActionGroup::triggered, this, &MainWindow::onChangeScreenAspect);
}
-
- connect(grpScreenAspectBot, &QActionGroup::triggered, this, &MainWindow::onChangeScreenAspectBot);
}
actScreenFiltering = menu->addAction("Screen filtering");
@@ -1574,6 +1716,9 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent)
resize(Config::WindowWidth, Config::WindowHeight);
+ if (Config::FirmwareUsername == "Arisotura")
+ actMPNewInstance->setText("Fart");
+
#ifdef Q_OS_MAC
QPoint screenCenter = screen()->availableGeometry().center();
QRect frameGeo = frameGeometry();
@@ -1588,6 +1733,16 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent)
createScreenPanel();
+ actEjectCart->setEnabled(false);
+ actEjectGBACart->setEnabled(false);
+
+ if (Config::ConsoleType == 1)
+ {
+ actInsertGBACart->setEnabled(false);
+ for (int i = 0; i < 1; i++)
+ actInsertGBAAddon[i]->setEnabled(false);
+ }
+
for (int i = 0; i < 9; i++)
{
actSaveState[i]->setEnabled(false);
@@ -1601,14 +1756,17 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent)
actStop->setEnabled(false);
actFrameStep->setEnabled(false);
+ actPowerManagement->setEnabled(false);
+
actSetupCheats->setEnabled(false);
- actTitleManager->setEnabled(strlen(Config::DSiNANDPath) > 0);
+ actTitleManager->setEnabled(!Config::DSiNANDPath.empty());
- actEnableCheats->setChecked(Config::EnableCheats != 0);
+ actEnableCheats->setChecked(Config::EnableCheats);
actROMInfo->setEnabled(false);
+ actRAMInfo->setEnabled(false);
- actSavestateSRAMReloc->setChecked(Config::SavestateRelocSRAM != 0);
+ actSavestateSRAMReloc->setChecked(Config::SavestateRelocSRAM);
actScreenRotation[Config::ScreenRotation]->setChecked(true);
@@ -1623,18 +1781,36 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent)
actScreenLayout[Config::ScreenLayout]->setChecked(true);
actScreenSizing[Config::ScreenSizing]->setChecked(true);
- actIntegerScaling->setChecked(Config::IntegerScaling != 0);
+ actIntegerScaling->setChecked(Config::IntegerScaling);
- actScreenSwap->setChecked(Config::ScreenSwap != 0);
+ actScreenSwap->setChecked(Config::ScreenSwap);
- actScreenAspectTop[Config::ScreenAspectTop]->setChecked(true);
- actScreenAspectBot[Config::ScreenAspectBot]->setChecked(true);
+ for (int i = 0; i < sizeof(aspectRatios) / sizeof(aspectRatios[0]); i++)
+ {
+ if (Config::ScreenAspectTop == aspectRatios[i].id)
+ actScreenAspectTop[i]->setChecked(true);
+ if (Config::ScreenAspectBot == aspectRatios[i].id)
+ actScreenAspectBot[i]->setChecked(true);
+ }
- actScreenFiltering->setChecked(Config::ScreenFilter != 0);
- actShowOSD->setChecked(Config::ShowOSD != 0);
+ actScreenFiltering->setChecked(Config::ScreenFilter);
+ actShowOSD->setChecked(Config::ShowOSD);
- actLimitFramerate->setChecked(Config::LimitFPS != 0);
- actAudioSync->setChecked(Config::AudioSync != 0);
+ actLimitFramerate->setChecked(Config::LimitFPS);
+ actAudioSync->setChecked(Config::AudioSync);
+
+ if (inst > 0)
+ {
+ actEmuSettings->setEnabled(false);
+ actVideoSettings->setEnabled(false);
+ actMPSettings->setEnabled(false);
+ actWifiSettings->setEnabled(false);
+ actInterfaceSettings->setEnabled(false);
+
+#ifdef __APPLE__
+ actPreferences->setEnabled(false);
+#endif // __APPLE__
+ }
}
MainWindow::~MainWindow()
@@ -1645,17 +1821,13 @@ void MainWindow::createScreenPanel()
{
hasOGL = (Config::ScreenUseGL != 0) || (Config::_3DRenderer != 0);
- QTimer* mouseTimer;
-
if (hasOGL)
{
- panelGL = new ScreenPanelGL(this);
+ ScreenPanelGL* panelGL = new ScreenPanelGL(this);
panelGL->show();
panel = panelGL;
- panelGL->setMouseTracking(true);
- mouseTimer = panelGL->setupMouseTimer();
- connect(mouseTimer, &QTimer::timeout, [=] { if (Config::MouseHide) panelGL->setCursor(Qt::BlankCursor);});
+ panelWidget = panelGL;
if (!panelGL->isValid())
hasOGL = false;
@@ -1672,17 +1844,14 @@ void MainWindow::createScreenPanel()
if (!hasOGL)
{
- panelNative = new ScreenPanelNative(this);
+ ScreenPanelNative* panelNative = new ScreenPanelNative(this);
panel = panelNative;
- panel->show();
-
- panelNative->setMouseTracking(true);
- mouseTimer = panelNative->setupMouseTimer();
- connect(mouseTimer, &QTimer::timeout, [=] { if (Config::MouseHide) panelNative->setCursor(Qt::BlankCursor);});
+ panelWidget = panelNative;
+ panelWidget->show();
}
- setCentralWidget(panel);
+ setCentralWidget(panelWidget);
- connect(this, SIGNAL(screenLayoutChange()), panel, SLOT(onScreenLayoutChanged()));
+ connect(this, SIGNAL(screenLayoutChange()), panelWidget, SLOT(onScreenLayoutChanged()));
emit screenLayoutChange();
}
@@ -1690,7 +1859,7 @@ QOpenGLContext* MainWindow::getOGLContext()
{
if (!hasOGL) return nullptr;
- QOpenGLWidget* glpanel = (QOpenGLWidget*)panel;
+ QOpenGLWidget* glpanel = dynamic_cast<QOpenGLWidget*>(panel);
return glpanel->context();
}
@@ -1755,9 +1924,9 @@ void MainWindow::dragEnterEvent(QDragEnterEvent* event)
QStringList acceptedExts{".nds", ".srl", ".dsi", ".gba", ".rar",
".zip", ".7z", ".tar", ".tar.gz", ".tar.xz", ".tar.bz2"};
- for(const QString &ext : acceptedExts)
+ for (const QString &ext : acceptedExts)
{
- if(filename.endsWith(ext, Qt::CaseInsensitive))
+ if (filename.endsWith(ext, Qt::CaseInsensitive))
event->acceptProposedAction();
}
}
@@ -1769,69 +1938,79 @@ void MainWindow::dropEvent(QDropEvent* event)
QList<QUrl> urls = event->mimeData()->urls();
if (urls.count() > 1) return; // not handling more than one file at once
- emuThread->emuPause();
-
QString filename = urls.at(0).toLocalFile();
- QString ext = filename.right(3).toLower();
+ QStringList arcexts{".zip", ".7z", ".rar", ".tar", ".tar.gz", ".tar.xz", ".tar.bz2"};
- recentFileList.removeAll(filename);
- recentFileList.prepend(filename);
- updateRecentFilesMenu();
-
- char _filename[1024];
- strncpy(_filename, filename.toStdString().c_str(), 1023); _filename[1023] = '\0';
+ emuThread->emuPause();
- int slot; int res;
- if (ext == "gba")
- {
- slot = 1;
- res = Frontend::LoadROM(_filename, Frontend::ROMSlot_GBA);
- }
- else if(ext == "nds" || ext == "srl" || ext == "dsi")
+ if (!verifySetup())
{
- slot = 0;
- res = Frontend::LoadROM(_filename, Frontend::ROMSlot_NDS);
+ emuThread->emuUnpause();
+ return;
}
- else
+
+ for (const QString &ext : arcexts)
{
- QByteArray romBuffer;
- QString romFileName = pickAndExtractFileFromArchive(_filename, &romBuffer);
- if(romFileName.isEmpty())
- {
- res = Frontend::Load_ROMLoadError;
- }
- else
+ if (filename.endsWith(ext, Qt::CaseInsensitive))
{
- slot = (romFileName.endsWith(".gba", Qt::CaseInsensitive) ? 1 : 0);
- QString sramFileName = QFileInfo(_filename).absolutePath() + QDir::separator() + QFileInfo(romFileName).completeBaseName() + ".sav";
-
- if(slot == 0)
- strncpy(Frontend::NDSROMExtension, QFileInfo(romFileName).suffix().toStdString().c_str(), 4);
+ QString arcfile = pickFileFromArchive(filename);
+ if (arcfile.isEmpty())
+ {
+ emuThread->emuUnpause();
+ return;
+ }
- res = Frontend::LoadROM((const u8*)romBuffer.constData(), romBuffer.size(),
- _filename, romFileName.toStdString().c_str(), sramFileName.toStdString().c_str(),
- slot);
+ filename += "|" + arcfile;
}
}
- if (res != Frontend::Load_OK)
- {
- QMessageBox::critical(this,
- "melonDS",
- loadErrorStr(res));
- emuThread->emuUnpause();
- }
- else if (slot == 1)
+ QStringList file = filename.split('|');
+
+ if (filename.endsWith(".gba", Qt::CaseInsensitive))
{
- // checkme
+ if (!ROMManager::LoadGBAROM(file))
+ {
+ // TODO: better error reporting?
+ QMessageBox::critical(this, "melonDS", "Failed to load the ROM.");
+ emuThread->emuUnpause();
+ return;
+ }
+
emuThread->emuUnpause();
+
+ updateCartInserted(true);
}
else
{
+ if (!ROMManager::LoadROM(file, true))
+ {
+ // TODO: better error reporting?
+ QMessageBox::critical(this, "melonDS", "Failed to load the ROM.");
+ emuThread->emuUnpause();
+ return;
+ }
+
+ recentFileList.removeAll(filename);
+ recentFileList.prepend(filename);
+ updateRecentFilesMenu();
+
+ NDS::Start();
emuThread->emuRun();
+
+ updateCartInserted(false);
}
}
+void MainWindow::focusInEvent(QFocusEvent* event)
+{
+ audioMute();
+}
+
+void MainWindow::focusOutEvent(QFocusEvent* event)
+{
+ audioMute();
+}
+
void MainWindow::onAppStateChanged(Qt::ApplicationState state)
{
if (state == Qt::ApplicationInactive)
@@ -1846,145 +2025,179 @@ void MainWindow::onAppStateChanged(Qt::ApplicationState state)
}
}
-QString MainWindow::loadErrorStr(int error)
+bool MainWindow::verifySetup()
{
- switch (error)
+ QString res = ROMManager::VerifySetup();
+ if (!res.isEmpty())
{
- case Frontend::Load_BIOS9Missing:
- return "DS ARM9 BIOS was not found or could not be accessed. Check your emu settings.";
- case Frontend::Load_BIOS9Bad:
- return "DS ARM9 BIOS is not a valid BIOS dump.";
+ QMessageBox::critical(this, "melonDS", res);
+ return false;
+ }
- case Frontend::Load_BIOS7Missing:
- return "DS ARM7 BIOS was not found or could not be accessed. Check your emu settings.";
- case Frontend::Load_BIOS7Bad:
- return "DS ARM7 BIOS is not a valid BIOS dump.";
+ return true;
+}
- case Frontend::Load_FirmwareMissing:
- return "DS firmware was not found or could not be accessed. Check your emu settings.";
- case Frontend::Load_FirmwareBad:
- return "DS firmware is not a valid firmware dump.";
- case Frontend::Load_FirmwareNotBootable:
- return "DS firmware is not bootable.";
+bool MainWindow::preloadROMs(QString filename, QString gbafilename)
+{
+ if (!verifySetup())
+ {
+ return false;
+ }
- case Frontend::Load_DSiBIOS9Missing:
- return "DSi ARM9 BIOS was not found or could not be accessed. Check your emu settings.";
- case Frontend::Load_DSiBIOS9Bad:
- return "DSi ARM9 BIOS is not a valid BIOS dump.";
+ bool gbaloaded = false;
+ if (!gbafilename.isEmpty())
+ {
+ QStringList gbafile = gbafilename.split('|');
+ if (!ROMManager::LoadGBAROM(gbafile))
+ {
+ // TODO: better error reporting?
+ QMessageBox::critical(this, "melonDS", "Failed to load the GBA ROM.");
+ return false;
+ }
- case Frontend::Load_DSiBIOS7Missing:
- return "DSi ARM7 BIOS was not found or could not be accessed. Check your emu settings.";
- case Frontend::Load_DSiBIOS7Bad:
- return "DSi ARM7 BIOS is not a valid BIOS dump.";
+ gbaloaded = true;
+ }
- case Frontend::Load_DSiNANDMissing:
- return "DSi NAND was not found or could not be accessed. Check your emu settings.";
- case Frontend::Load_DSiNANDBad:
- return "DSi NAND is not a valid NAND dump.";
+ QStringList file = filename.split('|');
+ if (!ROMManager::LoadROM(file, true))
+ {
+ // TODO: better error reporting?
+ QMessageBox::critical(this, "melonDS", "Failed to load the ROM.");
+ return false;
+ }
- case Frontend::Load_ROMLoadError:
- return "Failed to load the ROM. Make sure the file is accessible and isn't used by another application.";
+ recentFileList.removeAll(filename);
+ recentFileList.prepend(filename);
+ updateRecentFilesMenu();
+
+ NDS::Start();
+ emuThread->emuRun();
- default: return "Unknown error during launch; smack Arisotura.";
+ updateCartInserted(false);
+
+ if (gbaloaded)
+ {
+ updateCartInserted(true);
}
+
+ return true;
}
-void MainWindow::loadROM(QByteArray *romData, QString archiveFileName, QString romFileName)
+QString MainWindow::pickFileFromArchive(QString archiveFileName)
{
- recentFileList.removeAll(archiveFileName);
- recentFileList.prepend(archiveFileName);
- updateRecentFilesMenu();
+ QVector<QString> archiveROMList = Archive::ListArchive(archiveFileName);
- // Strip entire archive name and get folder path
- strncpy(Config::LastROMFolder, QFileInfo(archiveFileName).absolutePath().toStdString().c_str(), 1024);
+ QString romFileName = ""; // file name inside archive
- QString sramFileName = QFileInfo(archiveFileName).absolutePath() + QDir::separator() + QFileInfo(romFileName).completeBaseName() + ".sav";
+ if (archiveROMList.size() > 2)
+ {
+ archiveROMList.removeFirst();
- int slot; int res;
- if (romFileName.endsWith("gba"))
+ bool ok;
+ QString toLoad = QInputDialog::getItem(this, "melonDS",
+ "This archive contains multiple files. Select which ROM you want to load.", archiveROMList.toList(), 0, false, &ok);
+ if (!ok) // User clicked on cancel
+ return QString();
+
+ romFileName = toLoad;
+ }
+ else if (archiveROMList.size() == 2)
{
- slot = 1;
- res = Frontend::LoadROM((const u8*)romData->constData(), romData->size(),
- archiveFileName.toStdString().c_str(),
- romFileName.toStdString().c_str(), sramFileName.toStdString().c_str(),
- Frontend::ROMSlot_GBA);
+ romFileName = archiveROMList.at(1);
}
- else
+ else if ((archiveROMList.size() == 1) && (archiveROMList[0] == QString("OK")))
{
- strncpy(Frontend::NDSROMExtension, QFileInfo(romFileName).suffix().toStdString().c_str(), 4);
- slot = 0;
- res = Frontend::LoadROM((const u8*)romData->constData(), romData->size(),
- archiveFileName.toStdString().c_str(),
- romFileName.toStdString().c_str(), sramFileName.toStdString().c_str(),
- Frontend::ROMSlot_NDS);
+ QMessageBox::warning(this, "melonDS", "This archive is empty.");
}
-
- if (res != Frontend::Load_OK)
+ else
{
- QMessageBox::critical(this,
- "melonDS",
- loadErrorStr(res));
- emuThread->emuUnpause();
+ QMessageBox::critical(this, "melonDS", "This archive could not be read. It may be corrupt or you don't have the permissions.");
}
- else if (slot == 1)
+
+ return romFileName;
+}
+
+QStringList MainWindow::pickROM(bool gba)
+{
+ QString console;
+ QStringList romexts;
+ QStringList arcexts{"*.zip", "*.7z", "*.rar", "*.tar", "*.tar.gz", "*.tar.xz", "*.tar.bz2"};
+ QStringList ret;
+
+ if (gba)
{
- // checkme
- emuThread->emuUnpause();
+ console = "GBA";
+ romexts.append("*.gba");
}
else
{
- emuThread->emuRun();
+ console = "DS";
+ romexts.append({"*.nds", "*.dsi", "*.ids", "*.srl"});
}
-}
-void MainWindow::loadROM(QString filename)
-{
- recentFileList.removeAll(filename);
- recentFileList.prepend(filename);
- updateRecentFilesMenu();
+ QString filter = romexts.join(' ') + " " + arcexts.join(' ');
+ filter = console + " ROMs (" + filter + ");;Any file (*.*)";
- // TODO: validate the input file!!
- // * check that it is a proper ROM
- // * ensure the binary offsets are sane
- // * etc
+ QString filename = QFileDialog::getOpenFileName(this,
+ "Open "+console+" ROM",
+ QString::fromStdString(Config::LastROMFolder),
+ filter);
+ if (filename.isEmpty())
+ return ret;
- // this shit is stupid
- char file[1024];
- strncpy(file, filename.toStdString().c_str(), 1023); file[1023] = '\0';
+ int pos = filename.length() - 1;
+ while (filename[pos] != '/' && filename[pos] != '\\' && pos > 0) pos--;
+ QString path_dir = filename.left(pos);
+ QString path_file = filename.mid(pos+1);
- int pos = strlen(file)-1;
- while (file[pos] != '/' && file[pos] != '\\' && pos > 0) pos--;
- strncpy(Config::LastROMFolder, file, pos);
- Config::LastROMFolder[pos] = '\0';
- char* ext = &file[strlen(file)-3];
+ Config::LastROMFolder = path_dir.toStdString();
- int slot; int res;
- if (!strcasecmp(ext, "gba"))
+ bool isarc = false;
+ for (const auto& ext : arcexts)
{
- slot = 1;
- res = Frontend::LoadROM(file, Frontend::ROMSlot_GBA);
+ int l = ext.length() - 1;
+ if (path_file.right(l).toLower() == ext.right(l))
+ {
+ isarc = true;
+ break;
+ }
}
- else
+
+ if (isarc)
{
- slot = 0;
- res = Frontend::LoadROM(file, Frontend::ROMSlot_NDS);
- }
+ path_file = pickFileFromArchive(filename);
+ if (path_file.isEmpty())
+ return ret;
- if (res != Frontend::Load_OK)
+ ret.append(filename);
+ ret.append(path_file);
+ }
+ else
{
- QMessageBox::critical(this,
- "melonDS",
- loadErrorStr(res));
- emuThread->emuUnpause();
+ ret.append(filename);
}
- else if (slot == 1)
+
+ return ret;
+}
+
+void MainWindow::updateCartInserted(bool gba)
+{
+ bool inserted;
+ if (gba)
{
- // checkme
- emuThread->emuUnpause();
+ inserted = ROMManager::GBACartInserted() && (Config::ConsoleType == 0);
+ actCurrentGBACart->setText("GBA slot: " + ROMManager::GBACartLabel());
+ actEjectGBACart->setEnabled(inserted);
}
else
{
- emuThread->emuRun();
+ inserted = ROMManager::CartInserted();
+ actCurrentCart->setText("DS slot: " + ROMManager::CartLabel());
+ actEjectCart->setEnabled(inserted);
+ actImportSavefile->setEnabled(inserted);
+ actSetupCheats->setEnabled(inserted);
+ actROMInfo->setEnabled(inserted);
+ actRAMInfo->setEnabled(inserted);
}
}
@@ -1992,99 +2205,43 @@ void MainWindow::onOpenFile()
{
emuThread->emuPause();
- QString filename = QFileDialog::getOpenFileName(this,
- "Open ROM",
- Config::LastROMFolder,
- "DS ROMs (*.nds *.dsi *.srl);;GBA ROMs (*.gba *.zip);;Any file (*.*)");
- if (filename.isEmpty())
+ if (!verifySetup())
{
emuThread->emuUnpause();
return;
}
- loadROM(filename);
-}
-
-void MainWindow::onOpenFileArchive()
-{
- emuThread->emuPause();
-
- QString archiveFileName = QFileDialog::getOpenFileName(this,
- "Open ROM Archive",
- Config::LastROMFolder,
- "Archived ROMs (*.zip *.7z *.rar *.tar *.tar.gz *.tar.xz *.tar.bz2);;Any file (*.*)");
- if (archiveFileName.isEmpty())
+ QStringList file = pickROM(false);
+ if (file.isEmpty())
{
emuThread->emuUnpause();
return;
}
- QByteArray romBuffer;
- QString romFileName = pickAndExtractFileFromArchive(archiveFileName, &romBuffer);
- if(!romFileName.isEmpty())
+ if (!ROMManager::LoadROM(file, true))
{
- loadROM(&romBuffer, archiveFileName, romFileName);
+ // TODO: better error reporting?
+ QMessageBox::critical(this, "melonDS", "Failed to load the ROM.");
+ emuThread->emuUnpause();
+ return;
}
-}
-QString MainWindow::pickAndExtractFileFromArchive(QString archiveFileName, QByteArray *romBuffer)
-{
- printf("Finding list of ROMs...\n");
- QVector<QString> archiveROMList = Archive::ListArchive(archiveFileName.toUtf8().constData());
-
-
- QString romFileName; // file name inside archive
-
- if (archiveROMList.size() > 2)
- {
- archiveROMList.removeFirst();
-
- bool ok;
- QString toLoad = QInputDialog::getItem(this, "melonDS",
- "The archive was found to have multiple files. Select which ROM you want to load.", archiveROMList.toList(), 0, false, &ok);
- if(!ok) // User clicked on cancel
- return QString();
+ QString filename = file.join('|');
+ recentFileList.removeAll(filename);
+ recentFileList.prepend(filename);
+ updateRecentFilesMenu();
- printf("Extracting '%s'\n", toLoad.toUtf8().constData());
- QVector<QString> extractResult = Archive::ExtractFileFromArchive(archiveFileName.toUtf8().constData(), toLoad.toUtf8().constData(), romBuffer);
- if (extractResult[0] != QString("Err"))
- {
- romFileName = extractResult[0];
- }
- else
- {
- QMessageBox::critical(this, "melonDS", QString("There was an error while trying to extract the ROM from the archive: ") + extractResult[1]);
- }
- }
- else if (archiveROMList.size() == 2)
- {
- printf("Extracting the only ROM in archive\n");
- QVector<QString> extractResult = Archive::ExtractFileFromArchive(archiveFileName.toUtf8().constData(), archiveROMList.at(1).toUtf8().constData(), romBuffer);
- if (extractResult[0] != QString("Err"))
- {
- romFileName = extractResult[0];
- }
- else
- {
- QMessageBox::critical(this, "melonDS", QString("There was an error while trying to extract the ROM from the archive: ") + extractResult[1]);
- }
- }
- else if ((archiveROMList.size() == 1) && (archiveROMList[0] == QString("OK")))
- {
- QMessageBox::warning(this, "melonDS", "The archive is intact, but there are no files inside.");
- }
- else
- {
- QMessageBox::critical(this, "melonDS", "The archive could not be read. It may be corrupt or you don't have the permissions.");
- }
+ NDS::Start();
+ emuThread->emuRun();
- return romFileName;
+ updateCartInserted(false);
}
void MainWindow::onClearRecentFiles()
{
recentFileList.clear();
- memset(Config::RecentROMList, 0, 10 * 1024);
+ for (int i = 0; i < 10; i++)
+ Config::RecentROMList[i] = "";
updateRecentFilesMenu();
}
@@ -2092,8 +2249,10 @@ void MainWindow::updateRecentFilesMenu()
{
recentMenu->clear();
- for(int i = 0; i < recentFileList.size(); ++i)
+ for (int i = 0; i < recentFileList.size(); ++i)
{
+ if (i >= 10) break;
+
QString item_full = recentFileList.at(i);
QString item_display = item_full;
int itemlen = item_full.length();
@@ -2120,16 +2279,18 @@ void MainWindow::updateRecentFilesMenu()
actRecentFile_i->setData(item_full);
connect(actRecentFile_i, &QAction::triggered, this, &MainWindow::onClickRecentFile);
- if(i < 10)
- strncpy(Config::RecentROMList[i], recentFileList.at(i).toStdString().c_str(), 1024);
+ Config::RecentROMList[i] = recentFileList.at(i).toStdString();
}
+ while (recentFileList.size() > 10)
+ recentFileList.removeLast();
+
recentMenu->addSeparator();
QAction *actClearRecentList = recentMenu->addAction("Clear");
connect(actClearRecentList, &QAction::triggered, this, &MainWindow::onClearRecentFiles);
- if(recentFileList.empty())
+ if (recentFileList.empty())
actClearRecentList->setEnabled(false);
Config::Save();
@@ -2138,48 +2299,139 @@ void MainWindow::updateRecentFilesMenu()
void MainWindow::onClickRecentFile()
{
QAction *act = (QAction *)sender();
- QString fileName = act->data().toString();
+ QString filename = act->data().toString();
+ QStringList file = filename.split('|');
+
+ emuThread->emuPause();
- if (fileName.endsWith(".gba", Qt::CaseInsensitive) ||
- fileName.endsWith(".nds", Qt::CaseInsensitive) ||
- fileName.endsWith(".srl", Qt::CaseInsensitive) ||
- fileName.endsWith(".dsi", Qt::CaseInsensitive))
+ if (!verifySetup())
{
- emuThread->emuPause();
- loadROM(fileName);
+ emuThread->emuUnpause();
+ return;
}
- else
+
+ if (!ROMManager::LoadROM(file, true))
{
- // Archives
- QString archiveFileName = fileName;
- QByteArray romBuffer;
- QString romFileName = MainWindow::pickAndExtractFileFromArchive(archiveFileName, &romBuffer);
- if(!romFileName.isEmpty())
- {
- emuThread->emuPause();
- loadROM(&romBuffer, archiveFileName, romFileName);
- }
+ // TODO: better error reporting?
+ QMessageBox::critical(this, "melonDS", "Failed to load the ROM.");
+ emuThread->emuUnpause();
+ return;
}
+
+ recentFileList.removeAll(filename);
+ recentFileList.prepend(filename);
+ updateRecentFilesMenu();
+
+ NDS::Start();
+ emuThread->emuRun();
+
+ updateCartInserted(false);
}
void MainWindow::onBootFirmware()
{
- // TODO: check the whole GBA cart shito
+ emuThread->emuPause();
+ if (!verifySetup())
+ {
+ emuThread->emuUnpause();
+ return;
+ }
+
+ if (!ROMManager::LoadBIOS())
+ {
+ // TODO: better error reporting?
+ QMessageBox::critical(this, "melonDS", "This firmware is not bootable.");
+ emuThread->emuUnpause();
+ return;
+ }
+
+ NDS::Start();
+ emuThread->emuRun();
+}
+
+void MainWindow::onInsertCart()
+{
emuThread->emuPause();
- int res = Frontend::LoadBIOS();
- if (res != Frontend::Load_OK)
+ QStringList file = pickROM(false);
+ if (file.isEmpty())
{
- QMessageBox::critical(this,
- "melonDS",
- loadErrorStr(res));
emuThread->emuUnpause();
+ return;
}
- else
+
+ if (!ROMManager::LoadROM(file, false))
{
- emuThread->emuRun();
+ // TODO: better error reporting?
+ QMessageBox::critical(this, "melonDS", "Failed to load the ROM.");
+ emuThread->emuUnpause();
+ return;
}
+
+ emuThread->emuUnpause();
+
+ updateCartInserted(false);
+}
+
+void MainWindow::onEjectCart()
+{
+ emuThread->emuPause();
+
+ ROMManager::EjectCart();
+
+ emuThread->emuUnpause();
+
+ updateCartInserted(false);
+}
+
+void MainWindow::onInsertGBACart()
+{
+ emuThread->emuPause();
+
+ QStringList file = pickROM(true);
+ if (file.isEmpty())
+ {
+ emuThread->emuUnpause();
+ return;
+ }
+
+ if (!ROMManager::LoadGBAROM(file))
+ {
+ // TODO: better error reporting?
+ QMessageBox::critical(this, "melonDS", "Failed to load the ROM.");
+ emuThread->emuUnpause();
+ return;
+ }
+
+ emuThread->emuUnpause();
+
+ updateCartInserted(true);
+}
+
+void MainWindow::onInsertGBAAddon()
+{
+ QAction* act = (QAction*)sender();
+ int type = act->data().toInt();
+
+ emuThread->emuPause();
+
+ ROMManager::LoadGBAAddon(type);
+
+ emuThread->emuUnpause();
+
+ updateCartInserted(true);
+}
+
+void MainWindow::onEjectGBACart()
+{
+ emuThread->emuPause();
+
+ ROMManager::EjectGBACart();
+
+ emuThread->emuUnpause();
+
+ updateCartInserted(true);
}
void MainWindow::onSaveState()
@@ -2188,17 +2440,17 @@ void MainWindow::onSaveState()
emuThread->emuPause();
- char filename[1024];
+ std::string filename;
if (slot > 0)
{
- Frontend::GetSavestateName(slot, filename, 1024);
+ filename = ROMManager::GetSavestateName(slot);
}
else
{
// TODO: specific 'last directory' for savestate files?
QString qfilename = QFileDialog::getSaveFileName(this,
"Save state",
- Config::LastROMFolder,
+ QString::fromStdString(Config::LastROMFolder),
"melonDS savestates (*.mln);;Any file (*.*)");
if (qfilename.isEmpty())
{
@@ -2206,10 +2458,10 @@ void MainWindow::onSaveState()
return;
}
- strncpy(filename, qfilename.toStdString().c_str(), 1023); filename[1023] = '\0';
+ filename = qfilename.toStdString();
}
- if (Frontend::SaveState(filename))
+ if (ROMManager::SaveState(filename))
{
char msg[64];
if (slot > 0) sprintf(msg, "State saved to slot %d", slot);
@@ -2232,17 +2484,17 @@ void MainWindow::onLoadState()
emuThread->emuPause();
- char filename[1024];
+ std::string filename;
if (slot > 0)
{
- Frontend::GetSavestateName(slot, filename, 1024);
+ filename = ROMManager::GetSavestateName(slot);
}
else
{
// TODO: specific 'last directory' for savestate files?
QString qfilename = QFileDialog::getOpenFileName(this,
"Load state",
- Config::LastROMFolder,
+ QString::fromStdString(Config::LastROMFolder),
"melonDS savestates (*.ml*);;Any file (*.*)");
if (qfilename.isEmpty())
{
@@ -2250,7 +2502,7 @@ void MainWindow::onLoadState()
return;
}
- strncpy(filename, qfilename.toStdString().c_str(), 1023); filename[1023] = '\0';
+ filename = qfilename.toStdString();
}
if (!Platform::FileExists(filename))
@@ -2264,7 +2516,7 @@ void MainWindow::onLoadState()
return;
}
- if (Frontend::LoadState(filename))
+ if (ROMManager::LoadState(filename))
{
char msg[64];
if (slot > 0) sprintf(msg, "State loaded from slot %d", slot);
@@ -2284,7 +2536,7 @@ void MainWindow::onLoadState()
void MainWindow::onUndoStateLoad()
{
emuThread->emuPause();
- Frontend::UndoStateLoad();
+ ROMManager::UndoStateLoad();
emuThread->emuUnpause();
OSD::AddMessage(0, "State load undone");
@@ -2292,36 +2544,52 @@ void MainWindow::onUndoStateLoad()
void MainWindow::onImportSavefile()
{
- if (!RunningSomething) return;
-
emuThread->emuPause();
QString path = QFileDialog::getOpenFileName(this,
"Select savefile",
- Config::LastROMFolder,
+ QString::fromStdString(Config::LastROMFolder),
"Savefiles (*.sav *.bin *.dsv);;Any file (*.*)");
- if (!path.isEmpty())
+ if (path.isEmpty())
+ {
+ emuThread->emuUnpause();
+ return;
+ }
+
+ FILE* f = Platform::OpenFile(path.toStdString(), "rb", true);
+ if (!f)
+ {
+ QMessageBox::critical(this, "melonDS", "Could not open the given savefile.");
+ emuThread->emuUnpause();
+ return;
+ }
+
+ if (RunningSomething)
{
if (QMessageBox::warning(this,
- "Emulation will be reset and data overwritten",
+ "melonDS",
"The emulation will be reset and the current savefile overwritten.",
- QMessageBox::Ok, QMessageBox::Cancel) == QMessageBox::Ok)
+ QMessageBox::Ok, QMessageBox::Cancel) != QMessageBox::Ok)
{
- int res = Frontend::Reset();
- if (res != Frontend::Load_OK)
- {
- QMessageBox::critical(this, "melonDS", "Reset failed\n" + loadErrorStr(res));
- }
- else
- {
- int diff = Frontend::ImportSRAM(path.toStdString().c_str());
- if (diff > 0)
- OSD::AddMessage(0, "Trimmed savefile");
- else if (diff < 0)
- OSD::AddMessage(0, "Savefile shorter than SRAM");
- }
+ emuThread->emuUnpause();
+ return;
}
+
+ ROMManager::Reset();
}
+
+ u32 len;
+ fseek(f, 0, SEEK_END);
+ len = (u32)ftell(f);
+
+ u8* data = new u8[len];
+ fseek(f, 0, SEEK_SET);
+ fread(data, len, 1, f);
+
+ NDS::LoadSave(data, len);
+ delete[] data;
+
+ fclose(f);
emuThread->emuUnpause();
}
@@ -2360,19 +2628,10 @@ void MainWindow::onReset()
actUndoStateLoad->setEnabled(false);
- int res = Frontend::Reset();
- if (res != Frontend::Load_OK)
- {
- QMessageBox::critical(this,
- "melonDS",
- loadErrorStr(res));
- emuThread->emuUnpause();
- }
- else
- {
- OSD::AddMessage(0, "Reset");
- emuThread->emuRun();
- }
+ ROMManager::Reset();
+
+ OSD::AddMessage(0, "Reset");
+ emuThread->emuRun();
}
void MainWindow::onStop()
@@ -2393,7 +2652,7 @@ void MainWindow::onFrameStep()
void MainWindow::onEnableCheats(bool checked)
{
Config::EnableCheats = checked?1:0;
- Frontend::EnableCheats(Config::EnableCheats != 0);
+ ROMManager::EnableCheats(Config::EnableCheats != 0);
}
void MainWindow::onSetupCheats()
@@ -2414,11 +2673,33 @@ void MainWindow::onROMInfo()
ROMInfoDialog* dlg = ROMInfoDialog::openDlg(this);
}
+void MainWindow::onRAMInfo()
+{
+ RAMInfoDialog* dlg = RAMInfoDialog::openDlg(this);
+}
+
void MainWindow::onOpenTitleManager()
{
TitleManagerDialog* dlg = TitleManagerDialog::openDlg(this);
}
+void MainWindow::onMPNewInstance()
+{
+ //QProcess::startDetached(QApplication::applicationFilePath());
+ QProcess newinst;
+ newinst.setProgram(QApplication::applicationFilePath());
+ newinst.setArguments(QApplication::arguments().mid(1, QApplication::arguments().length()-1));
+
+#ifdef __WIN32__
+ newinst.setCreateProcessArgumentsModifier([] (QProcess::CreateProcessArguments *args)
+ {
+ args->flags |= CREATE_NEW_CONSOLE;
+ });
+#endif
+
+ newinst.startDetached();
+}
+
void MainWindow::onOpenEmuSettings()
{
emuThread->emuPause();
@@ -2431,11 +2712,33 @@ void MainWindow::onEmuSettingsDialogFinished(int res)
{
emuThread->emuUnpause();
+ if (Config::ConsoleType == 1)
+ {
+ actInsertGBACart->setEnabled(false);
+ for (int i = 0; i < 1; i++)
+ actInsertGBAAddon[i]->setEnabled(false);
+ actEjectGBACart->setEnabled(false);
+ }
+ else
+ {
+ actInsertGBACart->setEnabled(true);
+ for (int i = 0; i < 1; i++)
+ actInsertGBAAddon[i]->setEnabled(true);
+ actEjectGBACart->setEnabled(ROMManager::GBACartInserted());
+ }
+
if (EmuSettingsDialog::needsReset)
onReset();
+ actCurrentGBACart->setText("GBA slot: " + ROMManager::GBACartLabel());
+
if (!RunningSomething)
- actTitleManager->setEnabled(strlen(Config::DSiNANDPath) > 0);
+ actTitleManager->setEnabled(!Config::DSiNANDPath.empty());
+}
+
+void MainWindow::onOpenPowerManagement()
+{
+ PowerManagementDialog* dlg = PowerManagementDialog::openDlg(this);
}
void MainWindow::onOpenInputConfig()
@@ -2457,6 +2760,27 @@ void MainWindow::onOpenVideoSettings()
connect(dlg, &VideoSettingsDialog::updateVideoSettings, this, &MainWindow::onUpdateVideoSettings);
}
+void MainWindow::onOpenCameraSettings()
+{
+ emuThread->emuPause();
+
+ camStarted[0] = camManager[0]->isStarted();
+ camStarted[1] = camManager[1]->isStarted();
+ if (camStarted[0]) camManager[0]->stop();
+ if (camStarted[1]) camManager[1]->stop();
+
+ CameraSettingsDialog* dlg = CameraSettingsDialog::openDlg(this);
+ connect(dlg, &CameraSettingsDialog::finished, this, &MainWindow::onCameraSettingsFinished);
+}
+
+void MainWindow::onCameraSettingsFinished(int res)
+{
+ if (camStarted[0]) camManager[0]->start();
+ if (camStarted[1]) camManager[1]->start();
+
+ emuThread->emuUnpause();
+}
+
void MainWindow::onOpenAudioSettings()
{
AudioSettingsDialog* dlg = AudioSettingsDialog::openDlg(this);
@@ -2480,6 +2804,22 @@ void MainWindow::onFirmwareSettingsFinished(int res)
emuThread->emuUnpause();
}
+void MainWindow::onOpenPathSettings()
+{
+ emuThread->emuPause();
+
+ PathSettingsDialog* dlg = PathSettingsDialog::openDlg(this);
+ connect(dlg, &PathSettingsDialog::finished, this, &MainWindow::onPathSettingsFinished);
+}
+
+void MainWindow::onPathSettingsFinished(int res)
+{
+ if (PathSettingsDialog::needsReset)
+ onReset();
+
+ emuThread->emuUnpause();
+}
+
void MainWindow::onUpdateAudioSettings()
{
SPU::SetInterpolation(Config::AudioInterp);
@@ -2515,6 +2855,22 @@ void MainWindow::onAudioSettingsFinished(int res)
micOpen();
}
+void MainWindow::onOpenMPSettings()
+{
+ emuThread->emuPause();
+
+ MPSettingsDialog* dlg = MPSettingsDialog::openDlg(this);
+ connect(dlg, &MPSettingsDialog::finished, this, &MainWindow::onMPSettingsFinished);
+}
+
+void MainWindow::onMPSettingsFinished(int res)
+{
+ audioMute();
+ LocalMP::SetRecvTimeout(Config::MPRecvTimeout);
+
+ emuThread->emuUnpause();
+}
+
void MainWindow::onOpenWifiSettings()
{
emuThread->emuPause();
@@ -2525,12 +2881,6 @@ void MainWindow::onOpenWifiSettings()
void MainWindow::onWifiSettingsFinished(int res)
{
- if (Wifi::MPInited)
- {
- Platform::MP_DeInit();
- Platform::MP_Init();
- }
-
Platform::LAN_DeInit();
Platform::LAN_Init();
@@ -2550,10 +2900,7 @@ void MainWindow::onOpenInterfaceSettings()
void MainWindow::onUpdateMouseTimer()
{
- if (hasOGL)
- panelGL->mouseTimer->setInterval(Config::MouseHideSeconds*1000);
- else
- panelNative->mouseTimer->setInterval(Config::MouseHideSeconds*1000);
+ panel->mouseTimer->setInterval(Config::MouseHideSeconds*1000);
}
void MainWindow::onInterfaceSettingsFinished(int res)
@@ -2569,8 +2916,8 @@ void MainWindow::onChangeSavestateSRAMReloc(bool checked)
void MainWindow::onChangeScreenSize()
{
int factor = ((QAction*)sender())->data().toInt();
- QSize diff = size() - panel->size();
- resize(dynamic_cast<ScreenHandler*>(panel)->screenGetMinSize(factor) + diff);
+ QSize diff = size() - panelWidget->size();
+ resize(panel->screenGetMinSize(factor) + diff);
}
void MainWindow::onChangeScreenRotation(QAction* act)
@@ -2601,6 +2948,22 @@ void MainWindow::onChangeScreenSwap(bool checked)
{
Config::ScreenSwap = checked?1:0;
+ // Swap between top and bottom screen when displaying one screen.
+ if (Config::ScreenSizing == screenSizing_TopOnly)
+ {
+ // Bottom Screen.
+ Config::ScreenSizing = screenSizing_BotOnly;
+ actScreenSizing[screenSizing_TopOnly]->setChecked(false);
+ actScreenSizing[Config::ScreenSizing]->setChecked(true);
+ }
+ else if (Config::ScreenSizing == screenSizing_BotOnly)
+ {
+ // Top Screen.
+ Config::ScreenSizing = screenSizing_TopOnly;
+ actScreenSizing[screenSizing_BotOnly]->setChecked(false);
+ actScreenSizing[Config::ScreenSizing]->setChecked(true);
+ }
+
emit screenLayoutChange();
}
@@ -2612,18 +2975,19 @@ void MainWindow::onChangeScreenSizing(QAction* act)
emit screenLayoutChange();
}
-void MainWindow::onChangeScreenAspectTop(QAction* act)
+void MainWindow::onChangeScreenAspect(QAction* act)
{
int aspect = act->data().toInt();
- Config::ScreenAspectTop = aspect;
-
- emit screenLayoutChange();
-}
+ QActionGroup* group = act->actionGroup();
-void MainWindow::onChangeScreenAspectBot(QAction* act)
-{
- int aspect = act->data().toInt();
- Config::ScreenAspectBot = aspect;
+ if (group == grpScreenAspectTop)
+ {
+ Config::ScreenAspectTop = aspect;
+ }
+ else
+ {
+ Config::ScreenAspectBot = aspect;
+ }
emit screenLayoutChange();
}
@@ -2678,39 +3042,24 @@ void MainWindow::onFullscreenToggled()
void MainWindow::onEmuStart()
{
- // TODO: make savestates work in DSi mode!!
- if (Config::ConsoleType == 1)
+ for (int i = 1; i < 9; i++)
{
- for (int i = 0; i < 9; i++)
- {
- actSaveState[i]->setEnabled(false);
- actLoadState[i]->setEnabled(false);
- }
- actUndoStateLoad->setEnabled(false);
- }
- else
- {
- for (int i = 1; i < 9; i++)
- {
- actSaveState[i]->setEnabled(true);
- actLoadState[i]->setEnabled(Frontend::SavestateExists(i));
- }
- actSaveState[0]->setEnabled(true);
- actLoadState[0]->setEnabled(true);
- actUndoStateLoad->setEnabled(false);
+ actSaveState[i]->setEnabled(true);
+ actLoadState[i]->setEnabled(ROMManager::SavestateExists(i));
}
+ actSaveState[0]->setEnabled(true);
+ actLoadState[0]->setEnabled(true);
+ actUndoStateLoad->setEnabled(false);
actPause->setEnabled(true);
actPause->setChecked(false);
actReset->setEnabled(true);
actStop->setEnabled(true);
actFrameStep->setEnabled(true);
- actImportSavefile->setEnabled(true);
- actSetupCheats->setEnabled(true);
- actTitleManager->setEnabled(false);
+ actPowerManagement->setEnabled(true);
- actROMInfo->setEnabled(true);
+ actTitleManager->setEnabled(false);
}
void MainWindow::onEmuStop()
@@ -2723,17 +3072,15 @@ void MainWindow::onEmuStop()
actLoadState[i]->setEnabled(false);
}
actUndoStateLoad->setEnabled(false);
- actImportSavefile->setEnabled(false);
actPause->setEnabled(false);
actReset->setEnabled(false);
actStop->setEnabled(false);
actFrameStep->setEnabled(false);
- actSetupCheats->setEnabled(false);
- actTitleManager->setEnabled(strlen(Config::DSiNANDPath) > 0);
+ actPowerManagement->setEnabled(false);
- actROMInfo->setEnabled(false);
+ actTitleManager->setEnabled(!Config::DSiNANDPath.empty());
}
void MainWindow::onUpdateVideoSettings(bool glchange)
@@ -2743,16 +3090,10 @@ void MainWindow::onUpdateVideoSettings(bool glchange)
emuThread->emuPause();
if (hasOGL)
- {
emuThread->deinitOpenGL();
- delete panelGL;
- }
- else
- {
- delete panelNative;
- }
+ delete panel;
createScreenPanel();
- connect(emuThread, SIGNAL(windowUpdate()), panel, SLOT(repaint()));
+ connect(emuThread, SIGNAL(windowUpdate()), panelWidget, SLOT(repaint()));
if (hasOGL) emuThread->initOpenGL();
}
@@ -2767,9 +3108,6 @@ void emuStop()
{
RunningSomething = false;
- Frontend::UnloadROM(Frontend::ROMSlot_NDS);
- Frontend::UnloadROM(Frontend::ROMSlot_GBA);
-
emit emuThread->windowEmuStop();
OSD::AddMessage(0xFFC040, "Shutdown");
@@ -2788,7 +3126,8 @@ bool MelonApplication::event(QEvent *event)
QFileOpenEvent *openEvent = static_cast<QFileOpenEvent*>(event);
emuThread->emuPause();
- mainWindow->loadROM(openEvent->file());
+ if (!mainWindow->preloadROMs(openEvent->file(), ""))
+ emuThread->emuUnpause();
}
return QApplication::event(event);
@@ -2796,7 +3135,7 @@ bool MelonApplication::event(QEvent *event)
int main(int argc, char** argv)
{
- srand(time(NULL));
+ srand(time(nullptr));
printf("melonDS " MELONDS_VERSION "\n");
printf(MELONDS_URL "\n");
@@ -2816,9 +3155,13 @@ int main(int argc, char** argv)
{
printf("SDL couldn't init joystick\n");
}
- if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) < 0)
+ if (SDL_Init(SDL_INIT_AUDIO) < 0)
{
- QMessageBox::critical(NULL, "melonDS", "SDL shat itself :(");
+ const char* err = SDL_GetError();
+ QString errorStr = "Failed to initialize SDL. This could indicate an issue with your audio driver.\n\nThe error was: ";
+ errorStr += err;
+
+ QMessageBox::critical(NULL, "melonDS", errorStr);
return 1;
}
@@ -2843,7 +3186,7 @@ int main(int argc, char** argv)
SANITIZE(Config::ScreenRotation, 0, 3);
SANITIZE(Config::ScreenGap, 0, 500);
SANITIZE(Config::ScreenLayout, 0, 3);
- SANITIZE(Config::ScreenSizing, 0, 5);
+ SANITIZE(Config::ScreenSizing, 0, (int)screenSizing_MAX);
SANITIZE(Config::ScreenAspectTop, 0, 4);
SANITIZE(Config::ScreenAspectBot, 0, 4);
#undef SANITIZE
@@ -2856,6 +3199,7 @@ int main(int argc, char** argv)
format.setSwapInterval(0);
QSurfaceFormat::setDefaultFormat(format);
+ audioMuted = false;
audioSync = SDL_CreateCond();
audioSyncLock = SDL_CreateMutex();
@@ -2881,13 +3225,18 @@ int main(int argc, char** argv)
micDevice = 0;
-
memset(micExtBuffer, 0, sizeof(micExtBuffer));
micExtBufferWritePos = 0;
micWavBuffer = nullptr;
- Frontend::Init_ROM();
- Frontend::EnableCheats(Config::EnableCheats != 0);
+ camStarted[0] = false;
+ camStarted[1] = false;
+ camManager[0] = new CameraManager(0, 640, 480, true);
+ camManager[1] = new CameraManager(1, 640, 480, true);
+ camManager[0]->setXFlip(Config::Camera[0].XFlip);
+ camManager[1]->setXFlip(Config::Camera[1].XFlip);
+
+ ROMManager::EnableCheats(Config::EnableCheats != 0);
Frontend::Init_Audio(audioFreq);
@@ -2910,33 +3259,17 @@ int main(int argc, char** argv)
emuThread->start();
emuThread->emuPause();
+ audioMute();
+
QObject::connect(&melon, &QApplication::applicationStateChanged, mainWindow, &MainWindow::onAppStateChanged);
if (argc > 1)
{
- char* file = argv[1];
- char* ext = &file[strlen(file)-3];
-
- if (!strcasecmp(ext, "nds") || !strcasecmp(ext, "srl") || !strcasecmp(ext, "dsi"))
- {
- int res = Frontend::LoadROM(file, Frontend::ROMSlot_NDS);
+ QString file = argv[1];
+ QString gbafile = "";
+ if (argc > 2) gbafile = argv[2];
- if (res == Frontend::Load_OK)
- {
- if (argc > 2)
- {
- file = argv[2];
- ext = &file[strlen(file)-3];
-
- if (!strcasecmp(ext, "gba"))
- {
- Frontend::LoadROM(file, Frontend::ROMSlot_GBA);
- }
- }
-
- emuThread->emuRun();
- }
- }
+ mainWindow->preloadROMs(file, gbafile);
}
int ret = melon.exec();
@@ -2947,8 +3280,6 @@ int main(int argc, char** argv)
Input::CloseJoystick();
- Frontend::DeInit_ROM();
-
if (audioDevice) SDL_CloseAudioDevice(audioDevice);
micClose();
@@ -2957,6 +3288,9 @@ int main(int argc, char** argv)
if (micWavBuffer) delete[] micWavBuffer;
+ delete camManager[0];
+ delete camManager[1];
+
Config::Save();
SDL_Quit();
@@ -2972,7 +3306,7 @@ int CALLBACK WinMain(HINSTANCE hinst, HINSTANCE hprev, LPSTR cmdline, int cmdsho
{
int argc = 0;
wchar_t** argv_w = CommandLineToArgvW(GetCommandLineW(), &argc);
- char* nullarg = "";
+ char nullarg[] = {'\0'};
char** argv = new char*[argc];
for (int i = 0; i < argc; i++)
@@ -2987,7 +3321,8 @@ int CALLBACK WinMain(HINSTANCE hinst, HINSTANCE hprev, LPSTR cmdline, int cmdsho
if (argv_w) LocalFree(argv_w);
- /*if (AttachConsole(ATTACH_PARENT_PROCESS))
+ //if (AttachConsole(ATTACH_PARENT_PROCESS))
+ /*if (AllocConsole())
{
freopen("CONOUT$", "w", stdout);
freopen("CONOUT$", "w", stderr);
diff --git a/src/frontend/qt_sdl/main.h b/src/frontend/qt_sdl/main.h
index 0b5e917..1977b7f 100644
--- a/src/frontend/qt_sdl/main.h
+++ b/src/frontend/qt_sdl/main.h
@@ -1,5 +1,5 @@
/*
- Copyright 2016-2021 Arisotura
+ Copyright 2016-2022 melonDS team
This file is part of melonDS.
@@ -101,7 +101,8 @@ class ScreenHandler
Q_GADGET
public:
- virtual ~ScreenHandler() {}
+ ScreenHandler(QWidget* widget);
+ virtual ~ScreenHandler();
QTimer* setupMouseTimer();
void updateMouseTimer();
QTimer* mouseTimer;
@@ -121,7 +122,7 @@ protected:
int screenKind[Frontend::MaxScreenTransforms];
int numScreens;
- bool touching;
+ bool touching = false;
void showCursor();
};
@@ -133,7 +134,7 @@ class ScreenPanelNative : public QWidget, public ScreenHandler
public:
explicit ScreenPanelNative(QWidget* parent);
- ~ScreenPanelNative();
+ virtual ~ScreenPanelNative();
protected:
void paintEvent(QPaintEvent* event) override;
@@ -163,7 +164,7 @@ class ScreenPanelGL : public QOpenGLWidget, public ScreenHandler, protected QOpe
public:
explicit ScreenPanelGL(QWidget* parent);
- ~ScreenPanelGL();
+ virtual ~ScreenPanelGL();
protected:
void initializeGL() override;
@@ -211,8 +212,7 @@ public:
bool hasOGL;
QOpenGLContext* getOGLContext();
- void loadROM(QString filename);
- void loadROM(QByteArray *romData, QString archiveFileName, QString romFileName);
+ bool preloadROMs(QString filename, QString gbafilename);
void onAppStateChanged(Qt::ApplicationState state);
@@ -226,15 +226,22 @@ protected:
void dragEnterEvent(QDragEnterEvent* event) override;
void dropEvent(QDropEvent* event) override;
+ void focusInEvent(QFocusEvent* event) override;
+ void focusOutEvent(QFocusEvent* event) override;
+
signals:
void screenLayoutChange();
private slots:
void onOpenFile();
- void onOpenFileArchive();
void onClickRecentFile();
void onClearRecentFiles();
void onBootFirmware();
+ void onInsertCart();
+ void onEjectCart();
+ void onInsertGBACart();
+ void onInsertGBAAddon();
+ void onEjectGBACart();
void onSaveState();
void onLoadState();
void onUndoStateLoad();
@@ -249,20 +256,29 @@ private slots:
void onSetupCheats();
void onCheatsDialogFinished(int res);
void onROMInfo();
+ void onRAMInfo();
void onOpenTitleManager();
+ void onMPNewInstance();
void onOpenEmuSettings();
void onEmuSettingsDialogFinished(int res);
+ void onOpenPowerManagement();
void onOpenInputConfig();
void onInputConfigFinished(int res);
void onOpenVideoSettings();
+ void onOpenCameraSettings();
+ void onCameraSettingsFinished(int res);
void onOpenAudioSettings();
- void onOpenFirmwareSettings();
void onUpdateAudioSettings();
void onAudioSettingsFinished(int res);
+ void onOpenMPSettings();
+ void onMPSettingsFinished(int res);
void onOpenWifiSettings();
void onWifiSettingsFinished(int res);
+ void onOpenFirmwareSettings();
void onFirmwareSettingsFinished(int res);
+ void onOpenPathSettings();
+ void onPathSettingsFinished(int res);
void onOpenInterfaceSettings();
void onInterfaceSettingsFinished(int res);
void onUpdateMouseTimer();
@@ -273,8 +289,7 @@ private slots:
void onChangeScreenLayout(QAction* act);
void onChangeScreenSwap(bool checked);
void onChangeScreenSizing(QAction* act);
- void onChangeScreenAspectTop(QAction* act);
- void onChangeScreenAspectBot(QAction* act);
+ void onChangeScreenAspect(QAction* act);
void onChangeIntegerScaling(bool checked);
void onChangeScreenFiltering(bool checked);
void onChangeShowOSD(bool checked);
@@ -291,33 +306,41 @@ private slots:
void onFullscreenToggled();
private:
+ QStringList currentROM;
+ QStringList currentGBAROM;
QList<QString> recentFileList;
QMenu *recentMenu;
void updateRecentFilesMenu();
- QString pickAndExtractFileFromArchive(QString archiveFileName, QByteArray *romBuffer);
+ bool verifySetup();
+ QString pickFileFromArchive(QString archiveFileName);
+ QStringList pickROM(bool gba);
+ void updateCartInserted(bool gba);
void createScreenPanel();
- QString loadErrorStr(int error);
-
bool pausedManually = false;
int oldW, oldH;
bool oldMax;
public:
- QWidget* panel;
- ScreenPanelGL* panelGL;
- ScreenPanelNative* panelNative;
+ ScreenHandler* panel;
+ QWidget* panelWidget;
QAction* actOpenROM;
- QAction* actOpenROMArchive;
QAction* actBootFirmware;
+ QAction* actCurrentCart;
+ QAction* actInsertCart;
+ QAction* actEjectCart;
+ QAction* actCurrentGBACart;
+ QAction* actInsertGBACart;
+ QAction* actInsertGBAAddon[1];
+ QAction* actEjectGBACart;
+ QAction* actImportSavefile;
QAction* actSaveState[9];
QAction* actLoadState[9];
QAction* actUndoStateLoad;
- QAction* actImportSavefile;
QAction* actQuit;
QAction* actPause;
@@ -327,14 +350,23 @@ public:
QAction* actEnableCheats;
QAction* actSetupCheats;
QAction* actROMInfo;
+ QAction* actRAMInfo;
QAction* actTitleManager;
+ QAction* actMPNewInstance;
QAction* actEmuSettings;
+#ifdef __APPLE__
+ QAction* actPreferences;
+#endif
+ QAction* actPowerManagement;
QAction* actInputConfig;
QAction* actVideoSettings;
+ QAction* actCameraSettings;
QAction* actAudioSettings;
+ QAction* actMPSettings;
QAction* actWifiSettings;
QAction* actFirmwareSettings;
+ QAction* actPathSettings;
QAction* actInterfaceSettings;
QAction* actSavestateSRAMReloc;
QAction* actScreenSize[4];
@@ -349,9 +381,9 @@ public:
QAction* actScreenSizing[6];
QAction* actIntegerScaling;
QActionGroup* grpScreenAspectTop;
- QAction* actScreenAspectTop[4];
+ QAction** actScreenAspectTop;
QActionGroup* grpScreenAspectBot;
- QAction* actScreenAspectBot[4];
+ QAction** actScreenAspectBot;
QAction* actScreenFiltering;
QAction* actShowOSD;
QAction* actLimitFramerate;
diff --git a/src/frontend/qt_sdl/main_shaders.h b/src/frontend/qt_sdl/main_shaders.h
index 0aa99ad..ca835c0 100644
--- a/src/frontend/qt_sdl/main_shaders.h
+++ b/src/frontend/qt_sdl/main_shaders.h
@@ -1,5 +1,5 @@
/*
- Copyright 2016-2021 Arisotura
+ Copyright 2016-2022 melonDS team
This file is part of melonDS.
diff --git a/src/frontend/qt_sdl/sem_timedwait.cpp b/src/frontend/qt_sdl/sem_timedwait.cpp
new file mode 100644
index 0000000..38b3c16
--- /dev/null
+++ b/src/frontend/qt_sdl/sem_timedwait.cpp
@@ -0,0 +1,488 @@
+/*
+ * s e m _ t i m e d w a i t
+ *
+ * Function:
+ * Implements a version of sem_timedwait().
+ *
+ * Description:
+ * Not all systems implement sem_timedwait(), which is a version of
+ * sem_wait() with a timeout. Mac OS X is one example, at least up to
+ * and including version 10.6 (Leopard). If such a function is needed,
+ * this code provides a reasonable implementation, which I think is
+ * compatible with the standard version, although possibly less
+ * efficient. It works by creating a thread that interrupts a normal
+ * sem_wait() call after the specified timeout.
+ *
+ * Call:
+ *
+ * The Linux man pages say:
+ *
+ * #include <semaphore.h>
+ *
+ * int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
+ *
+ * sem_timedwait() is the same as sem_wait(), except that abs_timeout
+ * specifies a limit on the amount of time that the call should block if
+ * the decrement cannot be immediately performed. The abs_timeout argument
+ * points to a structure that specifies an absolute timeout in seconds and
+ * nanoseconds since the Epoch (00:00:00, 1 January 1970). This structure
+ * is defined as follows:
+ *
+ * struct timespec {
+ * time_t tv_sec; Seconds
+ * long tv_nsec; Nanoseconds [0 .. 999999999]
+ * };
+ *
+ * If the timeout has already expired by the time of the call, and the
+ * semaphore could not be locked immediately, then sem_timedwait() fails
+ * with a timeout error (errno set to ETIMEDOUT).
+ * If the operation can be performed immediately, then sem_timedwait()
+ * never fails with a timeout error, regardless of the value of abs_timeout.
+ * Furthermore, the validity of abs_timeout is not checked in this case.
+ *
+ * Limitations:
+ *
+ * The mechanism used involves sending a SIGUSR2 signal to the thread
+ * calling sem_timedwait(). The handler for this signal is set to a null
+ * routine which does nothing, and with any flags for the signal
+ * (eg SA_RESTART) cleared. Note that this effective disabling of the
+ * SIGUSR2 signal is a side-effect of using this routine, and means it
+ * may not be a completely transparent plug-in replacement for a
+ * 'normal' sig_timedwait() call. Since OS X does not declare the
+ * sem_timedwait() call in its standard include files, the relevant
+ * declaration (shown above in the man pages extract) will probably have
+ * to be added to any code that uses this.
+ *
+ * Compiling:
+ * This compiles and runs cleanly on OS X (10.6) with gcc with the
+ * -Wall -ansi -pedantic flags. On Linux, using -ansi causes a sweep of
+ * compiler complaints about the timespec structure, but it compiles
+ * and works fine with just -Wall -pedantic. (Since Linux provides
+ * sem_timedwait() anyway, this really isn't needed on Linux.) However,
+ * since Linux provides sem_timedwait anyway, the sem_timedwait()
+ * code in this file is only compiled on OS X, and is a null on other
+ * systems.
+ *
+ * Testing:
+ * This file contains a test program that exercises the sem_timedwait
+ * code. It is compiled if the pre-processor variable TEST is defined.
+ * For more details, see the comments for the test routine at the end
+ * of the file.
+ *
+ * Author: Keith Shortridge, AAO.
+ *
+ * History:
+ * 8th Sep 2009. Original version. KS.
+ * 24th Sep 2009. Added test that the calling thread still exists before
+ * trying to set the timed-out flag. KS.
+ * 2nd Oct 2009. No longer restores the original SIGUSR2 signal handler.
+ * See comments in the body of the code for more details.
+ * Prototypes for now discontinued internal routines removed.
+ * 12th Aug 2010. Added the cleanup handler, so that this code no longer
+ * leaks resources if the calling thread is cancelled. KS.
+ * 21st Sep 2011. Added copyright notice below. Modified header comments
+ * to describe the use of SIGUSR2 more accurately in the
+ * light of the 2/10/09 change above. Now undefs DEBUG
+ * before defining it, to avoid any possible clash. KS.
+ * 14th Feb 2012. Tidied out a number of TABs that had got into the
+ * code. KS.
+ * 6th May 2013. Copyright notice modified to one based on the MIT licence,
+ * which is more permissive than the previous notice. KS.
+ *
+ * Copyright (c) Australian Astronomical Observatory (AAO), (2013).
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#ifdef __APPLE__
+
+#include <semaphore.h>
+#include <time.h>
+#include <sys/time.h>
+#include <pthread.h>
+#include <errno.h>
+#include <signal.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/fcntl.h>
+#include <setjmp.h>
+
+#include "sem_timedwait.h"
+
+/* Some useful definitions - TRUE, FALSE, and DEBUG */
+
+#undef TRUE
+#define TRUE 1
+#undef FALSE
+#define FALSE 0
+#undef DEBUG
+#define DEBUG printf
+
+/* A structure of type timeoutDetails is passed to the thread used to
+ * implement the timeout.
+ */
+
+typedef struct {
+ struct timespec delay; /* Specifies the delay, relative to now */
+ pthread_t callingThread; /* The thread doing the sem_wait call */
+ volatile short *timedOutShort; /* Address of a flag set to indicate that
+ * the timeout was triggered. */
+} timeoutDetails;
+
+/* A structure of type cleanupDetails is passed to the thread cleanup
+ * routine which is called at the end of the routine or if the thread calling
+ * it is cancelled.
+ */
+
+typedef struct {
+ pthread_t *threadIdAddr; /* Address of the variable that holds
+ * the Id of the timeout thread. */
+ struct sigaction *sigHandlerAddr; /* Address of the old signal action
+ * handler. */
+ volatile short *timedOutShort; /* Address of a flag set to indicate that
+ * the timeout was triggered. */
+} cleanupDetails;
+
+/* Forward declarations of internal routines */
+
+static void* timeoutThreadMain (void* passedPtr);
+static int triggerSignal (int Signal, pthread_t Thread);
+static void ignoreSignal (int Signal);
+static void timeoutThreadCleanup (void* passedPtr);
+
+/* -------------------------------------------------------------------------- */
+/*
+ * s e m _ t i m e d w a i t
+ *
+ * This is the main code for the sem_timedwait() implementation.
+ */
+
+int sem_timedwait (
+ sem_t *sem,
+ const struct timespec *abs_timeout)
+{
+ int result = 0; /* Code returned by this routine 0 or -1 */
+
+ /* "Under no circumstances shall the function fail if the semaphore
+ * can be locked immediately". So we try to get it quickly to see if we
+ * can avoid all the timeout overheads.
+ */
+
+ if (sem_trywait(sem) == 0) {
+
+ /* Yes, got it immediately. */
+
+ result = 0;
+
+ } else {
+
+ /* No, we've got to do it with a sem_wait() call and a thread to run
+ * the timeout. First, work out the time from now to the specified
+ * timeout, which we will pass to the timeout thread in a way that can
+ * be used to pass to nanosleep(). So we need this in seconds and
+ * nanoseconds. Along the way, we check for an invalid passed time,
+ * and for one that's already expired.
+ */
+
+ if ((abs_timeout->tv_nsec < 0) || (abs_timeout->tv_nsec > 1000000000)) {
+
+ /* Passed time is invalid */
+
+ result = -1;
+ errno = EINVAL;
+
+ } else {
+
+ struct timeval currentTime; /* Time now */
+ long secsToWait,nsecsToWait; /* Seconds and nsec to delay */
+ gettimeofday (&currentTime,NULL);
+ secsToWait = abs_timeout->tv_sec - currentTime.tv_sec;
+ nsecsToWait = (abs_timeout->tv_nsec - (currentTime.tv_usec * 1000));
+ while (nsecsToWait < 0) {
+ nsecsToWait += 1000000000;
+ secsToWait--;
+ }
+ if ((secsToWait < 0) || ((secsToWait == 0) && (nsecsToWait < 0))) {
+
+ /* Time has passed. Report an immediate timeout. */
+
+ result = -1;
+ errno = ETIMEDOUT;
+
+ } else {
+
+ /* We're going to have to do a sem_wait() with a timeout thread.
+ * The thread will wait the specified time, then will issue a
+ * SIGUSR2 signal that will interrupt the sem_wait() call.
+ * We pass the thread the id of the current thread, the delay,
+ * and the address of a flag to set on a timeout, so we can
+ * distinguish an interrupt caused by the timeout thread from
+ * one caused by some other signal.
+ */
+
+ volatile short timedOut; /* Flag to set on timeout */
+ timeoutDetails details; /* All the stuff the thread must know */
+ struct sigaction oldSignalAction; /* Current signal setting */
+ pthread_t timeoutThread; /* Id of timeout thread */
+ cleanupDetails cleaningDetails; /* What the cleanup routine needs */
+ int oldCancelState; /* Previous cancellation state */
+ int ignoreCancelState; /* Used in call, but ignored */
+ int createStatus; /* Status of pthread_create() call */
+
+ /* If the current thread is cancelled (and CML does do this)
+ * we don't want to leave our timer thread running - if we've
+ * started the thread we want to make sure we join it in order
+ * to release its resources. So we set a cleanup handler to
+ * do this. We pass it the address of the structure that will
+ * hold all it needs to know. While we set all this up,
+ * we prevent ourselves being cancelled, so all this data is
+ * coherent.
+ */
+
+ pthread_setcancelstate (PTHREAD_CANCEL_DISABLE,&oldCancelState);
+ timeoutThread = (pthread_t) 0;
+ cleaningDetails.timedOutShort = &timedOut;
+ cleaningDetails.threadIdAddr = &timeoutThread;
+ cleaningDetails.sigHandlerAddr = &oldSignalAction;
+ pthread_cleanup_push (timeoutThreadCleanup,&cleaningDetails);
+
+ /* Set up the details for the thread. Clear the timeout flag,
+ * record the current SIGUSR2 action settings so we can restore
+ * them later.
+ */
+
+ details.delay.tv_sec = secsToWait;
+ details.delay.tv_nsec = nsecsToWait;
+ details.callingThread = pthread_self();
+ details.timedOutShort = &timedOut;
+ timedOut = FALSE;
+ sigaction (SIGUSR2,NULL,&oldSignalAction);
+
+ /* Start up the timeout thread. Once we've done that, we can
+ * restore the previous cancellation state.
+ */
+
+ createStatus = pthread_create(&timeoutThread,NULL,
+ timeoutThreadMain, (void*)&details);
+ pthread_setcancelstate (oldCancelState,&ignoreCancelState);
+
+ if (createStatus < 0) {
+
+ /* Failed to create thread. errno will already be set properly */
+
+ result = -1;
+
+ } else {
+
+ /* Thread created OK. This is where we wait for the semaphore.
+ */
+
+ if (sem_wait(sem) == 0) {
+
+ /* Got the semaphore OK. We return zero, and all's well. */
+
+ result = 0;
+
+ } else {
+
+ /* If we got a -1 error from sem_wait(), it may be because
+ * it was interrupted by a timeout, or failed for some
+ * other reason. We check for the expected timeout
+ * condition, which is an 'interrupted' status and the
+ * timeout flag set by the timeout thread. We report that as
+ * a timeout error. Anything else is some other error and
+ * errno is already set properly.
+ */
+
+ result = -1;
+ if (errno == EINTR) {
+ if (timedOut) errno = ETIMEDOUT;
+ }
+ }
+
+ }
+
+ /* The cleanup routine - timeoutThreadCleanup() - packages up
+ * any tidying up that is needed, including joining with the
+ * timer thread. This will be called if the current thread is
+ * cancelled, but we need it to happen anyway, so we set the
+ * execute flag true here as we remove it from the list of
+ * cleanup routines to be called. So normally, this line amounts
+ * to calling timeoutThreadCleanup().
+ */
+
+ pthread_cleanup_pop (TRUE);
+ }
+ }
+ }
+ return (result);
+}
+
+/* -------------------------------------------------------------------------- */
+/*
+ * t i m e o u t T h r e a d C l e a n u p
+ *
+ * This internal routine tidies up at the end of a sem_timedwait() call.
+ * It is set as a cleanup routine for the current thread (not the timer
+ * thread) so it is executed even if the thread is cancelled. This is
+ * important, as we need to tidy up the timeout thread. If we took the
+ * semaphore (in other words, if we didn't timeout) then the timer thread
+ * will still be running, sitting in its nanosleep() call, and we need
+ * to cancel it. If the timer thread did signal a timeout then it will
+ * now be closing down. In either case, we need to join it (using a call
+ * to pthread_join()) or its resources will never be released.
+ * The single argument is a pointer to a cleanupDetails structure that has
+ * all the routine needs to know.
+ */
+
+static void timeoutThreadCleanup (void* passedPtr)
+{
+ /* Get what we need from the structure we've been passed. */
+
+ cleanupDetails *detailsPtr = (cleanupDetails*) passedPtr;
+ short timedOut = *(detailsPtr->timedOutShort);
+ pthread_t timeoutThread = *(detailsPtr->threadIdAddr);
+
+ /* If we created the thread, stop it - doesn't matter if it's no longer
+ * running, pthread_cancel can handle that. We make sure we wait for it
+ * to complete, because it is this pthread_join() call that releases any
+ * memory the thread may have allocated. Note that cancelling a thread is
+ * generally not a good idea, because of the difficulty of cleaning up
+ * after it, but this is a very simple thread that does nothing but call
+ * nanosleep(), and that we can cancel quite happily.
+ */
+
+ if (!timedOut) pthread_cancel(timeoutThread);
+ pthread_join(timeoutThread,NULL);
+
+ /* The code originally restored the old action handler, which generally
+ * was the default handler that caused the task to exit. Just occasionally,
+ * there seem to be cases where the signal is still queued and ready to
+ * trigger even though the thread that presumably sent it off just before
+ * it was cancelled has finished. I had thought that once we'd joined
+ * that thread, we could be sure of not seeing the signal, but that seems
+ * not to be the case, and so restoring a handler that will allow the task
+ * to crash is not a good idea, and so the line below has been commented
+ * out.
+ *
+ * sigaction (SIGUSR2,detailsPtr->sigHandlerAddr,NULL);
+ */
+}
+
+/* -------------------------------------------------------------------------- */
+/*
+ * t i m e o u t T h r e a d M a i n
+ *
+ * This internal routine is the main code for the timeout thread.
+ * The single argument is a pointer to a timeoutDetails structure that has
+ * all the thread needs to know - thread to signal, delay time, and the
+ * address of a flag to set if it triggers a timeout.
+ */
+
+static void* timeoutThreadMain (void* passedPtr)
+{
+ void* Return = (void*) 0;
+
+ /* We grab all the data held in the calling thread right now. In some
+ * cases, we find that the calling thread has vanished and released
+ * its memory, including the details structure, by the time the timeout
+ * expires, and then we get an access violation when we try to set the
+ * 'timed out' flag.
+ */
+
+ timeoutDetails details = *((timeoutDetails*) passedPtr);
+ struct timespec requestedDelay = details.delay;
+
+ /* We do a nanosleep() for the specified delay, and then trigger a
+ * timeout. Note that we allow for the case where the nanosleep() is
+ * interrupted, and restart it for the remaining time. If the
+ * thread that is doing the sem_wait() call gets the semaphore, it
+ * will cancel this thread, which is fine as we aren't doing anything
+ * other than a sleep and a signal.
+ */
+
+ for (;;) {
+ struct timespec remainingDelay;
+ if (nanosleep (&requestedDelay,&remainingDelay) == 0) {
+ break;
+ } else if (errno == EINTR) {
+ requestedDelay = remainingDelay;
+ } else {
+ Return = (void*) errno;
+ break;
+ }
+ }
+
+ /* We've completed the delay without being cancelled, so we now trigger
+ * the timeout by sending a signal to the calling thread. And that's it,
+ * although we set the timeout flag first to indicate that it was us
+ * that interrupted the sem_wait() call. One precaution: before we
+ * try to set the timed-out flag, make sure the calling thread still
+ * exists - this may not be the case if things are closing down a bit
+ * messily. We check this quickly using a zero test signal.
+ */
+
+ if (pthread_kill(details.callingThread,0) == 0) {
+ *(details.timedOutShort) = TRUE;
+ if (triggerSignal (SIGUSR2,details.callingThread) < 0) {
+ Return = (void*) errno;
+ }
+ }
+
+ return Return;
+}
+
+/* -------------------------------------------------------------------------- */
+/*
+ * t r i g g e r S i g n a l
+ *
+ * This is a general purpose routine that sends a specified signal to
+ * a specified thread, setting up a signal handler that does nothing,
+ * and then giving the signal. The only effect will be to interrupt any
+ * operation that is currently blocking - in this case, we expect this to
+ * be a sem_wait() call.
+ */
+
+static int triggerSignal (int Signal, pthread_t Thread)
+{
+ int Result = 0;
+ struct sigaction SignalDetails;
+ SignalDetails.sa_handler = ignoreSignal;
+ SignalDetails.sa_flags = 0;
+ (void) sigemptyset(&SignalDetails.sa_mask);
+ if ((Result = sigaction(Signal,&SignalDetails,NULL)) == 0) {
+ Result = pthread_kill(Thread,Signal);
+ }
+ return Result;
+}
+
+/* -------------------------------------------------------------------------- */
+/*
+ * i g n o r e S i g n a l
+ *
+ * And this is the signal handler that does nothing. (It clears its argument,
+ * but this has no effect and prevents a compiler warning about an unused
+ * argument.)
+ */
+
+static void ignoreSignal (int Signal) {
+ Signal = 0;
+}
+
+#endif
diff --git a/src/frontend/qt_sdl/sem_timedwait.h b/src/frontend/qt_sdl/sem_timedwait.h
new file mode 100644
index 0000000..42ae201
--- /dev/null
+++ b/src/frontend/qt_sdl/sem_timedwait.h
@@ -0,0 +1,8 @@
+#ifndef __SEM_TIMEDWAIT_H
+#define __SEM_TIMEDWAIT_H
+
+#ifdef __APPLE__
+int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
+#endif
+
+#endif