diff options
Diffstat (limited to 'src/frontend/qt_sdl')
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><html><head/><body><p>The bitrate of audio playback. If set to "Automatic" this will be 10-bit for DS mode and 16-bit for DSi mode.</p></body></html></string> + <string><html><head/><body><p>The bitrate of audio playback. If set to "Automatic" this will be 10-bit for DS mode and 16-bit for DSi mode.</p></body></html></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><html><head/><body><p>Selects which joystick will be used for joystick input, if any is present.</p></body></html></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><html><head/><body><p><span style=" font-weight:600;">R</span></p></body></html></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><html><head/><body><p><span style=" font-weight:600;">L</span></p></body></html></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><html><head/><body><p><span style=" font-weight:600;">X</span></p></body></html></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><html><head/><body><p><span style=" font-weight:600;">Y</span></p></body></html></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><html><head/><body><p><span style=" font-weight:600;">A</span></p></body></html></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><html><head/><body><p><span style=" font-weight:600;">B</span></p></body></html></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><html><head/><body><p><span style=" font-weight:600;">Select</span></p></body></html></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><html><head/><body><p><span style=" font-weight:600;">Start</span></p></body></html></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><html><head/><body><p><span style=" font-weight:600;">Up</span></p></body></html></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><html><head/><body><p><span style=" font-weight:600;">Left</span></p></body></html></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><html><head/><body><p><span style=" font-weight:600;">Right</span></p></body></html></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><html><head/><body><p><span style=" font-weight:600;">Down</span></p></body></html></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><html><head/><body><p><span style=" font-weight:600;">L</span></p></body></html></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><html><head/><body><p><span style=" font-weight:600;">Up</span></p></body></html></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><html><head/><body><p><span style=" font-weight:600;">Left</span></p></body></html></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><html><head/><body><p><span style=" font-weight:600;">Right</span></p></body></html></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><html><head/><body><p><span style=" font-weight:600;">Down</span></p></body></html></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><html><head/><body><p><span style=" font-weight:600;">X</span></p></body></html></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><html><head/><body><p><span style=" font-weight:600;">Y</span></p></body></html></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><html><head/><body><p><span style=" font-weight:600;">A</span></p></body></html></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><html><head/><body><p><span style=" font-weight:600;">B</span></p></body></html></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><html><head/><body><p><span style=" font-weight:600;">Select</span></p></body></html></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><html><head/><body><p><span style=" font-weight:600;">Start</span></p></body></html></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><html><head/><body><p><span style=" font-weight:600;">R</span></p></body></html></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><html><head/><body><p>Selects which joystick will be used for joystick input, if any is present.</p></body></html></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><html><head/><body><p>Indirect mode uses libslirp. It requires no extra setup and is easy to use.</p></body></html></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><html><head/><body><p>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.</p></body></html></string> + <string><html><head/><body><p>Direct mode directly routes network traffic to the host network. It is the most reliable, but requires an ethernet connection.</p><p><br/></p><p>Non-direct mode uses a layer of emulation to get around this, but is more prone to problems.</p></body></html></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><html><head/><body><p>Selects the network adapter through which to route network traffic under direct mode.</p></body></html></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><html><head/><body><p>Indirect mode uses libslirp. It requires no extra setup and is easy to use.</p></body></html></string> + <string><html><head/><body><p>Selects the network adapter through which to route network traffic under direct mode.</p></body></html></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><html><head/><body><p>Direct mode directly routes network traffic to the host network. It is the most reliable, but requires an ethernet connection.</p><p><br/></p><p>Non-direct mode uses a layer of emulation to get around this, but is more prone to problems.</p></body></html></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 (¤tTime,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 |