aboutsummaryrefslogtreecommitdiff
path: root/src/frontend/qt_sdl
diff options
context:
space:
mode:
Diffstat (limited to 'src/frontend/qt_sdl')
-rw-r--r--src/frontend/qt_sdl/AudioSettingsDialog.cpp14
-rw-r--r--src/frontend/qt_sdl/AudioSettingsDialog.ui11
-rw-r--r--src/frontend/qt_sdl/CMakeLists.txt4
-rw-r--r--src/frontend/qt_sdl/Config.cpp402
-rw-r--r--src/frontend/qt_sdl/Config.h6
-rw-r--r--src/frontend/qt_sdl/FirmwareSettingsDialog.cpp20
-rw-r--r--src/frontend/qt_sdl/FirmwareSettingsDialog.h1
-rw-r--r--src/frontend/qt_sdl/FirmwareSettingsDialog.ui13
-rw-r--r--src/frontend/qt_sdl/InputConfig/InputConfigDialog.cpp7
-rw-r--r--src/frontend/qt_sdl/InputConfig/InputConfigDialog.ui145
-rw-r--r--src/frontend/qt_sdl/LAN_PCap.cpp12
-rw-r--r--src/frontend/qt_sdl/LocalMP.cpp634
-rw-r--r--src/frontend/qt_sdl/LocalMP.h45
-rw-r--r--src/frontend/qt_sdl/MPSettingsDialog.cpp73
-rw-r--r--src/frontend/qt_sdl/MPSettingsDialog.h65
-rw-r--r--src/frontend/qt_sdl/MPSettingsDialog.ui142
-rw-r--r--src/frontend/qt_sdl/PathSettingsDialog.cpp7
-rw-r--r--src/frontend/qt_sdl/PathSettingsDialog.ui71
-rw-r--r--src/frontend/qt_sdl/Platform.cpp277
-rw-r--r--src/frontend/qt_sdl/PowerManagement/PowerManagementDialog.cpp7
-rw-r--r--src/frontend/qt_sdl/PowerManagement/PowerManagementDialog.ui79
-rw-r--r--src/frontend/qt_sdl/ROMManager.cpp12
-rw-r--r--src/frontend/qt_sdl/WifiSettingsDialog.cpp5
-rw-r--r--src/frontend/qt_sdl/WifiSettingsDialog.ui133
-rw-r--r--src/frontend/qt_sdl/main.cpp155
-rw-r--r--src/frontend/qt_sdl/main.h11
-rw-r--r--src/frontend/qt_sdl/sem_timedwait.cpp488
-rw-r--r--src/frontend/qt_sdl/sem_timedwait.h8
28 files changed, 2234 insertions, 613 deletions
diff --git a/src/frontend/qt_sdl/AudioSettingsDialog.cpp b/src/frontend/qt_sdl/AudioSettingsDialog.cpp
index a9b3ade..4beefaf 100644
--- a/src/frontend/qt_sdl/AudioSettingsDialog.cpp
+++ b/src/frontend/qt_sdl/AudioSettingsDialog.cpp
@@ -67,6 +67,20 @@ AudioSettingsDialog::AudioSettingsDialog(QWidget* parent) : QDialog(parent), ui(
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()
diff --git a/src/frontend/qt_sdl/AudioSettingsDialog.ui b/src/frontend/qt_sdl/AudioSettingsDialog.ui
index d7cfadd..8fc38d9 100644
--- a/src/frontend/qt_sdl/AudioSettingsDialog.ui
+++ b/src/frontend/qt_sdl/AudioSettingsDialog.ui
@@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>482</width>
- <height>256</height>
+ <height>301</height>
</rect>
</property>
<property name="sizePolicy">
@@ -24,6 +24,13 @@
<enum>QLayout::SetFixedSize</enum>
</property>
<item>
+ <widget class="QLabel" name="lblInstanceNum">
+ <property name="text">
+ <string>Configuring settings for instance X</string>
+ </property>
+ </widget>
+ </item>
+ <item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Audio output</string>
@@ -76,7 +83,7 @@
<item row="1" column="1">
<widget class="QComboBox" name="cbBitrate">
<property name="whatsThis">
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The bitrate of audio playback. If set to &quot;Automatic&quot; this will be 10-bit for DS mode and 16-bit for DSi mode.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The bitrate of audio playback. If set to &quot;Automatic&quot; this will be 10-bit for DS mode and 16-bit for DSi mode.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
diff --git a/src/frontend/qt_sdl/CMakeLists.txt b/src/frontend/qt_sdl/CMakeLists.txt
index 3089c32..5f1c490 100644
--- a/src/frontend/qt_sdl/CMakeLists.txt
+++ b/src/frontend/qt_sdl/CMakeLists.txt
@@ -17,6 +17,7 @@ set(SOURCES_QT_SDL
AudioSettingsDialog.cpp
FirmwareSettingsDialog.cpp
PathSettingsDialog.cpp
+ MPSettingsDialog.cpp
WifiSettingsDialog.cpp
InterfaceSettingsDialog.cpp
ROMInfoDialog.cpp
@@ -25,6 +26,7 @@ set(SOURCES_QT_SDL
Input.cpp
LAN_PCap.cpp
LAN_Socket.cpp
+ LocalMP.cpp
OSD.cpp
OSD_shaders.h
font.h
@@ -112,6 +114,8 @@ if (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}")
diff --git a/src/frontend/qt_sdl/Config.cpp b/src/frontend/qt_sdl/Config.cpp
index 7a0ec69..a8df8ee 100644
--- a/src/frontend/qt_sdl/Config.cpp
+++ b/src/frontend/qt_sdl/Config.cpp
@@ -106,9 +106,10 @@ int FirmwareBirthdayDay;
int FirmwareFavouriteColour;
std::string FirmwareMessage;
std::string FirmwareMAC;
-bool RandomizeMAC;
-bool SocketBindAnyAddr;
+int MPAudioMode;
+int MPRecvTimeout;
+
std::string LANDevice;
bool DirectLAN;
@@ -141,200 +142,196 @@ bool DSiBatteryCharging;
const char* kConfigFile = "melonDS.ini";
+const char* kUniqueConfigFile = "melonDS.%d.ini";
ConfigEntry ConfigFile[] =
{
- {"Key_A", 0, &KeyMapping[0], -1},
- {"Key_B", 0, &KeyMapping[1], -1},
- {"Key_Select", 0, &KeyMapping[2], -1},
- {"Key_Start", 0, &KeyMapping[3], -1},
- {"Key_Right", 0, &KeyMapping[4], -1},
- {"Key_Left", 0, &KeyMapping[5], -1},
- {"Key_Up", 0, &KeyMapping[6], -1},
- {"Key_Down", 0, &KeyMapping[7], -1},
- {"Key_R", 0, &KeyMapping[8], -1},
- {"Key_L", 0, &KeyMapping[9], -1},
- {"Key_X", 0, &KeyMapping[10], -1},
- {"Key_Y", 0, &KeyMapping[11], -1},
-
- {"Joy_A", 0, &JoyMapping[0], -1},
- {"Joy_B", 0, &JoyMapping[1], -1},
- {"Joy_Select", 0, &JoyMapping[2], -1},
- {"Joy_Start", 0, &JoyMapping[3], -1},
- {"Joy_Right", 0, &JoyMapping[4], -1},
- {"Joy_Left", 0, &JoyMapping[5], -1},
- {"Joy_Up", 0, &JoyMapping[6], -1},
- {"Joy_Down", 0, &JoyMapping[7], -1},
- {"Joy_R", 0, &JoyMapping[8], -1},
- {"Joy_L", 0, &JoyMapping[9], -1},
- {"Joy_X", 0, &JoyMapping[10], -1},
- {"Joy_Y", 0, &JoyMapping[11], -1},
-
- {"HKKey_Lid", 0, &HKKeyMapping[HK_Lid], -1},
- {"HKKey_Mic", 0, &HKKeyMapping[HK_Mic], -1},
- {"HKKey_Pause", 0, &HKKeyMapping[HK_Pause], -1},
- {"HKKey_Reset", 0, &HKKeyMapping[HK_Reset], -1},
- {"HKKey_FastForward", 0, &HKKeyMapping[HK_FastForward], -1},
- {"HKKey_FastForwardToggle", 0, &HKKeyMapping[HK_FastForwardToggle], -1},
- {"HKKey_FullscreenToggle", 0, &HKKeyMapping[HK_FullscreenToggle], -1},
- {"HKKey_SwapScreens", 0, &HKKeyMapping[HK_SwapScreens], -1},
- {"HKKey_SolarSensorDecrease", 0, &HKKeyMapping[HK_SolarSensorDecrease], -1},
- {"HKKey_SolarSensorIncrease", 0, &HKKeyMapping[HK_SolarSensorIncrease], -1},
- {"HKKey_FrameStep", 0, &HKKeyMapping[HK_FrameStep], -1},
-
- {"HKJoy_Lid", 0, &HKJoyMapping[HK_Lid], -1},
- {"HKJoy_Mic", 0, &HKJoyMapping[HK_Mic], -1},
- {"HKJoy_Pause", 0, &HKJoyMapping[HK_Pause], -1},
- {"HKJoy_Reset", 0, &HKJoyMapping[HK_Reset], -1},
- {"HKJoy_FastForward", 0, &HKJoyMapping[HK_FastForward], -1},
- {"HKJoy_FastForwardToggle", 0, &HKJoyMapping[HK_FastForwardToggle], -1},
- {"HKJoy_FullscreenToggle", 0, &HKJoyMapping[HK_FullscreenToggle], -1},
- {"HKJoy_SwapScreens", 0, &HKJoyMapping[HK_SwapScreens], -1},
- {"HKJoy_SolarSensorDecrease", 0, &HKJoyMapping[HK_SolarSensorDecrease], -1},
- {"HKJoy_SolarSensorIncrease", 0, &HKJoyMapping[HK_SolarSensorIncrease], -1},
- {"HKJoy_FrameStep", 0, &HKJoyMapping[HK_FrameStep], -1},
-
- {"JoystickID", 0, &JoystickID, 0},
-
- {"WindowWidth", 0, &WindowWidth, 256},
- {"WindowHeight", 0, &WindowHeight, 384},
- {"WindowMax", 1, &WindowMaximized, false},
-
- {"ScreenRotation", 0, &ScreenRotation, 0},
- {"ScreenGap", 0, &ScreenGap, 0},
- {"ScreenLayout", 0, &ScreenLayout, 0},
- {"ScreenSwap", 1, &ScreenSwap, false},
- {"ScreenSizing", 0, &ScreenSizing, 0},
- {"IntegerScaling", 1, &IntegerScaling, false},
- {"ScreenAspectTop",0, &ScreenAspectTop,0},
- {"ScreenAspectBot",0, &ScreenAspectBot,0},
- {"ScreenFilter", 1, &ScreenFilter, true},
-
- {"ScreenUseGL", 1, &ScreenUseGL, false},
- {"ScreenVSync", 1, &ScreenVSync, false},
- {"ScreenVSyncInterval", 0, &ScreenVSyncInterval, 1},
-
- {"3DRenderer", 0, &_3DRenderer, 0},
- {"Threaded3D", 1, &Threaded3D, true},
-
- {"GL_ScaleFactor", 0, &GL_ScaleFactor, 1},
- {"GL_BetterPolygons", 1, &GL_BetterPolygons, false},
-
- {"LimitFPS", 1, &LimitFPS, true},
+ {"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},
+ {"ShowOSD", 1, &ShowOSD, true, false},
- {"ConsoleType", 0, &ConsoleType, 0},
- {"DirectBoot", 1, &DirectBoot, true},
+ {"ConsoleType", 0, &ConsoleType, 0, false},
+ {"DirectBoot", 1, &DirectBoot, true, false},
#ifdef JIT_ENABLED
- {"JIT_Enable", 1, &JIT_Enable, false},
- {"JIT_MaxBlockSize", 0, &JIT_MaxBlockSize, 32},
- {"JIT_BranchOptimisations", 1, &JIT_BranchOptimisations, true},
- {"JIT_LiteralOptimisations", 1, &JIT_LiteralOptimisations, true},
+ {"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", 1, &JIT_FastMemory, false},
+ {"JIT_FastMemory", 1, &JIT_FastMemory, false, false},
#else
- {"JIT_FastMemory", 1, &JIT_FastMemory, true},
+ {"JIT_FastMemory", 1, &JIT_FastMemory, true, false},
#endif
#endif
- {"ExternalBIOSEnable", 1, &ExternalBIOSEnable, false},
-
- {"BIOS9Path", 2, &BIOS9Path, (std::string)""},
- {"BIOS7Path", 2, &BIOS7Path, (std::string)""},
- {"FirmwarePath", 2, &FirmwarePath, (std::string)""},
-
- {"DSiBIOS9Path", 2, &DSiBIOS9Path, (std::string)""},
- {"DSiBIOS7Path", 2, &DSiBIOS7Path, (std::string)""},
- {"DSiFirmwarePath", 2, &DSiFirmwarePath, (std::string)""},
- {"DSiNANDPath", 2, &DSiNANDPath, (std::string)""},
-
- {"DLDIEnable", 1, &DLDIEnable, false},
- {"DLDISDPath", 2, &DLDISDPath, (std::string)"dldi.bin"},
- {"DLDISize", 0, &DLDISize, 0},
- {"DLDIReadOnly", 1, &DLDIReadOnly, false},
- {"DLDIFolderSync", 1, &DLDIFolderSync, false},
- {"DLDIFolderPath", 2, &DLDIFolderPath, (std::string)""},
-
- {"DSiSDEnable", 1, &DSiSDEnable, false},
- {"DSiSDPath", 2, &DSiSDPath, (std::string)"dsisd.bin"},
- {"DSiSDSize", 0, &DSiSDSize, 0},
- {"DSiSDReadOnly", 1, &DSiSDReadOnly, false},
- {"DSiSDFolderSync", 1, &DSiSDFolderSync, false},
- {"DSiSDFolderPath", 2, &DSiSDFolderPath, (std::string)""},
-
- {"FirmwareOverrideSettings", 1, &FirmwareOverrideSettings, false},
- {"FirmwareUsername", 2, &FirmwareUsername, (std::string)"melonDS"},
- {"FirmwareLanguage", 0, &FirmwareLanguage, 1},
- {"FirmwareBirthdayMonth", 0, &FirmwareBirthdayMonth, 1},
- {"FirmwareBirthdayDay", 0, &FirmwareBirthdayDay, 1},
- {"FirmwareFavouriteColour", 0, &FirmwareFavouriteColour, 0},
- {"FirmwareMessage", 2, &FirmwareMessage, (std::string)""},
- {"FirmwareMAC", 2, &FirmwareMAC, (std::string)""},
- {"RandomizeMAC", 1, &RandomizeMAC, false},
-
- {"SockBindAnyAddr", 1, &SocketBindAnyAddr, false},
- {"LANDevice", 2, &LANDevice, (std::string)""},
- {"DirectLAN", 1, &DirectLAN, false},
-
- {"SavStaRelocSRAM", 1, &SavestateRelocSRAM, false},
-
- {"AudioInterp", 0, &AudioInterp, 0},
- {"AudioBitrate", 0, &AudioBitrate, 0},
- {"AudioVolume", 0, &AudioVolume, 256},
- {"MicInputType", 0, &MicInputType, 1},
- {"MicWavPath", 2, &MicWavPath, (std::string)""},
-
- {"LastROMFolder", 2, &LastROMFolder, (std::string)""},
-
- {"RecentROM_0", 2, &RecentROMList[0], (std::string)""},
- {"RecentROM_1", 2, &RecentROMList[1], (std::string)""},
- {"RecentROM_2", 2, &RecentROMList[2], (std::string)""},
- {"RecentROM_3", 2, &RecentROMList[3], (std::string)""},
- {"RecentROM_4", 2, &RecentROMList[4], (std::string)""},
- {"RecentROM_5", 2, &RecentROMList[5], (std::string)""},
- {"RecentROM_6", 2, &RecentROMList[6], (std::string)""},
- {"RecentROM_7", 2, &RecentROMList[7], (std::string)""},
- {"RecentROM_8", 2, &RecentROMList[8], (std::string)""},
- {"RecentROM_9", 2, &RecentROMList[9], (std::string)""},
-
- {"SaveFilePath", 2, &SaveFilePath, (std::string)""},
- {"SavestatePath", 2, &SavestatePath, (std::string)""},
- {"CheatFilePath", 2, &CheatFilePath, (std::string)""},
-
- {"EnableCheats", 1, &EnableCheats, false},
-
- {"MouseHide", 1, &MouseHide, false},
- {"MouseHideSeconds", 0, &MouseHideSeconds, 5},
- {"PauseLostFocus", 1, &PauseLostFocus, false},
-
- {"DSBatteryLevelOkay", 1, &DSBatteryLevelOkay, true},
- {"DSiBatteryLevel", 0, &DSiBatteryLevel, 0xF},
- {"DSiBatteryCharging", 1, &DSiBatteryCharging, true},
-
- {"", -1, nullptr, 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},
+
+ {"", -1, nullptr, 0, false}
};
-void Load()
+void LoadFile(int inst)
{
- ConfigEntry* entry = &ConfigFile[0];
- for (;;)
+ FILE* f;
+ if (inst > 0)
{
- if (!entry->Value) break;
-
- 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;
- }
-
- 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];
@@ -349,13 +346,13 @@ 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 ((inst > 0) && (!entry->InstanceUnique))
+ break;
+
switch (entry->Type)
{
case 0: *(int*)entry->Value = strtol(entryval, NULL, 10); break;
@@ -365,23 +362,52 @@ void Load()
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");
+ int inst = Platform::InstanceID();
+
+ FILE* f;
+ if (inst > 0)
+ {
+ char name[100] = {0};
+ snprintf(name, 99, kUniqueConfigFile, inst+1);
+ f = Platform::OpenLocalFile(name, "w");
+ }
+ else
+ f = Platform::OpenLocalFile(kConfigFile, "w");
+
if (!f) return;
- ConfigEntry* entry = &ConfigFile[0];
- for (;;)
+ for (ConfigEntry* entry = &ConfigFile[0]; entry->Value; entry++)
{
- if (!entry->Value) break;
+ if ((inst > 0) && (!entry->InstanceUnique))
+ continue;
switch (entry->Type)
{
@@ -389,8 +415,6 @@ void Save()
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;
}
-
- entry++;
}
fclose(f);
diff --git a/src/frontend/qt_sdl/Config.h b/src/frontend/qt_sdl/Config.h
index f2f8ddc..cc6792c 100644
--- a/src/frontend/qt_sdl/Config.h
+++ b/src/frontend/qt_sdl/Config.h
@@ -58,6 +58,7 @@ struct ConfigEntry
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
};
@@ -141,9 +142,10 @@ extern int FirmwareBirthdayDay;
extern int FirmwareFavouriteColour;
extern std::string FirmwareMessage;
extern std::string FirmwareMAC;
-extern bool RandomizeMAC;
-extern bool SocketBindAnyAddr;
+extern int MPAudioMode;
+extern int MPRecvTimeout;
+
extern std::string LANDevice;
extern bool DirectLAN;
diff --git a/src/frontend/qt_sdl/FirmwareSettingsDialog.cpp b/src/frontend/qt_sdl/FirmwareSettingsDialog.cpp
index 754cc8a..ffca567 100644
--- a/src/frontend/qt_sdl/FirmwareSettingsDialog.cpp
+++ b/src/frontend/qt_sdl/FirmwareSettingsDialog.cpp
@@ -18,6 +18,7 @@
#include <QMessageBox>
+#include "Platform.h"
#include "Config.h"
#include "FirmwareSettingsDialog.h"
@@ -64,10 +65,14 @@ FirmwareSettingsDialog::FirmwareSettingsDialog(QWidget* parent) : QDialog(parent
ui->overrideFirmwareBox->setChecked(Config::FirmwareOverrideSettings);
ui->txtMAC->setText(QString::fromStdString(Config::FirmwareMAC));
- ui->cbRandomizeMAC->setChecked(Config::RandomizeMAC);
on_overrideFirmwareBox_toggled();
- on_cbRandomizeMAC_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()
@@ -135,7 +140,6 @@ void FirmwareSettingsDialog::done(int r)
std::string newMessage = ui->messageEdit->text().toStdString();
std::string newMAC = ui->txtMAC->text().toStdString();
- bool newRandomizeMAC = ui->cbRandomizeMAC->isChecked();
if ( newOverride != Config::FirmwareOverrideSettings
|| newName != Config::FirmwareUsername
@@ -144,8 +148,7 @@ void FirmwareSettingsDialog::done(int r)
|| newBirthdayDay != Config::FirmwareBirthdayDay
|| newBirthdayMonth != Config::FirmwareBirthdayMonth
|| newMessage != Config::FirmwareMessage
- || newMAC != Config::FirmwareMAC
- || newRandomizeMAC != Config::RandomizeMAC)
+ || newMAC != Config::FirmwareMAC)
{
if (RunningSomething
&& QMessageBox::warning(this, "Reset necessary to apply changes",
@@ -163,7 +166,6 @@ void FirmwareSettingsDialog::done(int r)
Config::FirmwareMessage = newMessage;
Config::FirmwareMAC = newMAC;
- Config::RandomizeMAC = newRandomizeMAC;
Config::Save();
@@ -210,9 +212,3 @@ void FirmwareSettingsDialog::on_overrideFirmwareBox_toggled()
ui->grpUserSettings->setDisabled(disable);
ui->grpWifiSettings->setDisabled(disable);
}
-
-void FirmwareSettingsDialog::on_cbRandomizeMAC_toggled()
-{
- bool disable = ui->cbRandomizeMAC->isChecked();
- ui->txtMAC->setDisabled(disable);
-}
diff --git a/src/frontend/qt_sdl/FirmwareSettingsDialog.h b/src/frontend/qt_sdl/FirmwareSettingsDialog.h
index 97bf5c0..b3695e2 100644
--- a/src/frontend/qt_sdl/FirmwareSettingsDialog.h
+++ b/src/frontend/qt_sdl/FirmwareSettingsDialog.h
@@ -124,7 +124,6 @@ private slots:
void on_cbxBirthdayMonth_currentIndexChanged(int idx);
void on_overrideFirmwareBox_toggled();
- void on_cbRandomizeMAC_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/InputConfig/InputConfigDialog.cpp b/src/frontend/qt_sdl/InputConfig/InputConfigDialog.cpp
index 697e983..92a0186 100644
--- a/src/frontend/qt_sdl/InputConfig/InputConfigDialog.cpp
+++ b/src/frontend/qt_sdl/InputConfig/InputConfigDialog.cpp
@@ -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.ui b/src/frontend/qt_sdl/InputConfig/InputConfigDialog.ui
index 15cb683..0db61b1 100644
--- a/src/frontend/qt_sdl/InputConfig/InputConfigDialog.ui
+++ b/src/frontend/qt_sdl/InputConfig/InputConfigDialog.ui
@@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>770</width>
- <height>719</height>
+ <height>678</height>
</rect>
</property>
<property name="windowTitle">
@@ -20,7 +20,7 @@
<property name="sizeConstraint">
<enum>QLayout::SetFixedSize</enum>
</property>
- <item row="7" column="1">
+ <item row="8" column="1">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
@@ -30,49 +30,7 @@
</property>
</widget>
</item>
- <item row="6" column="0" colspan="2">
- <layout class="QHBoxLayout" name="horizontalLayout">
- <property name="leftMargin">
- <number>0</number>
- </property>
- <property name="topMargin">
- <number>0</number>
- </property>
- <property name="rightMargin">
- <number>0</number>
- </property>
- <property name="bottomMargin">
- <number>0</number>
- </property>
- <item>
- <widget class="QLabel" name="label">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>Joystick:</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QComboBox" name="cbxJoystick">
- <property name="sizePolicy">
- <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="whatsThis">
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Selects which joystick will be used for joystick input, if any is present.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item row="0" column="0" colspan="2">
+ <item row="1" column="0" colspan="2">
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>0</number>
@@ -167,7 +125,7 @@
<widget class="QPushButton" name="btnKeyR">
<property name="minimumSize">
<size>
- <width>76</width>
+ <width>70</width>
<height>0</height>
</size>
</property>
@@ -258,7 +216,7 @@
<widget class="QPushButton" name="btnKeyL">
<property name="minimumSize">
<size>
- <width>76</width>
+ <width>70</width>
<height>0</height>
</size>
</property>
@@ -384,7 +342,7 @@
<widget class="QPushButton" name="btnKeyX">
<property name="minimumSize">
<size>
- <width>76</width>
+ <width>70</width>
<height>0</height>
</size>
</property>
@@ -464,7 +422,7 @@
<widget class="QPushButton" name="btnKeyY">
<property name="minimumSize">
<size>
- <width>76</width>
+ <width>70</width>
<height>0</height>
</size>
</property>
@@ -523,7 +481,7 @@
<widget class="QPushButton" name="btnKeyA">
<property name="minimumSize">
<size>
- <width>76</width>
+ <width>70</width>
<height>0</height>
</size>
</property>
@@ -615,7 +573,7 @@
<widget class="QPushButton" name="btnKeyB">
<property name="minimumSize">
<size>
- <width>76</width>
+ <width>70</width>
<height>0</height>
</size>
</property>
@@ -698,7 +656,7 @@
<widget class="QPushButton" name="btnKeySelect">
<property name="minimumSize">
<size>
- <width>76</width>
+ <width>70</width>
<height>0</height>
</size>
</property>
@@ -757,7 +715,7 @@
<widget class="QPushButton" name="btnKeyStart">
<property name="minimumSize">
<size>
- <width>76</width>
+ <width>70</width>
<height>0</height>
</size>
</property>
@@ -882,7 +840,7 @@
<widget class="QPushButton" name="btnKeyUp">
<property name="minimumSize">
<size>
- <width>76</width>
+ <width>70</width>
<height>0</height>
</size>
</property>
@@ -962,7 +920,7 @@
<widget class="QPushButton" name="btnKeyLeft">
<property name="minimumSize">
<size>
- <width>76</width>
+ <width>70</width>
<height>0</height>
</size>
</property>
@@ -1021,7 +979,7 @@
<widget class="QPushButton" name="btnKeyRight">
<property name="minimumSize">
<size>
- <width>76</width>
+ <width>70</width>
<height>0</height>
</size>
</property>
@@ -1113,7 +1071,7 @@
<widget class="QPushButton" name="btnKeyDown">
<property name="minimumSize">
<size>
- <width>76</width>
+ <width>70</width>
<height>0</height>
</size>
</property>
@@ -1289,7 +1247,7 @@
<widget class="QPushButton" name="btnJoyL">
<property name="minimumSize">
<size>
- <width>76</width>
+ <width>70</width>
<height>0</height>
</size>
</property>
@@ -1441,7 +1399,7 @@
<widget class="QPushButton" name="btnJoyUp">
<property name="minimumSize">
<size>
- <width>76</width>
+ <width>70</width>
<height>0</height>
</size>
</property>
@@ -1521,7 +1479,7 @@
<widget class="QPushButton" name="btnJoyLeft">
<property name="minimumSize">
<size>
- <width>76</width>
+ <width>70</width>
<height>0</height>
</size>
</property>
@@ -1580,7 +1538,7 @@
<widget class="QPushButton" name="btnJoyRight">
<property name="minimumSize">
<size>
- <width>76</width>
+ <width>70</width>
<height>0</height>
</size>
</property>
@@ -1672,7 +1630,7 @@
<widget class="QPushButton" name="btnJoyDown">
<property name="minimumSize">
<size>
- <width>76</width>
+ <width>70</width>
<height>0</height>
</size>
</property>
@@ -1814,7 +1772,7 @@
<widget class="QPushButton" name="btnJoyX">
<property name="minimumSize">
<size>
- <width>76</width>
+ <width>70</width>
<height>0</height>
</size>
</property>
@@ -1894,7 +1852,7 @@
<widget class="QPushButton" name="btnJoyY">
<property name="minimumSize">
<size>
- <width>76</width>
+ <width>70</width>
<height>0</height>
</size>
</property>
@@ -1953,7 +1911,7 @@
<widget class="QPushButton" name="btnJoyA">
<property name="minimumSize">
<size>
- <width>76</width>
+ <width>70</width>
<height>0</height>
</size>
</property>
@@ -2045,7 +2003,7 @@
<widget class="QPushButton" name="btnJoyB">
<property name="minimumSize">
<size>
- <width>76</width>
+ <width>70</width>
<height>0</height>
</size>
</property>
@@ -2128,7 +2086,7 @@
<widget class="QPushButton" name="btnJoySelect">
<property name="minimumSize">
<size>
- <width>76</width>
+ <width>70</width>
<height>0</height>
</size>
</property>
@@ -2187,7 +2145,7 @@
<widget class="QPushButton" name="btnJoyStart">
<property name="minimumSize">
<size>
- <width>76</width>
+ <width>70</width>
<height>0</height>
</size>
</property>
@@ -2251,7 +2209,7 @@
<widget class="QPushButton" name="btnJoyR">
<property name="minimumSize">
<size>
- <width>76</width>
+ <width>70</width>
<height>0</height>
</size>
</property>
@@ -2321,6 +2279,55 @@
</widget>
</widget>
</item>
+ <item row="7" column="0" colspan="2">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="label">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Joystick:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="cbxJoystick">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="whatsThis">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Selects which joystick will be used for joystick input, if any is present.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="lblInstanceNum">
+ <property name="text">
+ <string>Configuring mappings for instance X</string>
+ </property>
+ </widget>
+ </item>
</layout>
</widget>
<resources>
diff --git a/src/frontend/qt_sdl/LAN_PCap.cpp b/src/frontend/qt_sdl/LAN_PCap.cpp
index 57223eb..86c218f 100644
--- a/src/frontend/qt_sdl/LAN_PCap.cpp
+++ b/src/frontend/qt_sdl/LAN_PCap.cpp
@@ -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;
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/PathSettingsDialog.cpp b/src/frontend/qt_sdl/PathSettingsDialog.cpp
index 7fa517d..286032e 100644
--- a/src/frontend/qt_sdl/PathSettingsDialog.cpp
+++ b/src/frontend/qt_sdl/PathSettingsDialog.cpp
@@ -22,6 +22,7 @@
#include "types.h"
#include "Config.h"
+#include "Platform.h"
#include "PathSettingsDialog.h"
#include "ui_PathSettingsDialog.h"
@@ -43,6 +44,12 @@ PathSettingsDialog::PathSettingsDialog(QWidget* parent) : QDialog(parent), ui(ne
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()
diff --git a/src/frontend/qt_sdl/PathSettingsDialog.ui b/src/frontend/qt_sdl/PathSettingsDialog.ui
index 95f5acc..295b1c4 100644
--- a/src/frontend/qt_sdl/PathSettingsDialog.ui
+++ b/src/frontend/qt_sdl/PathSettingsDialog.ui
@@ -7,49 +7,63 @@
<x>0</x>
<y>0</y>
<width>439</width>
- <height>166</height>
+ <height>185</height>
</rect>
</property>
<property name="windowTitle">
<string>Path settings - melonDS</string>
</property>
<layout class="QGridLayout" name="gridLayout">
- <item row="0" column="1">
- <widget class="QLineEdit" name="txtSaveFilePath">
- <property name="clearButtonEnabled">
- <bool>true</bool>
+ <item row="3" column="0">
+ <widget class="QLabel" name="label_4">
+ <property name="text">
+ <string>Cheat files path:</string>
</property>
</widget>
</item>
- <item row="2" column="2">
+ <item row="3" column="2">
<widget class="QPushButton" name="btnCheatFileBrowse">
<property name="text">
<string>Browse...</string>
</property>
</widget>
</item>
- <item row="1" column="0">
+ <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="txtCheatFilePath">
+ <widget class="QLineEdit" name="txtSavestatePath">
<property name="clearButtonEnabled">
<bool>true</bool>
</property>
</widget>
</item>
- <item row="3" column="0" colspan="3">
- <widget class="QLabel" name="label">
+ <item row="1" column="2">
+ <widget class="QPushButton" name="btnSaveFileBrowse">
<property name="text">
- <string>Leave a path blank to use the current ROM's path.</string>
+ <string>Browse...</string>
</property>
</widget>
</item>
- <item row="5" column="0" colspan="3">
+ <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>
@@ -59,45 +73,38 @@
</property>
</widget>
</item>
- <item row="2" column="0">
- <widget class="QLabel" name="label_4">
+ <item row="2" column="2">
+ <widget class="QPushButton" name="btnSavestateBrowse">
<property name="text">
- <string>Cheat files path:</string>
+ <string>Browse...</string>
</property>
</widget>
</item>
- <item row="0" column="2">
- <widget class="QPushButton" name="btnSaveFileBrowse">
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_2">
<property name="text">
- <string>Browse...</string>
+ <string>Save files path:</string>
</property>
</widget>
</item>
- <item row="1" column="2">
- <widget class="QPushButton" name="btnSavestateBrowse">
+ <item row="4" column="0" colspan="3">
+ <widget class="QLabel" name="label">
<property name="text">
- <string>Browse...</string>
+ <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="txtSavestatePath">
+ <widget class="QLineEdit" name="txtSaveFilePath">
<property name="clearButtonEnabled">
<bool>true</bool>
</property>
</widget>
</item>
- <item row="0" 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_5">
+ <item row="0" column="0" colspan="3">
+ <widget class="QLabel" name="lblInstanceNum">
<property name="text">
- <string/>
+ <string>Configuring paths for instance X</string>
</property>
</widget>
</item>
diff --git a/src/frontend/qt_sdl/Platform.cpp b/src/frontend/qt_sdl/Platform.cpp
index 4306c98..68bdd3e 100644
--- a/src/frontend/qt_sdl/Platform.cpp
+++ b/src/frontend/qt_sdl/Platform.cpp
@@ -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,32 +28,80 @@
#include <QSemaphore>
#include <QMutex>
#include <QOpenGLContext>
+#include <QSharedMemory>
#include "Platform.h"
#include "Config.h"
#include "ROMManager.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;
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);
+}
-#define NIFI_VER 1
+void IPCDeInit()
+{
+ if (IPCBuffer)
+ {
+ IPCBuffer->lock();
+ u8* data = (u8*)IPCBuffer->data();
+ *(u16*)&data[0] &= ~(1<<IPCInstanceID);
+ IPCBuffer->unlock();
+
+ IPCBuffer->detach();
+ delete IPCBuffer;
+ }
+
+ IPCBuffer = nullptr;
+}
void Init(int argc, char** argv)
@@ -110,10 +137,13 @@ void Init(int argc, char** argv)
confdir = config.absolutePath() + "/melonDS/";
EmuDirectory = confdir.toStdString();
#endif
+
+ IPCInit();
}
void DeInit()
{
+ IPCDeInit();
}
@@ -123,6 +153,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};
@@ -169,7 +215,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;
}
@@ -372,6 +417,11 @@ bool Mutex_TryLock(Mutex* mutex)
return ((QMutex*) mutex)->try_lock();
}
+void Sleep(u64 usecs)
+{
+ QThread::usleep(usecs);
+}
+
void WriteNDSSave(const u8* savedata, u32 savelen, u32 writeoffset, u32 writelen)
{
@@ -386,146 +436,60 @@ void WriteGBASave(const u8* savedata, u32 savelen, u32 writeoffset, u32 writelen
}
+
bool MP_Init()
{
- int opt_true = 1;
- int res;
-
-#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;
- }
-
-#if defined(BSD) || defined(__APPLE__)
- res = setsockopt(MPSocket, SOL_SOCKET, SO_REUSEPORT, (const char*)&opt_true, sizeof(int));
- if (res < 0)
- {
- closesocket(MPSocket);
- MPSocket = INVALID_SOCKET;
- return false;
- }
-#endif
-
- 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;
- }
-
- 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;
+ 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;
+ return LocalMP::End();
+}
- FD_ZERO(&fd);
- FD_SET(MPSocket, &fd);
- tv.tv_sec = 0;
- tv.tv_usec = block ? 5000 : 0;
+int MP_SendPacket(u8* data, int len, u64 timestamp)
+{
+ return LocalMP::SendPacket(data, len, timestamp);
+}
- if (!select(MPSocket+1, &fd, 0, 0, &tv))
- {
- return 0;
- }
+int MP_RecvPacket(u8* data, u64* timestamp)
+{
+ return LocalMP::RecvPacket(data, timestamp);
+}
- 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_SendCmd(u8* data, int len, u64 timestamp)
+{
+ return LocalMP::SendCmd(data, len, timestamp);
+}
- if (ntohl(*(u32*)&PacketBuffer[0]) != 0x4946494E)
- {
- return 0;
- }
+int MP_SendReply(u8* data, int len, u64 timestamp, u16 aid)
+{
+ return LocalMP::SendReply(data, len, timestamp, aid);
+}
- if (PacketBuffer[4] != NIFI_VER)
- {
- return 0;
- }
+int MP_SendAck(u8* data, int len, u64 timestamp)
+{
+ return LocalMP::SendAck(data, len, timestamp);
+}
- if (ntohs(*(u16*)&PacketBuffer[6]) != rlen)
- {
- return 0;
- }
+int MP_RecvHostPacket(u8* data, u64* timestamp)
+{
+ return LocalMP::RecvHostPacket(data, timestamp);
+}
- memcpy(data, &PacketBuffer[8], rlen);
- return rlen;
+u16 MP_RecvReplies(u8* data, u64 timestamp, u16 aidmask)
+{
+ return LocalMP::RecvReplies(data, timestamp, aidmask);
}
@@ -573,9 +537,4 @@ int LAN_RecvPacket(u8* data)
return LAN_Socket::RecvPacket(data);
}
-void Sleep(u64 usecs)
-{
- QThread::usleep(usecs);
-}
-
}
diff --git a/src/frontend/qt_sdl/PowerManagement/PowerManagementDialog.cpp b/src/frontend/qt_sdl/PowerManagement/PowerManagementDialog.cpp
index 499c176..89f74e5 100644
--- a/src/frontend/qt_sdl/PowerManagement/PowerManagementDialog.cpp
+++ b/src/frontend/qt_sdl/PowerManagement/PowerManagementDialog.cpp
@@ -23,6 +23,7 @@
#include "DSi_I2C.h"
#include "NDS.h"
#include "Config.h"
+#include "Platform.h"
#include "types.h"
@@ -65,6 +66,12 @@ PowerManagementDialog::PowerManagementDialog(QWidget* parent) : QDialog(parent),
}
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;
}
diff --git a/src/frontend/qt_sdl/PowerManagement/PowerManagementDialog.ui b/src/frontend/qt_sdl/PowerManagement/PowerManagementDialog.ui
index e0e7c6e..77af225 100644
--- a/src/frontend/qt_sdl/PowerManagement/PowerManagementDialog.ui
+++ b/src/frontend/qt_sdl/PowerManagement/PowerManagementDialog.ui
@@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>562</width>
- <height>279</height>
+ <height>288</height>
</rect>
</property>
<property name="sizePolicy">
@@ -23,37 +23,7 @@
<property name="sizeConstraint">
<enum>QLayout::SetFixedSize</enum>
</property>
- <item row="0" 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="3" column="0">
+ <item row="4" column="0">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
@@ -63,7 +33,7 @@
</property>
</widget>
</item>
- <item row="2" column="0">
+ <item row="3" column="0">
<widget class="QGroupBox" name="grpDSiBattery">
<property name="title">
<string>DSi Battery</string>
@@ -219,6 +189,49 @@
</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>
diff --git a/src/frontend/qt_sdl/ROMManager.cpp b/src/frontend/qt_sdl/ROMManager.cpp
index 304862e..716a454 100644
--- a/src/frontend/qt_sdl/ROMManager.cpp
+++ b/src/frontend/qt_sdl/ROMManager.cpp
@@ -326,6 +326,7 @@ bool LoadState(std::string filename)
std::string savefile = filename.substr(LastSep(filename)+1);
savefile = GetAssetPath(false, Config::SaveFilePath, ".sav", savefile);
+ savefile += Platform::InstanceFileSuffix();
NDSSave->SetPath(savefile, true);
}
@@ -350,6 +351,7 @@ bool SaveState(std::string filename)
{
std::string savefile = filename.substr(LastSep(filename)+1);
savefile = GetAssetPath(false, Config::SaveFilePath, ".sav", savefile);
+ savefile += Platform::InstanceFileSuffix();
NDSSave->SetPath(savefile, false);
}
@@ -432,6 +434,7 @@ void Reset()
{
std::string oldsave = NDSSave->GetPath();
std::string newsave = GetAssetPath(false, Config::SaveFilePath, ".sav");
+ newsave += Platform::InstanceFileSuffix();
if (oldsave != newsave)
NDSSave->SetPath(newsave, false);
}
@@ -440,6 +443,7 @@ void Reset()
{
std::string oldsave = GBASave->GetPath();
std::string newsave = GetAssetPath(true, Config::SaveFilePath, ".sav");
+ newsave += Platform::InstanceFileSuffix();
if (oldsave != newsave)
GBASave->SetPath(newsave, false);
}
@@ -562,7 +566,11 @@ bool LoadROM(QStringList filepath, bool reset)
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);
@@ -711,7 +719,11 @@ bool LoadGBAROM(QStringList filepath)
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);
diff --git a/src/frontend/qt_sdl/WifiSettingsDialog.cpp b/src/frontend/qt_sdl/WifiSettingsDialog.cpp
index 19cece6..9bf265e 100644
--- a/src/frontend/qt_sdl/WifiSettingsDialog.cpp
+++ b/src/frontend/qt_sdl/WifiSettingsDialog.cpp
@@ -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);
+ ui->lblAdapterMAC->setText("(none)");
+ ui->lblAdapterIP->setText("(none)");
int sel = 0;
for (int i = 0; i < LAN_PCap::NumAdapters; i++)
@@ -88,7 +88,6 @@ void WifiSettingsDialog::done(int r)
if (r == QDialog::Accepted)
{
- Config::SocketBindAnyAddr = ui->cbBindAnyAddr->isChecked();
Config::DirectLAN = ui->rbDirectMode->isChecked();
int sel = ui->cbxDirectAdapter->currentIndex();
diff --git a/src/frontend/qt_sdl/WifiSettingsDialog.ui b/src/frontend/qt_sdl/WifiSettingsDialog.ui
index 0897059..444e1d5 100644
--- a/src/frontend/qt_sdl/WifiSettingsDialog.ui
+++ b/src/frontend/qt_sdl/WifiSettingsDialog.ui
@@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>572</width>
- <height>273</height>
+ <height>217</height>
</rect>
</property>
<property name="sizePolicy">
@@ -26,16 +26,26 @@
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
- <string>Local</string>
+ <string>Network mode</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
- <widget class="QCheckBox" name="cbBindAnyAddr">
+ <widget class="QRadioButton" name="rbIndirectMode">
+ <property name="whatsThis">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Indirect mode uses libslirp. It requires no extra setup and is easy to use.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="text">
+ <string>Indirect mode (uses libslirp, recommended)</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QRadioButton" name="rbDirectMode">
<property name="whatsThis">
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Enabling this allows (theoretically) playing local multiplayer games over a local network. It may or may not help make for a better connection in general.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Direct mode directly routes network traffic to the host network. It is the most reliable, but requires an ethernet connection.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Non-direct mode uses a layer of emulation to get around this, but is more prone to problems.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
- <string>Bind socket to any address</string>
+ <string>Direct mode [TEXT PLACEHOLDER]</string>
</property>
</widget>
</item>
@@ -43,91 +53,62 @@
</widget>
</item>
<item>
- <widget class="QGroupBox" name="groupBox_2">
+ <widget class="QGroupBox" name="groupBox_3">
<property name="title">
- <string>Online</string>
+ <string>Direct mode settings</string>
</property>
- <layout class="QGridLayout" name="gridLayout_2">
- <item row="3" column="0" rowspan="3" colspan="2">
- <widget class="QGroupBox" name="groupBox_3">
- <property name="title">
- <string>Direct mode settings</string>
+ <layout class="QGridLayout" name="gridLayout_3">
+ <item row="0" column="0">
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Network adapter:</string>
</property>
- <layout class="QGridLayout" name="gridLayout_3">
- <item row="0" column="0">
- <widget class="QLabel" name="label">
- <property name="text">
- <string>Network adapter:</string>
- </property>
- </widget>
- </item>
- <item row="0" column="1">
- <widget class="QComboBox" name="cbxDirectAdapter">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>300</width>
- <height>0</height>
- </size>
- </property>
- <property name="whatsThis">
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Selects the network adapter through which to route network traffic under direct mode.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
- </property>
- </widget>
- </item>
- <item row="1" column="0">
- <widget class="QLabel" name="label_2">
- <property name="text">
- <string>MAC address:</string>
- </property>
- </widget>
- </item>
- <item row="1" column="1">
- <widget class="QLabel" name="lblAdapterMAC">
- <property name="text">
- <string>[PLACEHOLDER]</string>
- </property>
- </widget>
- </item>
- <item row="2" column="0">
- <widget class="QLabel" name="label_3">
- <property name="text">
- <string>IP address:</string>
- </property>
- </widget>
- </item>
- <item row="2" column="1">
- <widget class="QLabel" name="lblAdapterIP">
- <property name="text">
- <string>[PLACEHOLDER]</string>
- </property>
- </widget>
- </item>
- </layout>
</widget>
</item>
- <item row="1" column="0">
- <widget class="QRadioButton" name="rbIndirectMode">
+ <item row="0" column="1">
+ <widget class="QComboBox" name="cbxDirectAdapter">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>300</width>
+ <height>0</height>
+ </size>
+ </property>
<property name="whatsThis">
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Indirect mode uses libslirp. It requires no extra setup and is easy to use.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Selects the network adapter through which to route network traffic under direct mode.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_2">
<property name="text">
- <string>Indirect mode (uses libslirp, recommended)</string>
+ <string>MAC address:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QLabel" name="lblAdapterMAC">
+ <property name="text">
+ <string>[PLACEHOLDER]</string>
</property>
</widget>
</item>
<item row="2" column="0">
- <widget class="QRadioButton" name="rbDirectMode">
- <property name="whatsThis">
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Direct mode directly routes network traffic to the host network. It is the most reliable, but requires an ethernet connection.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Non-direct mode uses a layer of emulation to get around this, but is more prone to problems.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ <widget class="QLabel" name="label_3">
+ <property name="text">
+ <string>IP address:</string>
</property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QLabel" name="lblAdapterIP">
<property name="text">
- <string>Direct mode [TEXT PLACEHOLDER]</string>
+ <string>[PLACEHOLDER]</string>
</property>
</widget>
</item>
diff --git a/src/frontend/qt_sdl/main.cpp b/src/frontend/qt_sdl/main.cpp
index 2401a53..34cd03a 100644
--- a/src/frontend/qt_sdl/main.cpp
+++ b/src/frontend/qt_sdl/main.cpp
@@ -25,6 +25,7 @@
#include <string>
#include <algorithm>
+#include <QProcess>
#include <QApplication>
#include <QMessageBox>
#include <QMenuBar>
@@ -57,6 +58,7 @@
#include "AudioSettingsDialog.h"
#include "FirmwareSettingsDialog.h"
#include "PathSettingsDialog.h"
+#include "MPSettingsDialog.h"
#include "WifiSettingsDialog.h"
#include "InterfaceSettingsDialog.h"
#include "ROMInfoDialog.h"
@@ -77,6 +79,7 @@
#include "SPU.h"
#include "Wifi.h"
#include "Platform.h"
+#include "LocalMP.h"
#include "Config.h"
#include "Savestate.h"
@@ -101,6 +104,7 @@ bool videoSettingsDirty;
SDL_AudioDeviceID audioDevice;
int audioFreq;
+bool audioMuted;
SDL_cond* audioSync;
SDL_mutex* audioSyncLock;
@@ -138,7 +142,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;
@@ -158,6 +162,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()
{
@@ -646,7 +667,11 @@ void EmuThread::run()
if (winUpdateFreq < 1)
winUpdateFreq = 1;
- sprintf(melontitle, "[%d/%.0f] melonDS " MELONDS_VERSION, fps, fpstarget);
+ 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);
}
}
@@ -661,7 +686,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);
@@ -1330,6 +1359,9 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent)
setWindowTitle("melonDS " MELONDS_VERSION);
setAttribute(Qt::WA_DeleteOnClose);
setAcceptDrops(true);
+ setFocusPolicy(Qt::ClickFocus);
+
+ int inst = Platform::InstanceID();
QMenuBar* menubar = new QMenuBar();
{
@@ -1462,19 +1494,30 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent)
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);
+ 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");
@@ -1483,7 +1526,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
@@ -1497,15 +1540,18 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent)
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);
- actInterfaceSettings = menu->addAction("Interface settings");
- connect(actInterfaceSettings, &QAction::triggered, this, &MainWindow::onOpenInterfaceSettings);
-
actFirmwareSettings = menu->addAction("Firmware settings");
connect(actFirmwareSettings, &QAction::triggered, this, &MainWindow::onOpenFirmwareSettings);
+ actInterfaceSettings = menu->addAction("Interface settings");
+ connect(actInterfaceSettings, &QAction::triggered, this, &MainWindow::onOpenInterfaceSettings);
+
actPathSettings = menu->addAction("Path settings");
connect(actPathSettings, &QAction::triggered, this, &MainWindow::onOpenPathSettings);
@@ -1661,6 +1707,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();
@@ -1740,6 +1789,19 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent)
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()
@@ -1828,7 +1890,7 @@ void MainWindow::keyPressEvent(QKeyEvent* event)
if (event->isAutoRepeat()) return;
// TODO!! REMOVE ME IN RELEASE BUILDS!!
- //if (event->key() == Qt::Key_F11) NDS::debug(0);
+ if (event->key() == Qt::Key_F11) NDS::debug(0);
Input::KeyPress(event);
}
@@ -1930,6 +1992,16 @@ void MainWindow::dropEvent(QDropEvent* event)
}
}
+void MainWindow::focusInEvent(QFocusEvent* event)
+{
+ audioMute();
+}
+
+void MainWindow::focusOutEvent(QFocusEvent* event)
+{
+ audioMute();
+}
+
void MainWindow::onAppStateChanged(Qt::ApplicationState state)
{
if (state == Qt::ApplicationInactive)
@@ -2602,6 +2674,23 @@ 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();
@@ -2736,6 +2825,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();
@@ -2746,12 +2851,6 @@ void MainWindow::onOpenWifiSettings()
void MainWindow::onWifiSettingsFinished(int res)
{
- if (Wifi::MPInited)
- {
- Platform::MP_DeInit();
- Platform::MP_Init();
- }
-
Platform::LAN_DeInit();
Platform::LAN_Init();
@@ -3070,6 +3169,7 @@ int main(int argc, char** argv)
format.setSwapInterval(0);
QSurfaceFormat::setDefaultFormat(format);
+ audioMuted = false;
audioSync = SDL_CreateCond();
audioSyncLock = SDL_CreateMutex();
@@ -3123,6 +3223,8 @@ int main(int argc, char** argv)
emuThread->start();
emuThread->emuPause();
+ audioMute();
+
QObject::connect(&melon, &QApplication::applicationStateChanged, mainWindow, &MainWindow::onAppStateChanged);
if (argc > 1)
@@ -3180,12 +3282,13 @@ 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);
printf("\n");
- }*/
+ }
int ret = main(argc, argv);
diff --git a/src/frontend/qt_sdl/main.h b/src/frontend/qt_sdl/main.h
index 45d1da0..5d03e54 100644
--- a/src/frontend/qt_sdl/main.h
+++ b/src/frontend/qt_sdl/main.h
@@ -226,6 +226,9 @@ protected:
void dragEnterEvent(QDragEnterEvent* event) override;
void dropEvent(QDropEvent* event) override;
+ void focusInEvent(QFocusEvent* event) override;
+ void focusOutEvent(QFocusEvent* event) override;
+
signals:
void screenLayoutChange();
@@ -255,6 +258,7 @@ private slots:
void onROMInfo();
void onRAMInfo();
void onOpenTitleManager();
+ void onMPNewInstance();
void onOpenEmuSettings();
void onEmuSettingsDialogFinished(int res);
@@ -267,6 +271,8 @@ private slots:
void onOpenPathSettings();
void onUpdateAudioSettings();
void onAudioSettingsFinished(int res);
+ void onOpenMPSettings();
+ void onMPSettingsFinished(int res);
void onOpenWifiSettings();
void onWifiSettingsFinished(int res);
void onFirmwareSettingsFinished(int res);
@@ -344,12 +350,17 @@ public:
QAction* actROMInfo;
QAction* actRAMInfo;
QAction* actTitleManager;
+ QAction* actMPNewInstance;
QAction* actEmuSettings;
+#ifdef __APPLE__
+ QAction* actPreferences;
+#endif
QAction* actPowerManagement;
QAction* actInputConfig;
QAction* actVideoSettings;
QAction* actAudioSettings;
+ QAction* actMPSettings;
QAction* actWifiSettings;
QAction* actFirmwareSettings;
QAction* actPathSettings;
diff --git a/src/frontend/qt_sdl/sem_timedwait.cpp b/src/frontend/qt_sdl/sem_timedwait.cpp
new file mode 100644
index 0000000..38b3c16
--- /dev/null
+++ b/src/frontend/qt_sdl/sem_timedwait.cpp
@@ -0,0 +1,488 @@
+/*
+ * s e m _ t i m e d w a i t
+ *
+ * Function:
+ * Implements a version of sem_timedwait().
+ *
+ * Description:
+ * Not all systems implement sem_timedwait(), which is a version of
+ * sem_wait() with a timeout. Mac OS X is one example, at least up to
+ * and including version 10.6 (Leopard). If such a function is needed,
+ * this code provides a reasonable implementation, which I think is
+ * compatible with the standard version, although possibly less
+ * efficient. It works by creating a thread that interrupts a normal
+ * sem_wait() call after the specified timeout.
+ *
+ * Call:
+ *
+ * The Linux man pages say:
+ *
+ * #include <semaphore.h>
+ *
+ * int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
+ *
+ * sem_timedwait() is the same as sem_wait(), except that abs_timeout
+ * specifies a limit on the amount of time that the call should block if
+ * the decrement cannot be immediately performed. The abs_timeout argument
+ * points to a structure that specifies an absolute timeout in seconds and
+ * nanoseconds since the Epoch (00:00:00, 1 January 1970). This structure
+ * is defined as follows:
+ *
+ * struct timespec {
+ * time_t tv_sec; Seconds
+ * long tv_nsec; Nanoseconds [0 .. 999999999]
+ * };
+ *
+ * If the timeout has already expired by the time of the call, and the
+ * semaphore could not be locked immediately, then sem_timedwait() fails
+ * with a timeout error (errno set to ETIMEDOUT).
+ * If the operation can be performed immediately, then sem_timedwait()
+ * never fails with a timeout error, regardless of the value of abs_timeout.
+ * Furthermore, the validity of abs_timeout is not checked in this case.
+ *
+ * Limitations:
+ *
+ * The mechanism used involves sending a SIGUSR2 signal to the thread
+ * calling sem_timedwait(). The handler for this signal is set to a null
+ * routine which does nothing, and with any flags for the signal
+ * (eg SA_RESTART) cleared. Note that this effective disabling of the
+ * SIGUSR2 signal is a side-effect of using this routine, and means it
+ * may not be a completely transparent plug-in replacement for a
+ * 'normal' sig_timedwait() call. Since OS X does not declare the
+ * sem_timedwait() call in its standard include files, the relevant
+ * declaration (shown above in the man pages extract) will probably have
+ * to be added to any code that uses this.
+ *
+ * Compiling:
+ * This compiles and runs cleanly on OS X (10.6) with gcc with the
+ * -Wall -ansi -pedantic flags. On Linux, using -ansi causes a sweep of
+ * compiler complaints about the timespec structure, but it compiles
+ * and works fine with just -Wall -pedantic. (Since Linux provides
+ * sem_timedwait() anyway, this really isn't needed on Linux.) However,
+ * since Linux provides sem_timedwait anyway, the sem_timedwait()
+ * code in this file is only compiled on OS X, and is a null on other
+ * systems.
+ *
+ * Testing:
+ * This file contains a test program that exercises the sem_timedwait
+ * code. It is compiled if the pre-processor variable TEST is defined.
+ * For more details, see the comments for the test routine at the end
+ * of the file.
+ *
+ * Author: Keith Shortridge, AAO.
+ *
+ * History:
+ * 8th Sep 2009. Original version. KS.
+ * 24th Sep 2009. Added test that the calling thread still exists before
+ * trying to set the timed-out flag. KS.
+ * 2nd Oct 2009. No longer restores the original SIGUSR2 signal handler.
+ * See comments in the body of the code for more details.
+ * Prototypes for now discontinued internal routines removed.
+ * 12th Aug 2010. Added the cleanup handler, so that this code no longer
+ * leaks resources if the calling thread is cancelled. KS.
+ * 21st Sep 2011. Added copyright notice below. Modified header comments
+ * to describe the use of SIGUSR2 more accurately in the
+ * light of the 2/10/09 change above. Now undefs DEBUG
+ * before defining it, to avoid any possible clash. KS.
+ * 14th Feb 2012. Tidied out a number of TABs that had got into the
+ * code. KS.
+ * 6th May 2013. Copyright notice modified to one based on the MIT licence,
+ * which is more permissive than the previous notice. KS.
+ *
+ * Copyright (c) Australian Astronomical Observatory (AAO), (2013).
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#ifdef __APPLE__
+
+#include <semaphore.h>
+#include <time.h>
+#include <sys/time.h>
+#include <pthread.h>
+#include <errno.h>
+#include <signal.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/fcntl.h>
+#include <setjmp.h>
+
+#include "sem_timedwait.h"
+
+/* Some useful definitions - TRUE, FALSE, and DEBUG */
+
+#undef TRUE
+#define TRUE 1
+#undef FALSE
+#define FALSE 0
+#undef DEBUG
+#define DEBUG printf
+
+/* A structure of type timeoutDetails is passed to the thread used to
+ * implement the timeout.
+ */
+
+typedef struct {
+ struct timespec delay; /* Specifies the delay, relative to now */
+ pthread_t callingThread; /* The thread doing the sem_wait call */
+ volatile short *timedOutShort; /* Address of a flag set to indicate that
+ * the timeout was triggered. */
+} timeoutDetails;
+
+/* A structure of type cleanupDetails is passed to the thread cleanup
+ * routine which is called at the end of the routine or if the thread calling
+ * it is cancelled.
+ */
+
+typedef struct {
+ pthread_t *threadIdAddr; /* Address of the variable that holds
+ * the Id of the timeout thread. */
+ struct sigaction *sigHandlerAddr; /* Address of the old signal action
+ * handler. */
+ volatile short *timedOutShort; /* Address of a flag set to indicate that
+ * the timeout was triggered. */
+} cleanupDetails;
+
+/* Forward declarations of internal routines */
+
+static void* timeoutThreadMain (void* passedPtr);
+static int triggerSignal (int Signal, pthread_t Thread);
+static void ignoreSignal (int Signal);
+static void timeoutThreadCleanup (void* passedPtr);
+
+/* -------------------------------------------------------------------------- */
+/*
+ * s e m _ t i m e d w a i t
+ *
+ * This is the main code for the sem_timedwait() implementation.
+ */
+
+int sem_timedwait (
+ sem_t *sem,
+ const struct timespec *abs_timeout)
+{
+ int result = 0; /* Code returned by this routine 0 or -1 */
+
+ /* "Under no circumstances shall the function fail if the semaphore
+ * can be locked immediately". So we try to get it quickly to see if we
+ * can avoid all the timeout overheads.
+ */
+
+ if (sem_trywait(sem) == 0) {
+
+ /* Yes, got it immediately. */
+
+ result = 0;
+
+ } else {
+
+ /* No, we've got to do it with a sem_wait() call and a thread to run
+ * the timeout. First, work out the time from now to the specified
+ * timeout, which we will pass to the timeout thread in a way that can
+ * be used to pass to nanosleep(). So we need this in seconds and
+ * nanoseconds. Along the way, we check for an invalid passed time,
+ * and for one that's already expired.
+ */
+
+ if ((abs_timeout->tv_nsec < 0) || (abs_timeout->tv_nsec > 1000000000)) {
+
+ /* Passed time is invalid */
+
+ result = -1;
+ errno = EINVAL;
+
+ } else {
+
+ struct timeval currentTime; /* Time now */
+ long secsToWait,nsecsToWait; /* Seconds and nsec to delay */
+ gettimeofday (&currentTime,NULL);
+ secsToWait = abs_timeout->tv_sec - currentTime.tv_sec;
+ nsecsToWait = (abs_timeout->tv_nsec - (currentTime.tv_usec * 1000));
+ while (nsecsToWait < 0) {
+ nsecsToWait += 1000000000;
+ secsToWait--;
+ }
+ if ((secsToWait < 0) || ((secsToWait == 0) && (nsecsToWait < 0))) {
+
+ /* Time has passed. Report an immediate timeout. */
+
+ result = -1;
+ errno = ETIMEDOUT;
+
+ } else {
+
+ /* We're going to have to do a sem_wait() with a timeout thread.
+ * The thread will wait the specified time, then will issue a
+ * SIGUSR2 signal that will interrupt the sem_wait() call.
+ * We pass the thread the id of the current thread, the delay,
+ * and the address of a flag to set on a timeout, so we can
+ * distinguish an interrupt caused by the timeout thread from
+ * one caused by some other signal.
+ */
+
+ volatile short timedOut; /* Flag to set on timeout */
+ timeoutDetails details; /* All the stuff the thread must know */
+ struct sigaction oldSignalAction; /* Current signal setting */
+ pthread_t timeoutThread; /* Id of timeout thread */
+ cleanupDetails cleaningDetails; /* What the cleanup routine needs */
+ int oldCancelState; /* Previous cancellation state */
+ int ignoreCancelState; /* Used in call, but ignored */
+ int createStatus; /* Status of pthread_create() call */
+
+ /* If the current thread is cancelled (and CML does do this)
+ * we don't want to leave our timer thread running - if we've
+ * started the thread we want to make sure we join it in order
+ * to release its resources. So we set a cleanup handler to
+ * do this. We pass it the address of the structure that will
+ * hold all it needs to know. While we set all this up,
+ * we prevent ourselves being cancelled, so all this data is
+ * coherent.
+ */
+
+ pthread_setcancelstate (PTHREAD_CANCEL_DISABLE,&oldCancelState);
+ timeoutThread = (pthread_t) 0;
+ cleaningDetails.timedOutShort = &timedOut;
+ cleaningDetails.threadIdAddr = &timeoutThread;
+ cleaningDetails.sigHandlerAddr = &oldSignalAction;
+ pthread_cleanup_push (timeoutThreadCleanup,&cleaningDetails);
+
+ /* Set up the details for the thread. Clear the timeout flag,
+ * record the current SIGUSR2 action settings so we can restore
+ * them later.
+ */
+
+ details.delay.tv_sec = secsToWait;
+ details.delay.tv_nsec = nsecsToWait;
+ details.callingThread = pthread_self();
+ details.timedOutShort = &timedOut;
+ timedOut = FALSE;
+ sigaction (SIGUSR2,NULL,&oldSignalAction);
+
+ /* Start up the timeout thread. Once we've done that, we can
+ * restore the previous cancellation state.
+ */
+
+ createStatus = pthread_create(&timeoutThread,NULL,
+ timeoutThreadMain, (void*)&details);
+ pthread_setcancelstate (oldCancelState,&ignoreCancelState);
+
+ if (createStatus < 0) {
+
+ /* Failed to create thread. errno will already be set properly */
+
+ result = -1;
+
+ } else {
+
+ /* Thread created OK. This is where we wait for the semaphore.
+ */
+
+ if (sem_wait(sem) == 0) {
+
+ /* Got the semaphore OK. We return zero, and all's well. */
+
+ result = 0;
+
+ } else {
+
+ /* If we got a -1 error from sem_wait(), it may be because
+ * it was interrupted by a timeout, or failed for some
+ * other reason. We check for the expected timeout
+ * condition, which is an 'interrupted' status and the
+ * timeout flag set by the timeout thread. We report that as
+ * a timeout error. Anything else is some other error and
+ * errno is already set properly.
+ */
+
+ result = -1;
+ if (errno == EINTR) {
+ if (timedOut) errno = ETIMEDOUT;
+ }
+ }
+
+ }
+
+ /* The cleanup routine - timeoutThreadCleanup() - packages up
+ * any tidying up that is needed, including joining with the
+ * timer thread. This will be called if the current thread is
+ * cancelled, but we need it to happen anyway, so we set the
+ * execute flag true here as we remove it from the list of
+ * cleanup routines to be called. So normally, this line amounts
+ * to calling timeoutThreadCleanup().
+ */
+
+ pthread_cleanup_pop (TRUE);
+ }
+ }
+ }
+ return (result);
+}
+
+/* -------------------------------------------------------------------------- */
+/*
+ * t i m e o u t T h r e a d C l e a n u p
+ *
+ * This internal routine tidies up at the end of a sem_timedwait() call.
+ * It is set as a cleanup routine for the current thread (not the timer
+ * thread) so it is executed even if the thread is cancelled. This is
+ * important, as we need to tidy up the timeout thread. If we took the
+ * semaphore (in other words, if we didn't timeout) then the timer thread
+ * will still be running, sitting in its nanosleep() call, and we need
+ * to cancel it. If the timer thread did signal a timeout then it will
+ * now be closing down. In either case, we need to join it (using a call
+ * to pthread_join()) or its resources will never be released.
+ * The single argument is a pointer to a cleanupDetails structure that has
+ * all the routine needs to know.
+ */
+
+static void timeoutThreadCleanup (void* passedPtr)
+{
+ /* Get what we need from the structure we've been passed. */
+
+ cleanupDetails *detailsPtr = (cleanupDetails*) passedPtr;
+ short timedOut = *(detailsPtr->timedOutShort);
+ pthread_t timeoutThread = *(detailsPtr->threadIdAddr);
+
+ /* If we created the thread, stop it - doesn't matter if it's no longer
+ * running, pthread_cancel can handle that. We make sure we wait for it
+ * to complete, because it is this pthread_join() call that releases any
+ * memory the thread may have allocated. Note that cancelling a thread is
+ * generally not a good idea, because of the difficulty of cleaning up
+ * after it, but this is a very simple thread that does nothing but call
+ * nanosleep(), and that we can cancel quite happily.
+ */
+
+ if (!timedOut) pthread_cancel(timeoutThread);
+ pthread_join(timeoutThread,NULL);
+
+ /* The code originally restored the old action handler, which generally
+ * was the default handler that caused the task to exit. Just occasionally,
+ * there seem to be cases where the signal is still queued and ready to
+ * trigger even though the thread that presumably sent it off just before
+ * it was cancelled has finished. I had thought that once we'd joined
+ * that thread, we could be sure of not seeing the signal, but that seems
+ * not to be the case, and so restoring a handler that will allow the task
+ * to crash is not a good idea, and so the line below has been commented
+ * out.
+ *
+ * sigaction (SIGUSR2,detailsPtr->sigHandlerAddr,NULL);
+ */
+}
+
+/* -------------------------------------------------------------------------- */
+/*
+ * t i m e o u t T h r e a d M a i n
+ *
+ * This internal routine is the main code for the timeout thread.
+ * The single argument is a pointer to a timeoutDetails structure that has
+ * all the thread needs to know - thread to signal, delay time, and the
+ * address of a flag to set if it triggers a timeout.
+ */
+
+static void* timeoutThreadMain (void* passedPtr)
+{
+ void* Return = (void*) 0;
+
+ /* We grab all the data held in the calling thread right now. In some
+ * cases, we find that the calling thread has vanished and released
+ * its memory, including the details structure, by the time the timeout
+ * expires, and then we get an access violation when we try to set the
+ * 'timed out' flag.
+ */
+
+ timeoutDetails details = *((timeoutDetails*) passedPtr);
+ struct timespec requestedDelay = details.delay;
+
+ /* We do a nanosleep() for the specified delay, and then trigger a
+ * timeout. Note that we allow for the case where the nanosleep() is
+ * interrupted, and restart it for the remaining time. If the
+ * thread that is doing the sem_wait() call gets the semaphore, it
+ * will cancel this thread, which is fine as we aren't doing anything
+ * other than a sleep and a signal.
+ */
+
+ for (;;) {
+ struct timespec remainingDelay;
+ if (nanosleep (&requestedDelay,&remainingDelay) == 0) {
+ break;
+ } else if (errno == EINTR) {
+ requestedDelay = remainingDelay;
+ } else {
+ Return = (void*) errno;
+ break;
+ }
+ }
+
+ /* We've completed the delay without being cancelled, so we now trigger
+ * the timeout by sending a signal to the calling thread. And that's it,
+ * although we set the timeout flag first to indicate that it was us
+ * that interrupted the sem_wait() call. One precaution: before we
+ * try to set the timed-out flag, make sure the calling thread still
+ * exists - this may not be the case if things are closing down a bit
+ * messily. We check this quickly using a zero test signal.
+ */
+
+ if (pthread_kill(details.callingThread,0) == 0) {
+ *(details.timedOutShort) = TRUE;
+ if (triggerSignal (SIGUSR2,details.callingThread) < 0) {
+ Return = (void*) errno;
+ }
+ }
+
+ return Return;
+}
+
+/* -------------------------------------------------------------------------- */
+/*
+ * t r i g g e r S i g n a l
+ *
+ * This is a general purpose routine that sends a specified signal to
+ * a specified thread, setting up a signal handler that does nothing,
+ * and then giving the signal. The only effect will be to interrupt any
+ * operation that is currently blocking - in this case, we expect this to
+ * be a sem_wait() call.
+ */
+
+static int triggerSignal (int Signal, pthread_t Thread)
+{
+ int Result = 0;
+ struct sigaction SignalDetails;
+ SignalDetails.sa_handler = ignoreSignal;
+ SignalDetails.sa_flags = 0;
+ (void) sigemptyset(&SignalDetails.sa_mask);
+ if ((Result = sigaction(Signal,&SignalDetails,NULL)) == 0) {
+ Result = pthread_kill(Thread,Signal);
+ }
+ return Result;
+}
+
+/* -------------------------------------------------------------------------- */
+/*
+ * i g n o r e S i g n a l
+ *
+ * And this is the signal handler that does nothing. (It clears its argument,
+ * but this has no effect and prevents a compiler warning about an unused
+ * argument.)
+ */
+
+static void ignoreSignal (int Signal) {
+ Signal = 0;
+}
+
+#endif
diff --git a/src/frontend/qt_sdl/sem_timedwait.h b/src/frontend/qt_sdl/sem_timedwait.h
new file mode 100644
index 0000000..42ae201
--- /dev/null
+++ b/src/frontend/qt_sdl/sem_timedwait.h
@@ -0,0 +1,8 @@
+#ifndef __SEM_TIMEDWAIT_H
+#define __SEM_TIMEDWAIT_H
+
+#ifdef __APPLE__
+int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
+#endif
+
+#endif