diff options
Diffstat (limited to 'src/frontend')
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><html><head/><body><p>The bitrate of audio playback. If set to "Automatic" this will be 10-bit for DS mode and 16-bit for DSi mode.</p></body></html></string> + <string><html><head/><body><p>The bitrate of audio playback. If set to "Automatic" this will be 10-bit for DS mode and 16-bit for DSi mode.</p></body></html></string> </property> </widget> </item> diff --git a/src/frontend/qt_sdl/CMakeLists.txt b/src/frontend/qt_sdl/CMakeLists.txt index 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><html><head/><body><p>Selects which joystick will be used for joystick input, if any is present.</p></body></html></string> - </property> - </widget> - </item> - </layout> - </item> - <item row="0" column="0" colspan="2"> + <item row="1" column="0" colspan="2"> <widget class="QTabWidget" name="tabWidget"> <property name="currentIndex"> <number>0</number> @@ -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><html><head/><body><p>Selects which joystick will be used for joystick input, if any is present.</p></body></html></string> + </property> + </widget> + </item> + </layout> + </item> + <item row="0" column="0"> + <widget class="QLabel" name="lblInstanceNum"> + <property name="text"> + <string>Configuring mappings for instance X</string> + </property> + </widget> + </item> </layout> </widget> <resources> diff --git a/src/frontend/qt_sdl/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><html><head/><body><p>Indirect mode uses libslirp. It requires no extra setup and is easy to use.</p></body></html></string> + </property> + <property name="text"> + <string>Indirect mode (uses libslirp, recommended)</string> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QRadioButton" name="rbDirectMode"> <property name="whatsThis"> - <string><html><head/><body><p>Enabling this allows (theoretically) playing local multiplayer games over a local network. It may or may not help make for a better connection in general.</p></body></html></string> + <string><html><head/><body><p>Direct mode directly routes network traffic to the host network. It is the most reliable, but requires an ethernet connection.</p><p><br/></p><p>Non-direct mode uses a layer of emulation to get around this, but is more prone to problems.</p></body></html></string> </property> <property name="text"> - <string>Bind socket to any address</string> + <string>Direct mode [TEXT PLACEHOLDER]</string> </property> </widget> </item> @@ -43,91 +53,62 @@ </widget> </item> <item> - <widget class="QGroupBox" name="groupBox_2"> + <widget class="QGroupBox" name="groupBox_3"> <property name="title"> - <string>Online</string> + <string>Direct mode settings</string> </property> - <layout class="QGridLayout" name="gridLayout_2"> - <item row="3" column="0" rowspan="3" colspan="2"> - <widget class="QGroupBox" name="groupBox_3"> - <property name="title"> - <string>Direct mode settings</string> + <layout class="QGridLayout" name="gridLayout_3"> + <item row="0" column="0"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Network adapter:</string> </property> - <layout class="QGridLayout" name="gridLayout_3"> - <item row="0" column="0"> - <widget class="QLabel" name="label"> - <property name="text"> - <string>Network adapter:</string> - </property> - </widget> - </item> - <item row="0" column="1"> - <widget class="QComboBox" name="cbxDirectAdapter"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="minimumSize"> - <size> - <width>300</width> - <height>0</height> - </size> - </property> - <property name="whatsThis"> - <string><html><head/><body><p>Selects the network adapter through which to route network traffic under direct mode.</p></body></html></string> - </property> - </widget> - </item> - <item row="1" column="0"> - <widget class="QLabel" name="label_2"> - <property name="text"> - <string>MAC address:</string> - </property> - </widget> - </item> - <item row="1" column="1"> - <widget class="QLabel" name="lblAdapterMAC"> - <property name="text"> - <string>[PLACEHOLDER]</string> - </property> - </widget> - </item> - <item row="2" column="0"> - <widget class="QLabel" name="label_3"> - <property name="text"> - <string>IP address:</string> - </property> - </widget> - </item> - <item row="2" column="1"> - <widget class="QLabel" name="lblAdapterIP"> - <property name="text"> - <string>[PLACEHOLDER]</string> - </property> - </widget> - </item> - </layout> </widget> </item> - <item row="1" column="0"> - <widget class="QRadioButton" name="rbIndirectMode"> + <item row="0" column="1"> + <widget class="QComboBox" name="cbxDirectAdapter"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>300</width> + <height>0</height> + </size> + </property> <property name="whatsThis"> - <string><html><head/><body><p>Indirect mode uses libslirp. It requires no extra setup and is easy to use.</p></body></html></string> + <string><html><head/><body><p>Selects the network adapter through which to route network traffic under direct mode.</p></body></html></string> </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_2"> <property name="text"> - <string>Indirect mode (uses libslirp, recommended)</string> + <string>MAC address:</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QLabel" name="lblAdapterMAC"> + <property name="text"> + <string>[PLACEHOLDER]</string> </property> </widget> </item> <item row="2" column="0"> - <widget class="QRadioButton" name="rbDirectMode"> - <property name="whatsThis"> - <string><html><head/><body><p>Direct mode directly routes network traffic to the host network. It is the most reliable, but requires an ethernet connection.</p><p><br/></p><p>Non-direct mode uses a layer of emulation to get around this, but is more prone to problems.</p></body></html></string> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>IP address:</string> </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QLabel" name="lblAdapterIP"> <property name="text"> - <string>Direct mode [TEXT PLACEHOLDER]</string> + <string>[PLACEHOLDER]</string> </property> </widget> </item> diff --git a/src/frontend/qt_sdl/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 (¤tTime,NULL); + secsToWait = abs_timeout->tv_sec - currentTime.tv_sec; + nsecsToWait = (abs_timeout->tv_nsec - (currentTime.tv_usec * 1000)); + while (nsecsToWait < 0) { + nsecsToWait += 1000000000; + secsToWait--; + } + if ((secsToWait < 0) || ((secsToWait == 0) && (nsecsToWait < 0))) { + + /* Time has passed. Report an immediate timeout. */ + + result = -1; + errno = ETIMEDOUT; + + } else { + + /* We're going to have to do a sem_wait() with a timeout thread. + * The thread will wait the specified time, then will issue a + * SIGUSR2 signal that will interrupt the sem_wait() call. + * We pass the thread the id of the current thread, the delay, + * and the address of a flag to set on a timeout, so we can + * distinguish an interrupt caused by the timeout thread from + * one caused by some other signal. + */ + + volatile short timedOut; /* Flag to set on timeout */ + timeoutDetails details; /* All the stuff the thread must know */ + struct sigaction oldSignalAction; /* Current signal setting */ + pthread_t timeoutThread; /* Id of timeout thread */ + cleanupDetails cleaningDetails; /* What the cleanup routine needs */ + int oldCancelState; /* Previous cancellation state */ + int ignoreCancelState; /* Used in call, but ignored */ + int createStatus; /* Status of pthread_create() call */ + + /* If the current thread is cancelled (and CML does do this) + * we don't want to leave our timer thread running - if we've + * started the thread we want to make sure we join it in order + * to release its resources. So we set a cleanup handler to + * do this. We pass it the address of the structure that will + * hold all it needs to know. While we set all this up, + * we prevent ourselves being cancelled, so all this data is + * coherent. + */ + + pthread_setcancelstate (PTHREAD_CANCEL_DISABLE,&oldCancelState); + timeoutThread = (pthread_t) 0; + cleaningDetails.timedOutShort = &timedOut; + cleaningDetails.threadIdAddr = &timeoutThread; + cleaningDetails.sigHandlerAddr = &oldSignalAction; + pthread_cleanup_push (timeoutThreadCleanup,&cleaningDetails); + + /* Set up the details for the thread. Clear the timeout flag, + * record the current SIGUSR2 action settings so we can restore + * them later. + */ + + details.delay.tv_sec = secsToWait; + details.delay.tv_nsec = nsecsToWait; + details.callingThread = pthread_self(); + details.timedOutShort = &timedOut; + timedOut = FALSE; + sigaction (SIGUSR2,NULL,&oldSignalAction); + + /* Start up the timeout thread. Once we've done that, we can + * restore the previous cancellation state. + */ + + createStatus = pthread_create(&timeoutThread,NULL, + timeoutThreadMain, (void*)&details); + pthread_setcancelstate (oldCancelState,&ignoreCancelState); + + if (createStatus < 0) { + + /* Failed to create thread. errno will already be set properly */ + + result = -1; + + } else { + + /* Thread created OK. This is where we wait for the semaphore. + */ + + if (sem_wait(sem) == 0) { + + /* Got the semaphore OK. We return zero, and all's well. */ + + result = 0; + + } else { + + /* If we got a -1 error from sem_wait(), it may be because + * it was interrupted by a timeout, or failed for some + * other reason. We check for the expected timeout + * condition, which is an 'interrupted' status and the + * timeout flag set by the timeout thread. We report that as + * a timeout error. Anything else is some other error and + * errno is already set properly. + */ + + result = -1; + if (errno == EINTR) { + if (timedOut) errno = ETIMEDOUT; + } + } + + } + + /* The cleanup routine - timeoutThreadCleanup() - packages up + * any tidying up that is needed, including joining with the + * timer thread. This will be called if the current thread is + * cancelled, but we need it to happen anyway, so we set the + * execute flag true here as we remove it from the list of + * cleanup routines to be called. So normally, this line amounts + * to calling timeoutThreadCleanup(). + */ + + pthread_cleanup_pop (TRUE); + } + } + } + return (result); +} + +/* -------------------------------------------------------------------------- */ +/* + * t i m e o u t T h r e a d C l e a n u p + * + * This internal routine tidies up at the end of a sem_timedwait() call. + * It is set as a cleanup routine for the current thread (not the timer + * thread) so it is executed even if the thread is cancelled. This is + * important, as we need to tidy up the timeout thread. If we took the + * semaphore (in other words, if we didn't timeout) then the timer thread + * will still be running, sitting in its nanosleep() call, and we need + * to cancel it. If the timer thread did signal a timeout then it will + * now be closing down. In either case, we need to join it (using a call + * to pthread_join()) or its resources will never be released. + * The single argument is a pointer to a cleanupDetails structure that has + * all the routine needs to know. + */ + +static void timeoutThreadCleanup (void* passedPtr) +{ + /* Get what we need from the structure we've been passed. */ + + cleanupDetails *detailsPtr = (cleanupDetails*) passedPtr; + short timedOut = *(detailsPtr->timedOutShort); + pthread_t timeoutThread = *(detailsPtr->threadIdAddr); + + /* If we created the thread, stop it - doesn't matter if it's no longer + * running, pthread_cancel can handle that. We make sure we wait for it + * to complete, because it is this pthread_join() call that releases any + * memory the thread may have allocated. Note that cancelling a thread is + * generally not a good idea, because of the difficulty of cleaning up + * after it, but this is a very simple thread that does nothing but call + * nanosleep(), and that we can cancel quite happily. + */ + + if (!timedOut) pthread_cancel(timeoutThread); + pthread_join(timeoutThread,NULL); + + /* The code originally restored the old action handler, which generally + * was the default handler that caused the task to exit. Just occasionally, + * there seem to be cases where the signal is still queued and ready to + * trigger even though the thread that presumably sent it off just before + * it was cancelled has finished. I had thought that once we'd joined + * that thread, we could be sure of not seeing the signal, but that seems + * not to be the case, and so restoring a handler that will allow the task + * to crash is not a good idea, and so the line below has been commented + * out. + * + * sigaction (SIGUSR2,detailsPtr->sigHandlerAddr,NULL); + */ +} + +/* -------------------------------------------------------------------------- */ +/* + * t i m e o u t T h r e a d M a i n + * + * This internal routine is the main code for the timeout thread. + * The single argument is a pointer to a timeoutDetails structure that has + * all the thread needs to know - thread to signal, delay time, and the + * address of a flag to set if it triggers a timeout. + */ + +static void* timeoutThreadMain (void* passedPtr) +{ + void* Return = (void*) 0; + + /* We grab all the data held in the calling thread right now. In some + * cases, we find that the calling thread has vanished and released + * its memory, including the details structure, by the time the timeout + * expires, and then we get an access violation when we try to set the + * 'timed out' flag. + */ + + timeoutDetails details = *((timeoutDetails*) passedPtr); + struct timespec requestedDelay = details.delay; + + /* We do a nanosleep() for the specified delay, and then trigger a + * timeout. Note that we allow for the case where the nanosleep() is + * interrupted, and restart it for the remaining time. If the + * thread that is doing the sem_wait() call gets the semaphore, it + * will cancel this thread, which is fine as we aren't doing anything + * other than a sleep and a signal. + */ + + for (;;) { + struct timespec remainingDelay; + if (nanosleep (&requestedDelay,&remainingDelay) == 0) { + break; + } else if (errno == EINTR) { + requestedDelay = remainingDelay; + } else { + Return = (void*) errno; + break; + } + } + + /* We've completed the delay without being cancelled, so we now trigger + * the timeout by sending a signal to the calling thread. And that's it, + * although we set the timeout flag first to indicate that it was us + * that interrupted the sem_wait() call. One precaution: before we + * try to set the timed-out flag, make sure the calling thread still + * exists - this may not be the case if things are closing down a bit + * messily. We check this quickly using a zero test signal. + */ + + if (pthread_kill(details.callingThread,0) == 0) { + *(details.timedOutShort) = TRUE; + if (triggerSignal (SIGUSR2,details.callingThread) < 0) { + Return = (void*) errno; + } + } + + return Return; +} + +/* -------------------------------------------------------------------------- */ +/* + * t r i g g e r S i g n a l + * + * This is a general purpose routine that sends a specified signal to + * a specified thread, setting up a signal handler that does nothing, + * and then giving the signal. The only effect will be to interrupt any + * operation that is currently blocking - in this case, we expect this to + * be a sem_wait() call. + */ + +static int triggerSignal (int Signal, pthread_t Thread) +{ + int Result = 0; + struct sigaction SignalDetails; + SignalDetails.sa_handler = ignoreSignal; + SignalDetails.sa_flags = 0; + (void) sigemptyset(&SignalDetails.sa_mask); + if ((Result = sigaction(Signal,&SignalDetails,NULL)) == 0) { + Result = pthread_kill(Thread,Signal); + } + return Result; +} + +/* -------------------------------------------------------------------------- */ +/* + * i g n o r e S i g n a l + * + * And this is the signal handler that does nothing. (It clears its argument, + * but this has no effect and prevents a compiler warning about an unused + * argument.) + */ + +static void ignoreSignal (int Signal) { + Signal = 0; +} + +#endif diff --git a/src/frontend/qt_sdl/sem_timedwait.h b/src/frontend/qt_sdl/sem_timedwait.h new file mode 100644 index 0000000..42ae201 --- /dev/null +++ b/src/frontend/qt_sdl/sem_timedwait.h @@ -0,0 +1,8 @@ +#ifndef __SEM_TIMEDWAIT_H +#define __SEM_TIMEDWAIT_H + +#ifdef __APPLE__ +int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout); +#endif + +#endif |