From 25a7b1ca1d04cb9d860c31d8107fbf36b58c81e4 Mon Sep 17 00:00:00 2001 From: Arisotura Date: Sat, 15 Jun 2024 13:52:47 +0200 Subject: [PATCH] merge doublemelon (#2067) non-exhaustive (but exhausting) list of changes: * base laid for multiple window support, but will likely require more work to work correctly * encapsulation of frontend state for proper multi-instance support * (JIT still needs a fix for the NDS::Current workaround but we can get there later) * new, more flexible configuration system --- src/DSi.cpp | 24 +- src/DSi.h | 3 +- src/DSi_Camera.cpp | 14 +- src/DSi_NWifi.cpp | 16 +- src/FIFO.h | 117 + src/GBACart.cpp | 45 +- src/GBACart.h | 21 +- src/NDS.cpp | 7 +- src/NDS.h | 6 +- src/NDSCart.cpp | 100 +- src/NDSCart.h | 34 +- src/NDSCartR4.cpp | 4 +- src/Platform.h | 71 +- src/RTC.cpp | 2 +- src/SPI.cpp | 2 +- src/Wifi.cpp | 40 +- src/Wifi.h | 3 - src/WifiAP.cpp | 19 +- src/WifiAP.h | 3 +- src/frontend/FrontendUtil.h | 124 - .../{Util_Video.cpp => ScreenLayout.cpp} | 1101 ++++---- src/frontend/ScreenLayout.h | 104 + src/frontend/Util_Audio.cpp | 143 - src/frontend/qt_sdl/AudioInOut.cpp | 390 --- src/frontend/qt_sdl/AudioSettingsDialog.cpp | 126 +- src/frontend/qt_sdl/AudioSettingsDialog.h | 16 +- src/frontend/qt_sdl/CMakeLists.txt | 16 +- src/frontend/qt_sdl/CameraManager.cpp | 17 +- src/frontend/qt_sdl/CameraManager.h | 5 + src/frontend/qt_sdl/CameraSettingsDialog.cpp | 49 +- src/frontend/qt_sdl/CameraSettingsDialog.h | 13 +- src/frontend/qt_sdl/CheatsDialog.cpp | 8 +- src/frontend/qt_sdl/CheatsDialog.h | 4 + src/frontend/qt_sdl/Config.cpp | 997 ++++--- src/frontend/qt_sdl/Config.h | 239 +- src/frontend/qt_sdl/DateTimeDialog.cpp | 12 +- src/frontend/qt_sdl/DateTimeDialog.h | 3 + .../{ROMManager.cpp => EmuInstance.cpp} | 1044 ++++--- src/frontend/qt_sdl/EmuInstance.h | 304 +++ src/frontend/qt_sdl/EmuInstanceAudio.cpp | 457 ++++ src/frontend/qt_sdl/EmuInstanceInput.cpp | 307 +++ src/frontend/qt_sdl/EmuSettingsDialog.cpp | 300 +- src/frontend/qt_sdl/EmuSettingsDialog.h | 5 + src/frontend/qt_sdl/EmuThread.cpp | 718 +++-- src/frontend/qt_sdl/EmuThread.h | 96 +- .../qt_sdl/FirmwareSettingsDialog.cpp | 96 +- src/frontend/qt_sdl/FirmwareSettingsDialog.h | 3 + src/frontend/qt_sdl/Input.cpp | 265 -- .../qt_sdl/InputConfig/InputConfigDialog.cpp | 66 +- .../qt_sdl/InputConfig/InputConfigDialog.h | 6 + src/frontend/qt_sdl/InputConfig/MapButton.h | 35 +- .../qt_sdl/InterfaceSettingsDialog.cpp | 42 +- src/frontend/qt_sdl/InterfaceSettingsDialog.h | 6 +- src/frontend/qt_sdl/LocalMP.cpp | 489 +--- src/frontend/qt_sdl/LocalMP.h | 20 +- src/frontend/qt_sdl/MPSettingsDialog.cpp | 21 +- src/frontend/qt_sdl/MPSettingsDialog.h | 3 + src/frontend/qt_sdl/Net.cpp | 112 + src/frontend/qt_sdl/{AudioInOut.h => Net.h} | 32 +- .../qt_sdl/{LAN_PCap.cpp => Net_PCap.cpp} | 120 +- .../qt_sdl/{LAN_PCap.h => Net_PCap.h} | 15 +- .../qt_sdl/{LAN_Socket.cpp => Net_Slirp.cpp} | 90 +- .../qt_sdl/{LAN_Socket.h => Net_Slirp.h} | 13 +- src/frontend/qt_sdl/PacketDispatcher.cpp | 162 ++ .../qt_sdl/{Input.h => PacketDispatcher.h} | 49 +- src/frontend/qt_sdl/PathSettingsDialog.cpp | 61 +- src/frontend/qt_sdl/PathSettingsDialog.h | 3 + src/frontend/qt_sdl/Platform.cpp | 312 +-- .../PowerManagement/PowerManagementDialog.cpp | 80 +- .../PowerManagement/PowerManagementDialog.h | 10 +- src/frontend/qt_sdl/RAMInfoDialog.cpp | 17 +- src/frontend/qt_sdl/RAMInfoDialog.h | 12 +- src/frontend/qt_sdl/ROMInfoDialog.cpp | 18 +- src/frontend/qt_sdl/ROMInfoDialog.h | 10 +- src/frontend/qt_sdl/ROMManager.h | 104 - src/frontend/qt_sdl/Screen.cpp | 249 +- src/frontend/qt_sdl/Screen.h | 47 +- src/frontend/qt_sdl/TitleManagerDialog.cpp | 22 +- src/frontend/qt_sdl/TitleManagerDialog.h | 4 + src/frontend/qt_sdl/VideoSettingsDialog.cpp | 97 +- src/frontend/qt_sdl/VideoSettingsDialog.h | 4 + src/frontend/qt_sdl/WifiSettingsDialog.cpp | 45 +- src/frontend/qt_sdl/WifiSettingsDialog.h | 3 + src/frontend/qt_sdl/Window.cpp | 583 ++-- src/frontend/qt_sdl/Window.h | 52 +- src/frontend/qt_sdl/main.cpp | 329 +-- src/frontend/qt_sdl/main.h | 8 +- src/frontend/qt_sdl/toml/toml.hpp | 38 + src/frontend/qt_sdl/toml/toml/color.hpp | 64 + src/frontend/qt_sdl/toml/toml/combinator.hpp | 306 +++ src/frontend/qt_sdl/toml/toml/comments.hpp | 472 ++++ src/frontend/qt_sdl/toml/toml/datetime.hpp | 631 +++++ src/frontend/qt_sdl/toml/toml/exception.hpp | 65 + src/frontend/qt_sdl/toml/toml/from.hpp | 19 + src/frontend/qt_sdl/toml/toml/get.hpp | 1119 ++++++++ src/frontend/qt_sdl/toml/toml/into.hpp | 19 + src/frontend/qt_sdl/toml/toml/lexer.hpp | 293 ++ src/frontend/qt_sdl/toml/toml/literal.hpp | 113 + src/frontend/qt_sdl/toml/toml/macros.hpp | 121 + src/frontend/qt_sdl/toml/toml/parser.hpp | 2416 +++++++++++++++++ src/frontend/qt_sdl/toml/toml/region.hpp | 417 +++ src/frontend/qt_sdl/toml/toml/result.hpp | 717 +++++ src/frontend/qt_sdl/toml/toml/serializer.hpp | 922 +++++++ .../qt_sdl/toml/toml/source_location.hpp | 233 ++ src/frontend/qt_sdl/toml/toml/storage.hpp | 43 + src/frontend/qt_sdl/toml/toml/string.hpp | 228 ++ src/frontend/qt_sdl/toml/toml/traits.hpp | 328 +++ src/frontend/qt_sdl/toml/toml/types.hpp | 173 ++ src/frontend/qt_sdl/toml/toml/utility.hpp | 150 + src/frontend/qt_sdl/toml/toml/value.hpp | 2035 ++++++++++++++ src/frontend/qt_sdl/toml/toml/version.hpp | 42 + 111 files changed, 16784 insertions(+), 5024 deletions(-) delete mode 100644 src/frontend/FrontendUtil.h rename src/frontend/{Util_Video.cpp => ScreenLayout.cpp} (93%) create mode 100644 src/frontend/ScreenLayout.h delete mode 100644 src/frontend/Util_Audio.cpp delete mode 100644 src/frontend/qt_sdl/AudioInOut.cpp rename src/frontend/qt_sdl/{ROMManager.cpp => EmuInstance.cpp} (55%) create mode 100644 src/frontend/qt_sdl/EmuInstance.h create mode 100644 src/frontend/qt_sdl/EmuInstanceAudio.cpp create mode 100644 src/frontend/qt_sdl/EmuInstanceInput.cpp delete mode 100644 src/frontend/qt_sdl/Input.cpp create mode 100644 src/frontend/qt_sdl/Net.cpp rename src/frontend/qt_sdl/{AudioInOut.h => Net.h} (67%) rename src/frontend/qt_sdl/{LAN_PCap.cpp => Net_PCap.cpp} (80%) rename src/frontend/qt_sdl/{LAN_PCap.h => Net_PCap.h} (86%) rename src/frontend/qt_sdl/{LAN_Socket.cpp => Net_Slirp.cpp} (86%) rename src/frontend/qt_sdl/{LAN_Socket.h => Net_Slirp.h} (87%) create mode 100644 src/frontend/qt_sdl/PacketDispatcher.cpp rename src/frontend/qt_sdl/{Input.h => PacketDispatcher.h} (53%) delete mode 100644 src/frontend/qt_sdl/ROMManager.h create mode 100644 src/frontend/qt_sdl/toml/toml.hpp create mode 100644 src/frontend/qt_sdl/toml/toml/color.hpp create mode 100644 src/frontend/qt_sdl/toml/toml/combinator.hpp create mode 100644 src/frontend/qt_sdl/toml/toml/comments.hpp create mode 100644 src/frontend/qt_sdl/toml/toml/datetime.hpp create mode 100644 src/frontend/qt_sdl/toml/toml/exception.hpp create mode 100644 src/frontend/qt_sdl/toml/toml/from.hpp create mode 100644 src/frontend/qt_sdl/toml/toml/get.hpp create mode 100644 src/frontend/qt_sdl/toml/toml/into.hpp create mode 100644 src/frontend/qt_sdl/toml/toml/lexer.hpp create mode 100644 src/frontend/qt_sdl/toml/toml/literal.hpp create mode 100644 src/frontend/qt_sdl/toml/toml/macros.hpp create mode 100644 src/frontend/qt_sdl/toml/toml/parser.hpp create mode 100644 src/frontend/qt_sdl/toml/toml/region.hpp create mode 100644 src/frontend/qt_sdl/toml/toml/result.hpp create mode 100644 src/frontend/qt_sdl/toml/toml/serializer.hpp create mode 100644 src/frontend/qt_sdl/toml/toml/source_location.hpp create mode 100644 src/frontend/qt_sdl/toml/toml/storage.hpp create mode 100644 src/frontend/qt_sdl/toml/toml/string.hpp create mode 100644 src/frontend/qt_sdl/toml/toml/traits.hpp create mode 100644 src/frontend/qt_sdl/toml/toml/types.hpp create mode 100644 src/frontend/qt_sdl/toml/toml/utility.hpp create mode 100644 src/frontend/qt_sdl/toml/toml/value.hpp create mode 100644 src/frontend/qt_sdl/toml/toml/version.hpp diff --git a/src/DSi.cpp b/src/DSi.cpp index 306c5d1ccf..01906b4d97 100644 --- a/src/DSi.cpp +++ b/src/DSi.cpp @@ -70,8 +70,28 @@ const u32 NDMAModes[] = 0xFF, // wifi / GBA cart slot (TODO) }; -DSi::DSi(DSiArgs&& args) noexcept : - NDS(std::move(args), 1), +/*DSi::DSi() noexcept : + DSi( + DSiArgs { + NDSArgs { + nullptr, + nullptr, + bios_arm9_bin, + bios_arm7_bin, + Firmware(0), + }, + nullptr, + nullptr, + nullptr, + nullptr, + false + } + ) +{ +}*/ + +DSi::DSi(DSiArgs&& args, void* userdata) noexcept : + NDS(std::move(args), 1, userdata), NDMAs { DSi_NDMA(0, 0, *this), DSi_NDMA(0, 1, *this), diff --git a/src/DSi.h b/src/DSi.h index 1d010e0fdc..3d8ced4b09 100644 --- a/src/DSi.h +++ b/src/DSi.h @@ -130,7 +130,8 @@ class DSi final : public NDS void ARM7IOWrite32(u32 addr, u32 val) override; public: - DSi(DSiArgs&& args) noexcept; + DSi(DSiArgs&& args, void* userdata = nullptr) noexcept; + //DSi() noexcept; ~DSi() noexcept override; DSi(const DSi&) = delete; DSi& operator=(const DSi&) = delete; diff --git a/src/DSi_Camera.cpp b/src/DSi_Camera.cpp index a1cdbe0a25..db797340a5 100644 --- a/src/DSi_Camera.cpp +++ b/src/DSi_Camera.cpp @@ -410,7 +410,7 @@ void DSi_Camera::DoSavestate(Savestate* file) void DSi_Camera::Reset() { - Platform::Camera_Stop(Num); + Platform::Camera_Stop(Num, DSi.UserData); DataPos = 0; RegAddr = 0; @@ -435,7 +435,7 @@ void DSi_Camera::Reset() void DSi_Camera::Stop() { - Platform::Camera_Stop(Num); + Platform::Camera_Stop(Num, DSi.UserData); } bool DSi_Camera::IsActivated() const @@ -474,7 +474,7 @@ void DSi_Camera::StartTransfer() FrameFormat = 0; } - Platform::Camera_CaptureFrame(Num, FrameBuffer, 640, 480, true); + Platform::Camera_CaptureFrame(Num, FrameBuffer, 640, 480, true, DSi.UserData); } bool DSi_Camera::TransferDone() const @@ -655,8 +655,8 @@ void DSi_Camera::I2C_WriteReg(u16 addr, u16 val) StandbyCnt = val; //printf("CAM%d STBCNT=%04X (%04X)\n", Num, StandbyCnt, val); bool isactive = IsActivated(); - if (isactive && !wasactive) Platform::Camera_Start(Num); - else if (wasactive && !isactive) Platform::Camera_Stop(Num); + if (isactive && !wasactive) Platform::Camera_Start(Num, DSi.UserData); + else if (wasactive && !isactive) Platform::Camera_Stop(Num, DSi.UserData); } return; case 0x001A: @@ -665,8 +665,8 @@ void DSi_Camera::I2C_WriteReg(u16 addr, u16 val) MiscCnt = val & 0x0B7B; //printf("CAM%d MISCCNT=%04X (%04X)\n", Num, MiscCnt, val); bool isactive = IsActivated(); - if (isactive && !wasactive) Platform::Camera_Start(Num); - else if (wasactive && !isactive) Platform::Camera_Stop(Num); + if (isactive && !wasactive) Platform::Camera_Start(Num, DSi.UserData); + else if (wasactive && !isactive) Platform::Camera_Stop(Num, DSi.UserData); } return; diff --git a/src/DSi_NWifi.cpp b/src/DSi_NWifi.cpp index a6177decf5..0245053b9a 100644 --- a/src/DSi_NWifi.cpp +++ b/src/DSi_NWifi.cpp @@ -1334,7 +1334,7 @@ void DSi_NWifi::WMI_SendPacket(u16 len) } printf("\n");*/ - Platform::LAN_SendPacket(LANBuffer, lan_len); + Platform::Net_SendPacket(LANBuffer, lan_len, DSi.UserData); } void DSi_NWifi::SendWMIEvent(u8 ep, u16 id, u8* data, u32 len) @@ -1442,20 +1442,26 @@ void DSi_NWifi::CheckRX() if (!Mailbox[8].CanFit(2048)) return; - int rxlen = Platform::LAN_RecvPacket(LANBuffer); - if (rxlen > 0) + int rxlen = Platform::Net_RecvPacket(LANBuffer, DSi.UserData); + while (rxlen > 0) { //printf("WMI packet recv %04X %04X %04X\n", *(u16*)&LANBuffer[0], *(u16*)&LANBuffer[2], *(u16*)&LANBuffer[4]); // check destination MAC if (*(u32*)&LANBuffer[0] != 0xFFFFFFFF || *(u16*)&LANBuffer[4] != 0xFFFF) { if (memcmp(&LANBuffer[0], &EEPROM[0x00A], 6)) - return; + { + rxlen = Platform::Net_RecvPacket(LANBuffer, DSi.UserData); + continue; + } } // check source MAC, in case we get a packet we just sent out if (!memcmp(&LANBuffer[6], &EEPROM[0x00A], 6)) - return; + { + rxlen = Platform::Net_RecvPacket(LANBuffer, DSi.UserData); + continue; + } // packet is good diff --git a/src/FIFO.h b/src/FIFO.h index 026c2c7f18..daa7b2308a 100644 --- a/src/FIFO.h +++ b/src/FIFO.h @@ -24,6 +24,7 @@ namespace melonDS { + template class FIFO { @@ -191,5 +192,121 @@ class DynamicFIFO u32 ReadPos, WritePos; }; +template +class RingBuffer +{ +public: + void Clear() + { + NumOccupied = 0; + ReadPos = 0; + WritePos = 0; + memset(Buffer, 0, Size); + } + + + void DoSavestate(Savestate* file) + { + file->Var32(&NumOccupied); + file->Var32(&ReadPos); + file->Var32(&WritePos); + + file->VarArray(Buffer, Size); + } + + + bool Write(const void* data, u32 len) + { + if (!CanFit(len)) return false; + + if ((WritePos + len) >= Size) + { + u32 part1 = Size - WritePos; + memcpy(&Buffer[WritePos], data, part1); + if (len > part1) + memcpy(Buffer, &((u8*)data)[part1], len - part1); + WritePos = len - part1; + } + else + { + memcpy(&Buffer[WritePos], data, len); + WritePos += len; + } + + NumOccupied += len; + + return true; + } + + bool Read(void* data, u32 len) + { + if (NumOccupied < len) return false; + + u32 readpos = ReadPos; + if ((readpos + len) >= Size) + { + u32 part1 = Size - readpos; + memcpy(data, &Buffer[readpos], part1); + if (len > part1) + memcpy(&((u8*)data)[part1], Buffer, len - part1); + ReadPos = len - part1; + } + else + { + memcpy(data, &Buffer[readpos], len); + ReadPos += len; + } + + NumOccupied -= len; + return true; + } + + bool Peek(void* data, u32 offset, u32 len) + { + if (NumOccupied < len) return false; + + u32 readpos = ReadPos + offset; + if (readpos >= Size) readpos -= Size; + + if ((readpos + len) >= Size) + { + u32 part1 = Size - readpos; + memcpy(data, &Buffer[readpos], part1); + if (len > part1) + memcpy(&((u8*)data)[part1], Buffer, len - part1); + } + else + { + memcpy(data, &Buffer[readpos], len); + } + + return true; + } + + bool Skip(u32 len) + { + if (NumOccupied < len) return false; + + ReadPos += len; + if (ReadPos >= Size) + ReadPos -= Size; + + NumOccupied -= len; + return true; + } + + u32 Level() const { return NumOccupied; } + bool IsEmpty() const { return NumOccupied == 0; } + bool IsFull() const { return NumOccupied >= Size; } + + bool CanFit(u32 num) const { return ((NumOccupied + num) <= Size); } + +private: + u8 Buffer[Size] = {0}; + u32 NumOccupied = 0; + u32 ReadPos = 0, WritePos = 0; +}; + } + #endif diff --git a/src/GBACart.cpp b/src/GBACart.cpp index 1be50e752b..456b51f105 100644 --- a/src/GBACart.cpp +++ b/src/GBACart.cpp @@ -95,17 +95,18 @@ u32 CartCommon::GetSaveMemoryLength() const return 0; } -CartGame::CartGame(const u8* rom, u32 len, const u8* sram, u32 sramlen, GBACart::CartType type) : - CartGame(CopyToUnique(rom, len), len, CopyToUnique(sram, sramlen), sramlen, type) +CartGame::CartGame(const u8* rom, u32 len, const u8* sram, u32 sramlen, void* userdata, GBACart::CartType type) : + CartGame(CopyToUnique(rom, len), len, CopyToUnique(sram, sramlen), sramlen, userdata, type) { } -CartGame::CartGame(std::unique_ptr&& rom, u32 len, std::unique_ptr&& sram, u32 sramlen, GBACart::CartType type) : +CartGame::CartGame(std::unique_ptr&& rom, u32 len, std::unique_ptr&& sram, u32 sramlen, void* userdata, GBACart::CartType type) : CartCommon(type), ROM(std::move(rom)), ROMLength(len), SRAM(std::move(sram)), - SRAMLength(sramlen) + SRAMLength(sramlen), + UserData(userdata) { if (SRAM && SRAMLength) { @@ -170,7 +171,7 @@ void CartGame::DoSavestate(Savestate* file) file->Var8((u8*)&SRAMType); if ((!file->Saving) && SRAM) - Platform::WriteGBASave(SRAM.get(), SRAMLength, 0, SRAMLength); + Platform::WriteGBASave(SRAM.get(), SRAMLength, 0, SRAMLength, UserData); } void CartGame::SetupSave(u32 type) @@ -223,7 +224,7 @@ void CartGame::SetSaveMemory(const u8* savedata, u32 savelen) u32 len = std::min(savelen, SRAMLength); memcpy(SRAM.get(), savedata, len); - Platform::WriteGBASave(savedata, len, 0, len); + Platform::WriteGBASave(savedata, len, 0, len, UserData); } u16 CartGame::ROMRead(u32 addr) const @@ -464,7 +465,7 @@ void CartGame::SRAMWrite_FLASH(u32 addr, u8 val) u32 start_addr = addr + 0x10000 * SRAMFlashState.bank; memset((u8*)&SRAM[start_addr], 0xFF, 0x1000); - Platform::WriteGBASave(SRAM.get(), SRAMLength, start_addr, 0x1000); + Platform::WriteGBASave(SRAM.get(), SRAMLength, start_addr, 0x1000, UserData); } SRAMFlashState.state = 0; SRAMFlashState.cmd = 0; @@ -523,18 +524,18 @@ void CartGame::SRAMWrite_SRAM(u32 addr, u8 val) *(u8*)&SRAM[addr] = val; // TODO: optimize this!! - Platform::WriteGBASave(SRAM.get(), SRAMLength, addr, 1); + Platform::WriteGBASave(SRAM.get(), SRAMLength, addr, 1, UserData); } } -CartGameSolarSensor::CartGameSolarSensor(const u8* rom, u32 len, const u8* sram, u32 sramlen) : - CartGameSolarSensor(CopyToUnique(rom, len), len, CopyToUnique(sram, sramlen), sramlen) +CartGameSolarSensor::CartGameSolarSensor(const u8* rom, u32 len, const u8* sram, u32 sramlen, void* userdata) : + CartGameSolarSensor(CopyToUnique(rom, len), len, CopyToUnique(sram, sramlen), sramlen, userdata) { } -CartGameSolarSensor::CartGameSolarSensor(std::unique_ptr&& rom, u32 len, std::unique_ptr&& sram, u32 sramlen) : - CartGame(std::move(rom), len, std::move(sram), sramlen, CartType::GameSolarSensor) +CartGameSolarSensor::CartGameSolarSensor(std::unique_ptr&& rom, u32 len, std::unique_ptr&& sram, u32 sramlen, void* userdata) : + CartGame(std::move(rom), len, std::move(sram), sramlen, userdata, CartType::GameSolarSensor) { } @@ -680,7 +681,7 @@ void CartRAMExpansion::ROMWrite(u32 addr, u16 val) } } -GBACartSlot::GBACartSlot(std::unique_ptr&& cart) noexcept : Cart(std::move(cart)) +GBACartSlot::GBACartSlot(melonDS::NDS& nds, std::unique_ptr&& cart) noexcept : NDS(nds), Cart(std::move(cart)) { } @@ -723,24 +724,24 @@ void GBACartSlot::DoSavestate(Savestate* file) noexcept if (Cart) Cart->DoSavestate(file); } -std::unique_ptr ParseROM(std::unique_ptr&& romdata, u32 romlen) +std::unique_ptr ParseROM(std::unique_ptr&& romdata, u32 romlen, void* userdata) { - return ParseROM(std::move(romdata), romlen, nullptr, 0); + return ParseROM(std::move(romdata), romlen, nullptr, 0, userdata); } -std::unique_ptr ParseROM(const u8* romdata, u32 romlen, const u8* sramdata, u32 sramlen) +std::unique_ptr ParseROM(const u8* romdata, u32 romlen, const u8* sramdata, u32 sramlen, void* userdata) { auto [romcopy, romcopylen] = PadToPowerOf2(romdata, romlen); - return ParseROM(std::move(romcopy), romcopylen, CopyToUnique(sramdata, sramlen), sramlen); + return ParseROM(std::move(romcopy), romcopylen, CopyToUnique(sramdata, sramlen), sramlen, userdata); } -std::unique_ptr ParseROM(const u8* romdata, u32 romlen) +std::unique_ptr ParseROM(const u8* romdata, u32 romlen, void* userdata) { - return ParseROM(romdata, romlen, nullptr, 0); + return ParseROM(romdata, romlen, nullptr, 0, userdata); } -std::unique_ptr ParseROM(std::unique_ptr&& romdata, u32 romlen, std::unique_ptr&& sramdata, u32 sramlen) +std::unique_ptr ParseROM(std::unique_ptr&& romdata, u32 romlen, std::unique_ptr&& sramdata, u32 sramlen, void* userdata) { if (romdata == nullptr) { @@ -773,9 +774,9 @@ std::unique_ptr ParseROM(std::unique_ptr&& romdata, u32 romlen std::unique_ptr cart; if (solarsensor) - cart = std::make_unique(std::move(cartrom), cartromsize, std::move(sramdata), sramlen); + cart = std::make_unique(std::move(cartrom), cartromsize, std::move(sramdata), sramlen, userdata); else - cart = std::make_unique(std::move(cartrom), cartromsize, std::move(sramdata), sramlen); + cart = std::make_unique(std::move(cartrom), cartromsize, std::move(sramdata), sramlen, userdata); cart->Reset(); diff --git a/src/GBACart.h b/src/GBACart.h index 493bf6b801..fa84c3358f 100644 --- a/src/GBACart.h +++ b/src/GBACart.h @@ -72,8 +72,8 @@ class CartCommon class CartGame : public CartCommon { public: - CartGame(const u8* rom, u32 len, const u8* sram, u32 sramlen, GBACart::CartType type = GBACart::CartType::Game); - CartGame(std::unique_ptr&& rom, u32 len, std::unique_ptr&& sram, u32 sramlen, GBACart::CartType type = GBACart::CartType::Game); + CartGame(const u8* rom, u32 len, const u8* sram, u32 sramlen, void* userdata, GBACart::CartType type = GBACart::CartType::Game); + CartGame(std::unique_ptr&& rom, u32 len, std::unique_ptr&& sram, u32 sramlen, void* userdata, GBACart::CartType type = GBACart::CartType::Game); ~CartGame() override; u32 Checksum() const override; @@ -104,6 +104,8 @@ class CartGame : public CartCommon u8 SRAMRead_SRAM(u32 addr); void SRAMWrite_SRAM(u32 addr, u8 val); + void* UserData; + std::unique_ptr ROM; u32 ROMLength; @@ -147,8 +149,8 @@ class CartGame : public CartCommon class CartGameSolarSensor : public CartGame { public: - CartGameSolarSensor(const u8* rom, u32 len, const u8* sram, u32 sramlen); - CartGameSolarSensor(std::unique_ptr&& rom, u32 len, std::unique_ptr&& sram, u32 sramlen); + CartGameSolarSensor(const u8* rom, u32 len, const u8* sram, u32 sramlen, void* userdata); + CartGameSolarSensor(std::unique_ptr&& rom, u32 len, std::unique_ptr&& sram, u32 sramlen, void* userdata); void Reset() override; @@ -197,7 +199,7 @@ enum class GBACartSlot { public: - GBACartSlot(std::unique_ptr&& cart = nullptr) noexcept; + GBACartSlot(melonDS::NDS& nds, std::unique_ptr&& cart = nullptr) noexcept; ~GBACartSlot() noexcept = default; void Reset() noexcept; void DoSavestate(Savestate* file) noexcept; @@ -258,6 +260,7 @@ class GBACartSlot /// if a cart is loaded and supports SRAM, otherwise zero. [[nodiscard]] u32 GetSaveMemoryLength() const noexcept { return Cart ? Cart->GetSaveMemoryLength() : 0; } private: + melonDS::NDS& NDS; std::unique_ptr Cart = nullptr; u16 OpenBusDecay = 0; }; @@ -270,9 +273,9 @@ class GBACartSlot /// @param romlen The length of the ROM data in bytes. /// @returns A \c GBACart::CartCommon object representing the parsed ROM, /// or \c nullptr if the ROM data couldn't be parsed. -std::unique_ptr ParseROM(const u8* romdata, u32 romlen); -std::unique_ptr ParseROM(std::unique_ptr&& romdata, u32 romlen); -std::unique_ptr ParseROM(const u8* romdata, u32 romlen, const u8* sramdata, u32 sramlen); +std::unique_ptr ParseROM(const u8* romdata, u32 romlen, void* userdata = nullptr); +std::unique_ptr ParseROM(std::unique_ptr&& romdata, u32 romlen, void* userdata = nullptr); +std::unique_ptr ParseROM(const u8* romdata, u32 romlen, const u8* sramdata, u32 sramlen, void* userdata = nullptr); /// @param romdata The ROM data to parse. Will be moved-from. /// @param romlen Length of romdata in bytes. @@ -282,7 +285,7 @@ std::unique_ptr ParseROM(const u8* romdata, u32 romlen, const u8* sr /// May be zero, in which case the cart will have no save data. /// @return Unique pointer to the parsed GBA cart, /// or \c nullptr if there was an error. -std::unique_ptr ParseROM(std::unique_ptr&& romdata, u32 romlen, std::unique_ptr&& sramdata, u32 sramlen); +std::unique_ptr ParseROM(std::unique_ptr&& romdata, u32 romlen, std::unique_ptr&& sramdata, u32 sramlen, void* userdata = nullptr); } diff --git a/src/NDS.cpp b/src/NDS.cpp index 94a240290a..4ad06cf180 100644 --- a/src/NDS.cpp +++ b/src/NDS.cpp @@ -89,8 +89,9 @@ NDS::NDS() noexcept : { } -NDS::NDS(NDSArgs&& args, int type) noexcept : +NDS::NDS(NDSArgs&& args, int type, void* userdata) noexcept : ConsoleType(type), + UserData(userdata), ARM7BIOS(*args.ARM7BIOS), ARM9BIOS(*args.ARM9BIOS), ARM7BIOSNative(CRC32(ARM7BIOS.data(), ARM7BIOS.size()) == ARM7BIOSCRC32), @@ -102,7 +103,7 @@ NDS::NDS(NDSArgs&& args, int type) noexcept : RTC(*this), Wifi(*this), NDSCartSlot(*this, std::move(args.NDSROM)), - GBACartSlot(type == 1 ? nullptr : std::move(args.GBAROM)), + GBACartSlot(*this, type == 1 ? nullptr : std::move(args.GBAROM)), AREngine(*this), ARM9(*this, args.GDB, args.JIT.has_value()), ARM7(*this, args.GDB, args.JIT.has_value()), @@ -574,7 +575,7 @@ void NDS::Stop(Platform::StopReason reason) Log(level, "Stopping emulated console (Reason: %s)\n", StopReasonName(reason)); Running = false; - Platform::SignalStop(reason); + Platform::SignalStop(reason, UserData); GPU.Stop(); SPU.Stop(); } diff --git a/src/NDS.h b/src/NDS.h index f9df2d6960..c91f70c938 100644 --- a/src/NDS.h +++ b/src/NDS.h @@ -229,6 +229,8 @@ class NDS #endif public: // TODO: Encapsulate the rest of these members + void* UserData; + int ConsoleType; int CurCPU; @@ -522,7 +524,7 @@ class NDS template u32 RunFrame(); public: - NDS(NDSArgs&& args) noexcept : NDS(std::move(args), 0) {} + NDS(NDSArgs&& args, void* userdata = nullptr) noexcept : NDS(std::move(args), 0, userdata) {} NDS() noexcept; virtual ~NDS() noexcept; NDS(const NDS&) = delete; @@ -532,7 +534,7 @@ class NDS // The frontend should set and unset this manually after creating and destroying the NDS object. [[deprecated("Temporary workaround until JIT code generation is revised to accommodate multiple NDS objects.")]] static NDS* Current; protected: - explicit NDS(NDSArgs&& args, int type) noexcept; + explicit NDS(NDSArgs&& args, int type, void* userdata) noexcept; virtual void DoSavestateExtra(Savestate* file) {} }; diff --git a/src/NDSCart.cpp b/src/NDSCart.cpp index a64d8a275f..2af6142332 100644 --- a/src/NDSCart.cpp +++ b/src/NDSCart.cpp @@ -173,17 +173,18 @@ void NDSCartSlot::Key2_Encrypt(const u8* data, u32 len) noexcept } -CartCommon::CartCommon(const u8* rom, u32 len, u32 chipid, bool badDSiDump, ROMListEntry romparams, melonDS::NDSCart::CartType type) : - CartCommon(CopyToUnique(rom, len), len, chipid, badDSiDump, romparams, type) +CartCommon::CartCommon(const u8* rom, u32 len, u32 chipid, bool badDSiDump, ROMListEntry romparams, melonDS::NDSCart::CartType type, void* userdata) : + CartCommon(CopyToUnique(rom, len), len, chipid, badDSiDump, romparams, type, userdata) { } -CartCommon::CartCommon(std::unique_ptr&& rom, u32 len, u32 chipid, bool badDSiDump, ROMListEntry romparams, melonDS::NDSCart::CartType type) : +CartCommon::CartCommon(std::unique_ptr&& rom, u32 len, u32 chipid, bool badDSiDump, ROMListEntry romparams, melonDS::NDSCart::CartType type, void* userdata) : ROM(std::move(rom)), ROMLength(len), ChipID(chipid), ROMParams(romparams), - CartType(type) + CartType(type), + UserData(userdata) { memcpy(&Header, ROM.get(), sizeof(Header)); IsDSi = Header.IsDSi() && !badDSiDump; @@ -375,13 +376,13 @@ const NDSBanner* CartCommon::Banner() const return nullptr; } -CartRetail::CartRetail(const u8* rom, u32 len, u32 chipid, bool badDSiDump, ROMListEntry romparams, std::unique_ptr&& sram, u32 sramlen, melonDS::NDSCart::CartType type) : - CartRetail(CopyToUnique(rom, len), len, chipid, badDSiDump, romparams, std::move(sram), sramlen, type) +CartRetail::CartRetail(const u8* rom, u32 len, u32 chipid, bool badDSiDump, ROMListEntry romparams, std::unique_ptr&& sram, u32 sramlen, void* userdata, melonDS::NDSCart::CartType type) : + CartRetail(CopyToUnique(rom, len), len, chipid, badDSiDump, romparams, std::move(sram), sramlen, userdata, type) { } -CartRetail::CartRetail(std::unique_ptr&& rom, u32 len, u32 chipid, bool badDSiDump, ROMListEntry romparams, std::unique_ptr&& sram, u32 sramlen, melonDS::NDSCart::CartType type) : - CartCommon(std::move(rom), len, chipid, badDSiDump, romparams, type) +CartRetail::CartRetail(std::unique_ptr&& rom, u32 len, u32 chipid, bool badDSiDump, ROMListEntry romparams, std::unique_ptr&& sram, u32 sramlen, void* userdata, melonDS::NDSCart::CartType type) : + CartCommon(std::move(rom), len, chipid, badDSiDump, romparams, type, userdata) { u32 savememtype = ROMParams.SaveMemType <= 10 ? ROMParams.SaveMemType : 0; constexpr int sramlengths[] = @@ -469,7 +470,7 @@ void CartRetail::DoSavestate(Savestate* file) file->Var8(&SRAMStatus); if ((!file->Saving) && SRAM) - Platform::WriteNDSSave(SRAM.get(), SRAMLength, 0, SRAMLength); + Platform::WriteNDSSave(SRAM.get(), SRAMLength, 0, SRAMLength, UserData); } void CartRetail::SetSaveMemory(const u8* savedata, u32 savelen) @@ -478,7 +479,7 @@ void CartRetail::SetSaveMemory(const u8* savedata, u32 savelen) u32 len = std::min(savelen, SRAMLength); memcpy(SRAM.get(), savedata, len); - Platform::WriteNDSSave(savedata, len, 0, len); + Platform::WriteNDSSave(savedata, len, 0, len, UserData); } int CartRetail::ROMCommandStart(NDS& nds, NDSCart::NDSCartSlot& cartslot, const u8* cmd, u8* data, u32 len) @@ -594,7 +595,8 @@ u8 CartRetail::SRAMWrite_EEPROMTiny(u8 val, u32 pos, bool last) { SRAMStatus &= ~(1<<1); Platform::WriteNDSSave(SRAM.get(), SRAMLength, - (SRAMFirstAddr + ((SRAMCmd==0x0A)?0x100:0)) & 0x1FF, SRAMAddr-SRAMFirstAddr); + (SRAMFirstAddr + ((SRAMCmd==0x0A)?0x100:0)) & 0x1FF, SRAMAddr-SRAMFirstAddr, + UserData); } return 0; @@ -658,7 +660,8 @@ u8 CartRetail::SRAMWrite_EEPROM(u8 val, u32 pos, bool last) { SRAMStatus &= ~(1<<1); Platform::WriteNDSSave(SRAM.get(), SRAMLength, - SRAMFirstAddr & (SRAMLength-1), SRAMAddr-SRAMFirstAddr); + SRAMFirstAddr & (SRAMLength-1), SRAMAddr-SRAMFirstAddr, + UserData); } return 0; @@ -715,7 +718,8 @@ u8 CartRetail::SRAMWrite_FLASH(u8 val, u32 pos, bool last) { SRAMStatus &= ~(1<<1); Platform::WriteNDSSave(SRAM.get(), SRAMLength, - SRAMFirstAddr & (SRAMLength-1), SRAMAddr-SRAMFirstAddr); + SRAMFirstAddr & (SRAMLength-1), SRAMAddr-SRAMFirstAddr, + UserData); } return 0; @@ -752,7 +756,8 @@ u8 CartRetail::SRAMWrite_FLASH(u8 val, u32 pos, bool last) { SRAMStatus &= ~(1<<1); Platform::WriteNDSSave(SRAM.get(), SRAMLength, - SRAMFirstAddr & (SRAMLength-1), SRAMAddr-SRAMFirstAddr); + SRAMFirstAddr & (SRAMLength-1), SRAMAddr-SRAMFirstAddr, + UserData); } return 0; @@ -798,7 +803,8 @@ u8 CartRetail::SRAMWrite_FLASH(u8 val, u32 pos, bool last) { SRAMStatus &= ~(1<<1); Platform::WriteNDSSave(SRAM.get(), SRAMLength, - SRAMFirstAddr & (SRAMLength-1), SRAMAddr-SRAMFirstAddr); + SRAMFirstAddr & (SRAMLength-1), SRAMAddr-SRAMFirstAddr, + UserData); } return 0; @@ -821,7 +827,8 @@ u8 CartRetail::SRAMWrite_FLASH(u8 val, u32 pos, bool last) { SRAMStatus &= ~(1<<1); Platform::WriteNDSSave(SRAM.get(), SRAMLength, - SRAMFirstAddr & (SRAMLength-1), SRAMAddr-SRAMFirstAddr); + SRAMFirstAddr & (SRAMLength-1), SRAMAddr-SRAMFirstAddr, + UserData); } return 0; @@ -832,13 +839,13 @@ u8 CartRetail::SRAMWrite_FLASH(u8 val, u32 pos, bool last) } } -CartRetailNAND::CartRetailNAND(const u8* rom, u32 len, u32 chipid, ROMListEntry romparams, std::unique_ptr&& sram, u32 sramlen) : - CartRetailNAND(CopyToUnique(rom, len), len, chipid, romparams, std::move(sram), sramlen) +CartRetailNAND::CartRetailNAND(const u8* rom, u32 len, u32 chipid, ROMListEntry romparams, std::unique_ptr&& sram, u32 sramlen, void* userdata) : + CartRetailNAND(CopyToUnique(rom, len), len, chipid, romparams, std::move(sram), sramlen, userdata) { } -CartRetailNAND::CartRetailNAND(std::unique_ptr&& rom, u32 len, u32 chipid, ROMListEntry romparams, std::unique_ptr&& sram, u32 sramlen) : - CartRetail(std::move(rom), len, chipid, false, romparams, std::move(sram), sramlen, CartType::RetailNAND) +CartRetailNAND::CartRetailNAND(std::unique_ptr&& rom, u32 len, u32 chipid, ROMListEntry romparams, std::unique_ptr&& sram, u32 sramlen, void* userdata) : + CartRetail(std::move(rom), len, chipid, false, romparams, std::move(sram), sramlen, userdata, CartType::RetailNAND) { BuildSRAMID(); } @@ -908,7 +915,7 @@ int CartRetailNAND::ROMCommandStart(NDS& nds, NDSCart::NDSCartSlot& cartslot, co if (SRAMLength && SRAMAddr < (SRAMBase+SRAMLength-0x20000)) { memcpy(&SRAM[SRAMAddr - SRAMBase], SRAMWriteBuffer, 0x800); - Platform::WriteNDSSave(SRAM.get(), SRAMLength, SRAMAddr - SRAMBase, 0x800); + Platform::WriteNDSSave(SRAM.get(), SRAMLength, SRAMAddr - SRAMBase, 0x800, UserData); } SRAMAddr = 0; @@ -1064,8 +1071,8 @@ void CartRetailNAND::BuildSRAMID() } -CartRetailIR::CartRetailIR(const u8* rom, u32 len, u32 chipid, u32 irversion, bool badDSiDump, ROMListEntry romparams, std::unique_ptr&& sram, u32 sramlen) : - CartRetailIR(CopyToUnique(rom, len), len, chipid, irversion, badDSiDump, romparams, std::move(sram), sramlen) +CartRetailIR::CartRetailIR(const u8* rom, u32 len, u32 chipid, u32 irversion, bool badDSiDump, ROMListEntry romparams, std::unique_ptr&& sram, u32 sramlen, void* userdata) : + CartRetailIR(CopyToUnique(rom, len), len, chipid, irversion, badDSiDump, romparams, std::move(sram), sramlen, userdata) { } @@ -1077,9 +1084,10 @@ CartRetailIR::CartRetailIR( bool badDSiDump, ROMListEntry romparams, std::unique_ptr&& sram, - u32 sramlen + u32 sramlen, + void* userdata ) : - CartRetail(std::move(rom), len, chipid, badDSiDump, romparams, std::move(sram), sramlen, CartType::RetailIR), + CartRetail(std::move(rom), len, chipid, badDSiDump, romparams, std::move(sram), sramlen, userdata, CartType::RetailIR), IRVersion(irversion) { } @@ -1122,13 +1130,13 @@ u8 CartRetailIR::SPIWrite(u8 val, u32 pos, bool last) return 0; } -CartRetailBT::CartRetailBT(const u8* rom, u32 len, u32 chipid, ROMListEntry romparams, std::unique_ptr&& sram, u32 sramlen) : - CartRetailBT(CopyToUnique(rom, len), len, chipid, romparams, std::move(sram), sramlen) +CartRetailBT::CartRetailBT(const u8* rom, u32 len, u32 chipid, ROMListEntry romparams, std::unique_ptr&& sram, u32 sramlen, void* userdata) : + CartRetailBT(CopyToUnique(rom, len), len, chipid, romparams, std::move(sram), sramlen, userdata) { } -CartRetailBT::CartRetailBT(std::unique_ptr&& rom, u32 len, u32 chipid, ROMListEntry romparams, std::unique_ptr&& sram, u32 sramlen) : - CartRetail(std::move(rom), len, chipid, false, romparams, std::move(sram), sramlen, CartType::RetailBT) +CartRetailBT::CartRetailBT(std::unique_ptr&& rom, u32 len, u32 chipid, ROMListEntry romparams, std::unique_ptr&& sram, u32 sramlen, void* userdata) : + CartRetail(std::move(rom), len, chipid, false, romparams, std::move(sram), sramlen, userdata, CartType::RetailBT) { Log(LogLevel::Info,"POKETYPE CART\n"); } @@ -1150,12 +1158,12 @@ u8 CartRetailBT::SPIWrite(u8 val, u32 pos, bool last) } -CartSD::CartSD(const u8* rom, u32 len, u32 chipid, ROMListEntry romparams, std::optional&& sdcard) : - CartSD(CopyToUnique(rom, len), len, chipid, romparams, std::move(sdcard)) +CartSD::CartSD(const u8* rom, u32 len, u32 chipid, ROMListEntry romparams, void* userdata, std::optional&& sdcard) : + CartSD(CopyToUnique(rom, len), len, chipid, romparams, userdata, std::move(sdcard)) {} -CartSD::CartSD(std::unique_ptr&& rom, u32 len, u32 chipid, ROMListEntry romparams, std::optional&& sdcard) : - CartCommon(std::move(rom), len, chipid, false, romparams, CartType::Homebrew), +CartSD::CartSD(std::unique_ptr&& rom, u32 len, u32 chipid, ROMListEntry romparams, void* userdata, std::optional&& sdcard) : + CartCommon(std::move(rom), len, chipid, false, romparams, CartType::Homebrew, userdata), SD(std::move(sdcard)) { sdcard = std::nullopt; @@ -1306,12 +1314,12 @@ void CartSD::ReadROM_B7(u32 addr, u32 len, u8* data, u32 offset) const memcpy(data+offset, ROM.get()+addr, len); } -CartHomebrew::CartHomebrew(const u8* rom, u32 len, u32 chipid, ROMListEntry romparams, std::optional&& sdcard) : - CartSD(rom, len, chipid, romparams, std::move(sdcard)) +CartHomebrew::CartHomebrew(const u8* rom, u32 len, u32 chipid, ROMListEntry romparams, void* userdata, std::optional&& sdcard) : + CartSD(rom, len, chipid, romparams, userdata, std::move(sdcard)) {} -CartHomebrew::CartHomebrew(std::unique_ptr&& rom, u32 len, u32 chipid, ROMListEntry romparams, std::optional&& sdcard) : - CartSD(std::move(rom), len, chipid, romparams, std::move(sdcard)) +CartHomebrew::CartHomebrew(std::unique_ptr&& rom, u32 len, u32 chipid, ROMListEntry romparams, void* userdata, std::optional&& sdcard) : + CartSD(std::move(rom), len, chipid, romparams, userdata, std::move(sdcard)) {} CartHomebrew::~CartHomebrew() = default; @@ -1565,12 +1573,12 @@ void NDSCartSlot::DecryptSecureArea(u8* out) noexcept } } -std::unique_ptr ParseROM(const u8* romdata, u32 romlen, std::optional&& args) +std::unique_ptr ParseROM(const u8* romdata, u32 romlen, void* userdata, std::optional&& args) { - return ParseROM(CopyToUnique(romdata, romlen), romlen, std::move(args)); + return ParseROM(CopyToUnique(romdata, romlen), romlen, userdata, std::move(args)); } -std::unique_ptr ParseROM(std::unique_ptr&& romdata, u32 romlen, std::optional&& args) +std::unique_ptr ParseROM(std::unique_ptr&& romdata, u32 romlen, void* userdata, std::optional&& args) { if (romdata == nullptr) { @@ -1659,21 +1667,21 @@ std::unique_ptr ParseROM(std::unique_ptr&& romdata, u32 romlen if (homebrew) { std::optional sdcard = args && args->SDCard ? std::make_optional(std::move(*args->SDCard)) : std::nullopt; - cart = std::make_unique(std::move(cartrom), cartromsize, cartid, romparams, std::move(sdcard)); + cart = std::make_unique(std::move(cartrom), cartromsize, cartid, romparams, userdata, std::move(sdcard)); } else if (gametitle[0] == 0 && !strncmp("SD/TF-NDS", gametitle + 1, 9) && gamecode == 0x414D5341) { std::optional sdcard = args && args->SDCard ? std::make_optional(std::move(*args->SDCard)) : std::nullopt; - cart = std::make_unique(std::move(cartrom), cartromsize, cartid, romparams, CartR4TypeR4, CartR4LanguageEnglish, std::move(sdcard)); + cart = std::make_unique(std::move(cartrom), cartromsize, cartid, romparams, CartR4TypeR4, CartR4LanguageEnglish, userdata, std::move(sdcard)); } else if (cartid & 0x08000000) - cart = std::make_unique(std::move(cartrom), cartromsize, cartid, romparams, std::move(sram), sramlen); + cart = std::make_unique(std::move(cartrom), cartromsize, cartid, romparams, std::move(sram), sramlen, userdata); else if (irversion != 0) - cart = std::make_unique(std::move(cartrom), cartromsize, cartid, irversion, badDSiDump, romparams, std::move(sram), sramlen); + cart = std::make_unique(std::move(cartrom), cartromsize, cartid, irversion, badDSiDump, romparams, std::move(sram), sramlen, userdata); else if ((gamecode & 0xFFFFFF) == 0x505A55) // UZPx - cart = std::make_unique(std::move(cartrom), cartromsize, cartid, romparams, std::move(sram), sramlen); + cart = std::make_unique(std::move(cartrom), cartromsize, cartid, romparams, std::move(sram), sramlen, userdata); else - cart = std::make_unique(std::move(cartrom), cartromsize, cartid, badDSiDump, romparams, std::move(sram), sramlen); + cart = std::make_unique(std::move(cartrom), cartromsize, cartid, badDSiDump, romparams, std::move(sram), sramlen, userdata); args = std::nullopt; return cart; diff --git a/src/NDSCart.h b/src/NDSCart.h index 2f6a3be5de..44dbc9067f 100644 --- a/src/NDSCart.h +++ b/src/NDSCart.h @@ -76,8 +76,8 @@ struct NDSCartArgs class CartCommon { public: - CartCommon(const u8* rom, u32 len, u32 chipid, bool badDSiDump, ROMListEntry romparams, CartType type); - CartCommon(std::unique_ptr&& rom, u32 len, u32 chipid, bool badDSiDump, ROMListEntry romparams, CartType type); + CartCommon(const u8* rom, u32 len, u32 chipid, bool badDSiDump, ROMListEntry romparams, CartType type, void* userdata); + CartCommon(std::unique_ptr&& rom, u32 len, u32 chipid, bool badDSiDump, ROMListEntry romparams, CartType type, void* userdata); virtual ~CartCommon(); [[nodiscard]] u32 Type() const { return CartType; }; @@ -111,6 +111,8 @@ class CartCommon protected: void ReadROM(u32 addr, u32 len, u8* data, u32 offset) const; + void* UserData; + std::unique_ptr ROM = nullptr; u32 ROMLength = 0; u32 ChipID = 0; @@ -139,6 +141,7 @@ class CartRetail : public CartCommon ROMListEntry romparams, std::unique_ptr&& sram, u32 sramlen, + void* userdata, melonDS::NDSCart::CartType type = CartType::Retail ); CartRetail( @@ -148,6 +151,7 @@ class CartRetail : public CartCommon ROMListEntry romparams, std::unique_ptr&& sram, u32 sramlen, + void* userdata, melonDS::NDSCart::CartType type = CartType::Retail ); ~CartRetail() override; @@ -187,8 +191,8 @@ class CartRetail : public CartCommon class CartRetailNAND : public CartRetail { public: - CartRetailNAND(const u8* rom, u32 len, u32 chipid, ROMListEntry romparams, std::unique_ptr&& sram, u32 sramlen); - CartRetailNAND(std::unique_ptr&& rom, u32 len, u32 chipid, ROMListEntry romparams, std::unique_ptr&& sram, u32 sramlen); + CartRetailNAND(const u8* rom, u32 len, u32 chipid, ROMListEntry romparams, std::unique_ptr&& sram, u32 sramlen, void* userdata); + CartRetailNAND(std::unique_ptr&& rom, u32 len, u32 chipid, ROMListEntry romparams, std::unique_ptr&& sram, u32 sramlen, void* userdata); ~CartRetailNAND() override; void Reset() override; @@ -216,8 +220,8 @@ class CartRetailNAND : public CartRetail class CartRetailIR : public CartRetail { public: - CartRetailIR(const u8* rom, u32 len, u32 chipid, u32 irversion, bool badDSiDump, ROMListEntry romparams, std::unique_ptr&& sram, u32 sramlen); - CartRetailIR(std::unique_ptr&& rom, u32 len, u32 chipid, u32 irversion, bool badDSiDump, ROMListEntry romparams, std::unique_ptr&& sram, u32 sramlen); + CartRetailIR(const u8* rom, u32 len, u32 chipid, u32 irversion, bool badDSiDump, ROMListEntry romparams, std::unique_ptr&& sram, u32 sramlen, void* userdata); + CartRetailIR(std::unique_ptr&& rom, u32 len, u32 chipid, u32 irversion, bool badDSiDump, ROMListEntry romparams, std::unique_ptr&& sram, u32 sramlen, void* userdata); ~CartRetailIR() override; void Reset() override; @@ -235,8 +239,8 @@ class CartRetailIR : public CartRetail class CartRetailBT : public CartRetail { public: - CartRetailBT(const u8* rom, u32 len, u32 chipid, ROMListEntry romparams, std::unique_ptr&& sram, u32 sramlen); - CartRetailBT(std::unique_ptr&& rom, u32 len, u32 chipid, ROMListEntry romparams, std::unique_ptr&& sram, u32 sramlen); + CartRetailBT(const u8* rom, u32 len, u32 chipid, ROMListEntry romparams, std::unique_ptr&& sram, u32 sramlen, void* userdata); + CartRetailBT(std::unique_ptr&& rom, u32 len, u32 chipid, ROMListEntry romparams, std::unique_ptr&& sram, u32 sramlen, void* userdata); ~CartRetailBT() override; u8 SPIWrite(u8 val, u32 pos, bool last) override; @@ -246,8 +250,8 @@ class CartRetailBT : public CartRetail class CartSD : public CartCommon { public: - CartSD(const u8* rom, u32 len, u32 chipid, ROMListEntry romparams, std::optional&& sdcard = std::nullopt); - CartSD(std::unique_ptr&& rom, u32 len, u32 chipid, ROMListEntry romparams, std::optional&& sdcard = std::nullopt); + CartSD(const u8* rom, u32 len, u32 chipid, ROMListEntry romparams, void* userdata, std::optional&& sdcard = std::nullopt); + CartSD(std::unique_ptr&& rom, u32 len, u32 chipid, ROMListEntry romparams, void* userdata, std::optional&& sdcard = std::nullopt); ~CartSD() override; [[nodiscard]] const std::optional& GetSDCard() const noexcept { return SD; } @@ -288,8 +292,8 @@ class CartSD : public CartCommon class CartHomebrew : public CartSD { public: - CartHomebrew(const u8* rom, u32 len, u32 chipid, ROMListEntry romparams, std::optional&& sdcard = std::nullopt); - CartHomebrew(std::unique_ptr&& rom, u32 len, u32 chipid, ROMListEntry romparams, std::optional&& sdcard = std::nullopt); + CartHomebrew(const u8* rom, u32 len, u32 chipid, ROMListEntry romparams, void* userdata, std::optional&& sdcard = std::nullopt); + CartHomebrew(std::unique_ptr&& rom, u32 len, u32 chipid, ROMListEntry romparams, void* userdata, std::optional&& sdcard = std::nullopt); ~CartHomebrew() override; void Reset() override; @@ -322,7 +326,7 @@ enum CartR4Language class CartR4 : public CartSD { public: - CartR4(std::unique_ptr&& rom, u32 len, u32 chipid, ROMListEntry romparams, CartR4Type ctype, CartR4Language clanguage, + CartR4(std::unique_ptr&& rom, u32 len, u32 chipid, ROMListEntry romparams, CartR4Type ctype, CartR4Language clanguage, void* userdata, std::optional&& sdcard = std::nullopt); ~CartR4() override; @@ -461,8 +465,8 @@ class NDSCartSlot /// If not given, the cart will not have an SD card. /// @returns A \c NDSCart::CartCommon object representing the parsed ROM, /// or \c nullptr if the ROM data couldn't be parsed. -std::unique_ptr ParseROM(const u8* romdata, u32 romlen, std::optional&& args = std::nullopt); -std::unique_ptr ParseROM(std::unique_ptr&& romdata, u32 romlen, std::optional&& args = std::nullopt); +std::unique_ptr ParseROM(const u8* romdata, u32 romlen, void* userdata = nullptr, std::optional&& args = std::nullopt); +std::unique_ptr ParseROM(std::unique_ptr&& romdata, u32 romlen, void* userdata = nullptr, std::optional&& args = std::nullopt); } #endif diff --git a/src/NDSCartR4.cpp b/src/NDSCartR4.cpp index 8497f55664..0441a39cd7 100644 --- a/src/NDSCartR4.cpp +++ b/src/NDSCartR4.cpp @@ -67,9 +67,9 @@ static void DecryptR4Sector(u8* dest, u8* src, u16 key1) } } -CartR4::CartR4(std::unique_ptr&& rom, u32 len, u32 chipid, ROMListEntry romparams, CartR4Type ctype, CartR4Language clanguage, +CartR4::CartR4(std::unique_ptr&& rom, u32 len, u32 chipid, ROMListEntry romparams, CartR4Type ctype, CartR4Language clanguage, void* userdata, std::optional&& sdcard) - : CartSD(std::move(rom), len, chipid, romparams, std::move(sdcard)) + : CartSD(std::move(rom), len, chipid, romparams, userdata, std::move(sdcard)) { InitStatus = 0; R4CartType = ctype; diff --git a/src/Platform.h b/src/Platform.h index 425c712c86..563cceb774 100644 --- a/src/Platform.h +++ b/src/Platform.h @@ -31,14 +31,6 @@ class Firmware; namespace Platform { -void Init(int argc, char** argv); - -/** - * Frees all resources that were allocated in \c Init - * or by any other \c Platform function. - */ -void DeInit(); - enum StopReason { /** * The emulator stopped for some unspecified reason. @@ -77,20 +69,8 @@ enum StopReason { * Frontends should not call this directly; * use \c NDS::Stop instead. */ -void SignalStop(StopReason reason); - -/** - * @returns The ID of the running melonDS instance if running in local multiplayer mode, - * or 0 if not. - */ -int InstanceID(); +void SignalStop(StopReason reason, void* userdata); -/** - * @returns A suffix that should be appended to all instance-specific paths - * if running in local multiplayer mode, - * or the empty string if not. - */ -std::string InstanceFileSuffix(); /** * Denotes how a file will be opened and accessed. @@ -188,6 +168,9 @@ enum class FileSeekOrigin */ struct FileHandle; +// retrieves the path to a local file, without opening the file +std::string GetLocalFilePath(const std::string& filename); + // Simple fopen() wrapper that supports UTF8. // Can be optionally restricted to only opening a file that already exists. FileHandle* OpenFile(const std::string& path, FileMode mode); @@ -288,41 +271,37 @@ void Sleep(u64 usecs); // functions called when the NDS or GBA save files need to be written back to storage // savedata and savelen are always the entire save memory buffer and its full length // writeoffset and writelen indicate which part of the memory was altered -void WriteNDSSave(const u8* savedata, u32 savelen, u32 writeoffset, u32 writelen); -void WriteGBASave(const u8* savedata, u32 savelen, u32 writeoffset, u32 writelen); +void WriteNDSSave(const u8* savedata, u32 savelen, u32 writeoffset, u32 writelen, void* userdata); +void WriteGBASave(const u8* savedata, u32 savelen, u32 writeoffset, u32 writelen, void* userdata); /// Called when the firmware needs to be written back to storage, /// after one of the supported write commands finishes execution. /// @param firmware The firmware that was just written. /// @param writeoffset The offset of the first byte that was written to firmware. /// @param writelen The number of bytes that were written to firmware. -void WriteFirmware(const Firmware& firmware, u32 writeoffset, u32 writelen); +void WriteFirmware(const Firmware& firmware, u32 writeoffset, u32 writelen, void* userdata); // called when the RTC date/time is changed and the frontend might need to take it into account -void WriteDateTime(int year, int month, int day, int hour, int minute, int second); +void WriteDateTime(int year, int month, int day, int hour, int minute, int second, void* userdata); // local multiplayer comm interface // packet type: DS-style TX header (12 bytes) + original 802.11 frame -bool MP_Init(); -void MP_DeInit(); -void MP_Begin(); -void MP_End(); -int MP_SendPacket(u8* data, int len, u64 timestamp); -int MP_RecvPacket(u8* data, u64* timestamp); -int MP_SendCmd(u8* data, int len, u64 timestamp); -int MP_SendReply(u8* data, int len, u64 timestamp, u16 aid); -int MP_SendAck(u8* data, int len, u64 timestamp); -int MP_RecvHostPacket(u8* data, u64* timestamp); -u16 MP_RecvReplies(u8* data, u64 timestamp, u16 aidmask); - - -// LAN comm interface +void MP_Begin(void* userdata); +void MP_End(void* userdata); +int MP_SendPacket(u8* data, int len, u64 timestamp, void* userdata); +int MP_RecvPacket(u8* data, u64* timestamp, void* userdata); +int MP_SendCmd(u8* data, int len, u64 timestamp, void* userdata); +int MP_SendReply(u8* data, int len, u64 timestamp, u16 aid, void* userdata); +int MP_SendAck(u8* data, int len, u64 timestamp, void* userdata); +int MP_RecvHostPacket(u8* data, u64* timestamp, void* userdata); +u16 MP_RecvReplies(u8* data, u64 timestamp, u16 aidmask, void* userdata); + + +// network comm interface // packet type: Ethernet (802.3) -bool LAN_Init(); -void LAN_DeInit(); -int LAN_SendPacket(u8* data, int len); -int LAN_RecvPacket(u8* data); +int Net_SendPacket(u8* data, int len, void* userdata); +int Net_RecvPacket(u8* data, void* userdata); // interface for camera emulation @@ -330,9 +309,9 @@ int LAN_RecvPacket(u8* data); // 0 = DSi outer camera // 1 = DSi inner camera // other values reserved for future camera addon emulation -void Camera_Start(int num); -void Camera_Stop(int num); -void Camera_CaptureFrame(int num, u32* frame, int width, int height, bool yuv); +void Camera_Start(int num, void* userdata); +void Camera_Stop(int num, void* userdata); +void Camera_CaptureFrame(int num, u32* frame, int width, int height, bool yuv, void* userdata); struct DynamicLibrary; diff --git a/src/RTC.cpp b/src/RTC.cpp index d8219df105..9d6aea86fc 100644 --- a/src/RTC.cpp +++ b/src/RTC.cpp @@ -602,7 +602,7 @@ void RTC::SaveDateTime() { int y, m, d, h, i, s; GetDateTime(y, m, d, h, i, s); - Platform::WriteDateTime(y, m, d, h, i, s); + Platform::WriteDateTime(y, m, d, h, i, s, NDS.UserData); } void RTC::CmdRead() diff --git a/src/SPI.cpp b/src/SPI.cpp index 2aa915c676..fda0d45b55 100644 --- a/src/SPI.cpp +++ b/src/SPI.cpp @@ -260,7 +260,7 @@ void FirmwareMem::Release() // Request that the start of the Wi-fi/userdata settings region // through the end of the firmware blob be flushed to disk - Platform::WriteFirmware(FirmwareData, wifioffset, FirmwareData.Length() - wifioffset); + Platform::WriteFirmware(FirmwareData, wifioffset, FirmwareData.Length() - wifioffset, NDS.UserData); } SPIDevice::Release(); diff --git a/src/Wifi.cpp b/src/Wifi.cpp index d8f440b470..ea33cebf21 100644 --- a/src/Wifi.cpp +++ b/src/Wifi.cpp @@ -93,25 +93,11 @@ Wifi::Wifi(melonDS::NDS& nds) : NDS(nds) { NDS.RegisterEventFunc(Event_Wifi, 0, MemberEventFunc(Wifi, USTimer)); - //MPInited = false; - //LANInited = false; - - Platform::MP_Init(); - MPInited = true; - - Platform::LAN_Init(); - LANInited = true; - - WifiAP = new class WifiAP(this); + WifiAP = new class WifiAP(this, NDS.UserData); } Wifi::~Wifi() { - if (MPInited) - Platform::MP_DeInit(); - if (LANInited) - Platform::LAN_DeInit(); - delete WifiAP; WifiAP = nullptr; NDS.UnregisterEventFunc(Event_Wifi, 0); @@ -368,7 +354,7 @@ void Wifi::UpdatePowerOn() ScheduleTimer(true); - Platform::MP_Begin(); + Platform::MP_Begin(NDS.UserData); } else { @@ -376,7 +362,7 @@ void Wifi::UpdatePowerOn() NDS.CancelEvent(Event_Wifi); - Platform::MP_End(); + Platform::MP_End(NDS.UserData); } } @@ -664,23 +650,23 @@ void Wifi::TXSendFrame(const TXSlot* slot, int num) case 0: case 2: case 3: - Platform::MP_SendPacket(TXBuffer, 12+len, USTimestamp); + Platform::MP_SendPacket(TXBuffer, 12+len, USTimestamp, NDS.UserData); if (!IsMP) WifiAP->SendPacket(TXBuffer, 12+len); break; case 1: *(u16*)&TXBuffer[12 + 24+2] = MPClientMask; - Platform::MP_SendCmd(TXBuffer, 12+len, USTimestamp); + Platform::MP_SendCmd(TXBuffer, 12+len, USTimestamp, NDS.UserData); break; case 5: IncrementTXCount(slot); - Platform::MP_SendReply(TXBuffer, 12+len, USTimestamp, IOPORT(W_AIDLow)); + Platform::MP_SendReply(TXBuffer, 12+len, USTimestamp, IOPORT(W_AIDLow), NDS.UserData); break; case 4: *(u64*)&TXBuffer[0xC + 24] = USCounter; - Platform::MP_SendPacket(TXBuffer, 12+len, USTimestamp); + Platform::MP_SendPacket(TXBuffer, 12+len, USTimestamp, NDS.UserData); break; } } @@ -836,7 +822,7 @@ void Wifi::SendMPDefaultReply() *(u16*)&reply[0xC + 0x16] = IOPORT(W_TXSeqNo) << 4; *(u32*)&reply[0xC + 0x18] = 0; - int txlen = Platform::MP_SendReply(reply, 12+28, USTimestamp, IOPORT(W_AIDLow)); + int txlen = Platform::MP_SendReply(reply, 12+28, USTimestamp, IOPORT(W_AIDLow), NDS.UserData); WIFI_LOG("wifi: sent %d/40 bytes of MP default reply\n", txlen); } @@ -946,7 +932,7 @@ void Wifi::SendMPAck(u16 cmdcount, u16 clientfail) *(u32*)&ack[0] = PreambleLen(TXSlots[1].Rate); } - int txlen = Platform::MP_SendAck(ack, 12+32, USTimestamp); + int txlen = Platform::MP_SendAck(ack, 12+32, USTimestamp, NDS.UserData); WIFI_LOG("wifi: sent %d/44 bytes of MP ack, %d %d\n", txlen, ComStatus, RXTime); } @@ -1069,7 +1055,7 @@ bool Wifi::ProcessTX(TXSlot* slot, int num) u16 res = 0; if (MPClientMask) - res = Platform::MP_RecvReplies(MPClientReplies, USTimestamp, MPClientMask); + res = Platform::MP_RecvReplies(MPClientReplies, USTimestamp, MPClientMask, NDS.UserData); MPClientFail &= ~res; // TODO: 112 likely includes the ack preamble, which needs adjusted @@ -1508,7 +1494,7 @@ void Wifi::FinishRX() // in the case this client wasn't ready to send a reply // TODO: also send this if we have RX disabled - Platform::MP_SendReply(nullptr, 0, USTimestamp, 0); + Platform::MP_SendReply(nullptr, 0, USTimestamp, 0, NDS.UserData); } } else if ((rxflags & 0x800F) == 0x8001) @@ -1592,13 +1578,13 @@ bool Wifi::CheckRX(int type) // 0=regular 1=MP replies 2=MP host frames if (type == 0) { - rxlen = Platform::MP_RecvPacket(RXBuffer, ×tamp); + rxlen = Platform::MP_RecvPacket(RXBuffer, ×tamp, NDS.UserData); if ((rxlen <= 0) && (!IsMP)) rxlen = WifiAP->RecvPacket(RXBuffer); } else { - rxlen = Platform::MP_RecvHostPacket(RXBuffer, ×tamp); + rxlen = Platform::MP_RecvHostPacket(RXBuffer, ×tamp, NDS.UserData); if (rxlen < 0) { // host is gone diff --git a/src/Wifi.h b/src/Wifi.h index 2e0465a621..724580c13e 100644 --- a/src/Wifi.h +++ b/src/Wifi.h @@ -241,9 +241,6 @@ class Wifi u16 MPLastSeqno; - bool MPInited; - bool LANInited; - int USUntilPowerOn; // MULTIPLAYER SYNC APPARATUS diff --git a/src/WifiAP.cpp b/src/WifiAP.cpp index 855dc244e0..52359a3d0a 100644 --- a/src/WifiAP.cpp +++ b/src/WifiAP.cpp @@ -71,7 +71,7 @@ bool MACEqual(const u8* a, const u8* b); bool MACIsBroadcast(const u8* a); -WifiAP::WifiAP(Wifi* client) : Client(client) +WifiAP::WifiAP(Wifi* client, void* userdata) : Client(client), UserData(userdata) { } @@ -301,7 +301,7 @@ int WifiAP::SendPacket(const u8* data, int len) *(u16*)&LANBuffer[12] = *(u16*)&data[30]; // type memcpy(&LANBuffer[14], &data[32], lan_len - 14); - Platform::LAN_SendPacket(LANBuffer, lan_len); + Platform::Net_SendPacket(LANBuffer, lan_len, UserData); } } return len; @@ -368,14 +368,23 @@ int WifiAP::RecvPacket(u8* data) if (ClientStatus < 2) return 0; - int rxlen = Platform::LAN_RecvPacket(LANBuffer); - if (rxlen > 0) + int rxlen = Platform::Net_RecvPacket(LANBuffer, UserData); + while (rxlen > 0) { // check destination MAC if (!MACIsBroadcast(&LANBuffer[0])) { if (!MACEqual(&LANBuffer[0], Client->GetMAC())) - return 0; + { + rxlen = Platform::Net_RecvPacket(LANBuffer, UserData); + continue; + } + } + + if (MACEqual(&LANBuffer[6], Client->GetMAC())) + { + rxlen = Platform::Net_RecvPacket(LANBuffer, UserData); + continue; } // packet is good diff --git a/src/WifiAP.h b/src/WifiAP.h index a9e80c3bbf..5acd0c850d 100644 --- a/src/WifiAP.h +++ b/src/WifiAP.h @@ -28,7 +28,7 @@ class Wifi; class WifiAP { public: - WifiAP(Wifi* client); + WifiAP(Wifi* client, void* userdata); ~WifiAP(); void Reset(); @@ -44,6 +44,7 @@ class WifiAP private: Wifi* Client; + void* UserData; u64 USCounter; diff --git a/src/frontend/FrontendUtil.h b/src/frontend/FrontendUtil.h deleted file mode 100644 index 6f09d4b7f3..0000000000 --- a/src/frontend/FrontendUtil.h +++ /dev/null @@ -1,124 +0,0 @@ -/* - Copyright 2016-2023 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 FRONTENDUTIL_H -#define FRONTENDUTIL_H - -#include "types.h" - -#include -#include - -namespace melonDS -{ -class NDS; -} -namespace Frontend -{ -using namespace melonDS; - -enum ScreenLayout -{ - screenLayout_Natural, // top screen above bottom screen always - screenLayout_Horizontal, - screenLayout_Vertical, - screenLayout_Hybrid, - screenLayout_MAX, -}; - -enum ScreenRotation -{ - screenRot_0Deg, - screenRot_90Deg, - screenRot_180Deg, - screenRot_270Deg, - screenRot_MAX, -}; - -enum ScreenSizing -{ - screenSizing_Even, // both screens get same size - screenSizing_EmphTop, // make top screen as big as possible, fit bottom screen in remaining space - screenSizing_EmphBot, - screenSizing_Auto, // not applied in SetupScreenLayout - screenSizing_TopOnly, - screenSizing_BotOnly, - screenSizing_MAX, -}; - -// setup the display layout based on the provided display size and parameters -// * screenWidth/screenHeight: size of the host display -// * screenLayout: how the DS screens are laid out -// * rotation: angle at which the DS screens are presented -// * sizing: how the display size is shared between the two screens -// * screenGap: size of the gap between the two screens in pixels -// * integerScale: force screens to be scaled up at integer scaling factors -// * screenSwap: whether to swap the position of both screens -// * topAspect/botAspect: ratio by which to scale the top and bottom screen respectively -void SetupScreenLayout(int screenWidth, int screenHeight, - ScreenLayout screenLayout, - ScreenRotation rotation, - ScreenSizing sizing, - int screenGap, - bool integerScale, - bool swapScreens, - float topAspect, float botAspect); - -const int MaxScreenTransforms = 3; - -// get a 2x3 transform matrix for each screen and whether it's a top or bottom screen -// note: the transform assumes an origin point at the top left of the display, -// X going right and Y going down -// for each screen the source coordinates should be (0,0) and (256,192) -// 'out' should point to an array of 6*MaxScreenTransforms floats -// 'kind' should point to an array of MaxScreenTransforms ints -// (0 = indicates top screen, 1 = bottom screen) -// returns the amount of screens -int GetScreenTransforms(float* out, int* kind); - -// de-transform the provided host display coordinates to get coordinates -// on the bottom screen -bool GetTouchCoords(int& x, int& y, bool clamp); - - -// initialize the audio utility -void Init_Audio(int outputfreq); - -// get how many samples to read from the core audio output -// based on how many are needed by the frontend (outlen in samples) -int AudioOut_GetNumSamples(int outlen); - -// resample audio from the core audio output to match the frontend's -// output frequency, and apply specified volume -// note: this assumes the output buffer is interleaved stereo -void AudioOut_Resample(s16* inbuf, int inlen, s16* outbuf, int outlen, int volume); - -// feed silence to the microphone input -void Mic_FeedSilence(NDS& nds); - -// feed random noise to the microphone input -void Mic_FeedNoise(NDS& nds); - -// feed an external buffer to the microphone input -// buffer should be mono -void Mic_FeedExternalBuffer(NDS& nds); -void Mic_SetExternalBuffer(s16* buffer, u32 len); - -} - -#endif // FRONTENDUTIL_H diff --git a/src/frontend/Util_Video.cpp b/src/frontend/ScreenLayout.cpp similarity index 93% rename from src/frontend/Util_Video.cpp rename to src/frontend/ScreenLayout.cpp index e772be9af9..d144f9821a 100644 --- a/src/frontend/Util_Video.cpp +++ b/src/frontend/ScreenLayout.cpp @@ -1,553 +1,548 @@ -/* - Copyright 2016-2023 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 -#include -#include -#include -#include - -#include "FrontendUtil.h" - - -namespace Frontend -{ - -float TopScreenMtx[6]; -float BotScreenMtx[6]; -float HybScreenMtx[6]; -float TouchMtx[6]; -float HybTouchMtx[6]; -bool TopEnable; -bool BotEnable; -bool HybEnable; -int HybScreen; -int HybPrevTouchScreen; // 0:unknown, 1:buttom screen, 2:hybrid screen - -void M23_Identity(float* m) -{ - m[0] = 1; m[1] = 0; - m[2] = 0; m[3] = 1; - m[4] = 0; m[5] = 0; -} - -void M23_Scale(float* m, float s) -{ - m[0] *= s; m[1] *= s; - m[2] *= s; m[3] *= s; - m[4] *= s; m[5] *= s; -} - -void M23_Scale(float* m, float x, float y) -{ - m[0] *= x; m[1] *= y; - m[2] *= x; m[3] *= y; - m[4] *= x; m[5] *= y; -} - -void M23_RotateFast(float* m, int angle) -{ - if (angle == 0) return; - - float temp[4]; memcpy(temp, m, sizeof(float)*4); - - switch (angle) - { - case 1: // 90 - m[0] = temp[2]; - m[1] = temp[3]; - m[2] = -temp[0]; - m[3] = -temp[1]; - break; - - case 2: // 180 - m[0] = -temp[0]; - m[1] = -temp[1]; - m[2] = -temp[2]; - m[3] = -temp[3]; - break; - - case 3: // 270 - m[0] = -temp[2]; - m[1] = -temp[3]; - m[2] = temp[0]; - m[3] = temp[1]; - break; - } -} - -void M23_Translate(float* m, float tx, float ty) -{ - m[4] += tx; - m[5] += ty; -} - -void M23_Multiply(float* m, float* _a, float* _b) -{ - float a[6]; memcpy(a, _a, 6*sizeof(float)); - float b[6]; memcpy(b, _b, 6*sizeof(float)); - - m[0] = (a[0] * b[0]) + (a[2] * b[1]); - m[1] = (a[1] * b[0]) + (a[3] * b[1]); - - m[2] = (a[0] * b[2]) + (a[2] * b[3]); - m[3] = (a[1] * b[2]) + (a[3] * b[3]); - - m[4] = (a[0] * b[4]) + (a[2] * b[5]) + a[4]; - m[5] = (a[1] * b[4]) + (a[3] * b[5]) + a[5]; -} - -void M23_Transform(float* m, float& x, float& y) -{ - float vx = x; - float vy = y; - - x = (vx * m[0]) + (vy * m[2]) + m[4]; - y = (vx * m[1]) + (vy * m[3]) + m[5]; -} - - -void SetupScreenLayout(int screenWidth, int screenHeight, - ScreenLayout screenLayout, - ScreenRotation rotation, - ScreenSizing sizing, - int screenGap, - bool integerScale, - bool swapScreens, - float topAspect, float botAspect) -{ - HybEnable = screenLayout == 3; - if (HybEnable) - { - screenLayout = screenLayout_Natural; - sizing = screenSizing_Even; - HybScreen = swapScreens ? 1 : 0; - swapScreens = false; - topAspect = botAspect = 1; - HybPrevTouchScreen = 0; - } - - float refpoints[6][2] = - { - {0, 0}, {256, 192}, - {0, 0}, {256, 192}, - {0, 0}, {256, 192} - }; - - int layout = screenLayout == screenLayout_Natural - ? rotation % 2 - : screenLayout - 1; - - float botScale = 1; - float hybScale = 1; - float botTrans[4] = {0}; - float hybTrans[2] = {0}; - - M23_Identity(TopScreenMtx); - M23_Identity(BotScreenMtx); - M23_Identity(HybScreenMtx); - - M23_Translate(TopScreenMtx, -256/2, -192/2); - M23_Translate(BotScreenMtx, -256/2, -192/2); - - M23_Scale(TopScreenMtx, topAspect, 1); - M23_Scale(BotScreenMtx, botAspect, 1); - - // rotation - { - float rotmtx[6]; - M23_Identity(rotmtx); - - M23_RotateFast(rotmtx, rotation); - M23_Multiply(TopScreenMtx, rotmtx, TopScreenMtx); - M23_Multiply(BotScreenMtx, rotmtx, BotScreenMtx); - M23_Multiply(HybScreenMtx, rotmtx, HybScreenMtx); - - M23_Transform(TopScreenMtx, refpoints[0][0], refpoints[0][1]); - M23_Transform(TopScreenMtx, refpoints[1][0], refpoints[1][1]); - M23_Transform(BotScreenMtx, refpoints[2][0], refpoints[2][1]); - M23_Transform(BotScreenMtx, refpoints[3][0], refpoints[3][1]); - } - - int posRefPointOffset = 0; - int posRefPointCount = HybEnable ? 6 : 4; - - if (sizing == screenSizing_TopOnly || sizing == screenSizing_BotOnly) - { - float* mtx = sizing == screenSizing_TopOnly ? TopScreenMtx : BotScreenMtx; - int primOffset = sizing == screenSizing_TopOnly ? 0 : 2; - int secOffset = sizing == screenSizing_BotOnly ? 2 : 0; - - float hSize = fabsf(refpoints[primOffset][0] - refpoints[primOffset+1][0]); - float vSize = fabsf(refpoints[primOffset][1] - refpoints[primOffset+1][1]); - - float scale = std::min(screenWidth / hSize, screenHeight / vSize); - if (integerScale) - scale = floorf(scale); - - TopEnable = sizing == screenSizing_TopOnly; - BotEnable = sizing == screenSizing_BotOnly; - botScale = scale; - - M23_Scale(mtx, scale); - refpoints[primOffset][0] *= scale; - refpoints[primOffset][1] *= scale; - refpoints[primOffset+1][0] *= scale; - refpoints[primOffset+1][1] *= scale; - - posRefPointOffset = primOffset; - posRefPointCount = 2; - } - else - { - TopEnable = BotEnable = true; - - // move screens apart - { - int idx = layout == 0 ? 1 : 0; - - bool moveV = rotation % 2 == layout; - - float offsetBot = (moveV ? 192.0 : 256.0 * botAspect) / 2.0 + screenGap / 2.0; - float offsetTop = -((moveV ? 192.0 : 256.0 * topAspect) / 2.0 + screenGap / 2.0); - - if ((rotation == 1 || rotation == 2) ^ swapScreens) - { - offsetTop *= -1; - offsetBot *= -1; - } - - M23_Translate(TopScreenMtx, (idx==0)?offsetTop:0, (idx==1)?offsetTop:0); - M23_Translate(BotScreenMtx, (idx==0)?offsetBot:0, (idx==1)?offsetBot:0); - - refpoints[0][idx] += offsetTop; - refpoints[1][idx] += offsetTop; - refpoints[2][idx] += offsetBot; - refpoints[3][idx] += offsetBot; - - botTrans[idx] = offsetBot; - } - - // scale - { - if (sizing == screenSizing_Even) - { - float minX = refpoints[0][0], maxX = minX; - float minY = refpoints[0][1], maxY = minY; - - for (int i = 1; i < 4; i++) - { - minX = std::min(minX, refpoints[i][0]); - minY = std::min(minY, refpoints[i][1]); - maxX = std::max(maxX, refpoints[i][0]); - maxY = std::max(maxY, refpoints[i][1]); - } - - float hSize = maxX - minX; - float vSize = maxY - minY; - - if (HybEnable) - { - hybScale = layout == 0 - ? (4 * vSize) / (3 * hSize) - : (4 * hSize) / (3 * vSize); - if (layout == 0) - hSize += (vSize * 4) / 3; - else - vSize += (hSize * 4) / 3; - } - - // scale evenly - float scale = std::min(screenWidth / hSize, screenHeight / vSize); - - if (integerScale) - scale = floor(scale); - - hybScale *= scale; - - M23_Scale(TopScreenMtx, scale); - M23_Scale(BotScreenMtx, scale); - M23_Scale(HybScreenMtx, hybScale); - - for (int i = 0; i < 4; i++) - { - refpoints[i][0] *= scale; - refpoints[i][1] *= scale; - } - - botScale = scale; - - // move screens aside - if (HybEnable) - { - float hybWidth = layout == 0 - ? (scale * vSize * 4) / 3 - : (scale * hSize * 4) / 3; - - if (rotation > screenRot_90Deg) - hybWidth *= -1; - - M23_Translate(TopScreenMtx, (layout==0)?hybWidth:0, (layout==1)?hybWidth:0); - M23_Translate(BotScreenMtx, (layout==0)?hybWidth:0, (layout==1)?hybWidth:0); - refpoints[0][layout] += hybWidth; - refpoints[1][layout] += hybWidth; - refpoints[2][layout] += hybWidth; - refpoints[3][layout] += hybWidth; - - botTrans[2+layout] += hybWidth; - - hybTrans[0] = scale * (rotation == screenRot_0Deg || rotation == screenRot_270Deg ? minX : maxX); - hybTrans[1] = scale * (rotation == screenRot_0Deg || rotation == screenRot_90Deg ? minY : maxY); - M23_Translate(HybScreenMtx, hybTrans[0], hybTrans[1]); - - M23_Transform(HybScreenMtx, refpoints[4][0], refpoints[4][1]); - M23_Transform(HybScreenMtx, refpoints[5][0], refpoints[5][1]); - } - } - else - { - int primOffset = (sizing == screenSizing_EmphTop) ? 0 : 2; - int secOffset = (sizing == screenSizing_EmphTop) ? 2 : 0; - float* primMtx = (sizing == screenSizing_EmphTop) ? TopScreenMtx : BotScreenMtx; - float* secMtx = (sizing == screenSizing_EmphTop) ? BotScreenMtx : TopScreenMtx; - - float primMinX = refpoints[primOffset][0], primMaxX = primMinX; - float primMinY = refpoints[primOffset][1], primMaxY = primMinY; - float secMinX = refpoints[secOffset][0], secMaxX = secMinX; - float secMinY = refpoints[secOffset][1], secMaxY = secMinY; - - primMinX = std::min(primMinX, refpoints[primOffset+1][0]); - primMinY = std::min(primMinY, refpoints[primOffset+1][1]); - primMaxX = std::max(primMaxX, refpoints[primOffset+1][0]); - primMaxY = std::max(primMaxY, refpoints[primOffset+1][1]); - - secMinX = std::min(secMinX, refpoints[secOffset+1][0]); - secMinY = std::min(secMinY, refpoints[secOffset+1][1]); - secMaxX = std::max(secMaxX, refpoints[secOffset+1][0]); - secMaxY = std::max(secMaxY, refpoints[secOffset+1][1]); - - float primHSize = layout == 1 ? std::max(primMaxX, -primMinX) : primMaxX - primMinX; - float primVSize = layout == 0 ? std::max(primMaxY, -primMinY) : primMaxY - primMinY; - - float secHSize = layout == 1 ? std::max(secMaxX, -secMinX) : secMaxX - secMinX; - float secVSize = layout == 0 ? std::max(secMaxY, -secMinY) : secMaxY - secMinY; - - float primScale = std::min(screenWidth / primHSize, screenHeight / primVSize); - float secScale = 1.f; - - if (integerScale) - primScale = floorf(primScale); - - if (layout == 0) - { - if (screenHeight - primVSize * primScale < secVSize) - primScale = std::min(screenWidth / primHSize, (screenHeight - secVSize) / primVSize); - else - secScale = std::min((screenHeight - primVSize * primScale) / secVSize, screenWidth / secHSize); - } - else - { - if (screenWidth - primHSize * primScale < secHSize) - primScale = std::min((screenWidth - secHSize) / primHSize, screenHeight / primVSize); - else - secScale = std::min((screenWidth - primHSize * primScale) / secHSize, screenHeight / secVSize); - } - - if (integerScale) - { - primScale = floorf(primScale); - secScale = floorf(secScale); - } - - M23_Scale(primMtx, primScale); - M23_Scale(secMtx, secScale); - - refpoints[primOffset+0][0] *= primScale; - refpoints[primOffset+0][1] *= primScale; - refpoints[primOffset+1][0] *= primScale; - refpoints[primOffset+1][1] *= primScale; - refpoints[secOffset+0][0] *= secScale; - refpoints[secOffset+0][1] *= secScale; - refpoints[secOffset+1][0] *= secScale; - refpoints[secOffset+1][1] *= secScale; - - botScale = (sizing == screenSizing_EmphTop) ? secScale : primScale; - } - } - } - - // position - { - float minX = refpoints[posRefPointOffset][0], maxX = minX; - float minY = refpoints[posRefPointOffset][1], maxY = minY; - - for (int i = posRefPointOffset + 1; i < posRefPointOffset + posRefPointCount; i++) - { - minX = std::min(minX, refpoints[i][0]); - minY = std::min(minY, refpoints[i][1]); - maxX = std::max(maxX, refpoints[i][0]); - maxY = std::max(maxY, refpoints[i][1]); - } - - float width = maxX - minX; - float height = maxY - minY; - - float tx = (screenWidth/2) - (width/2) - minX; - float ty = (screenHeight/2) - (height/2) - minY; - - M23_Translate(TopScreenMtx, tx, ty); - M23_Translate(BotScreenMtx, tx, ty); - M23_Translate(HybScreenMtx, tx, ty); - - botTrans[2] += tx; botTrans[3] += ty; - hybTrans[0] += tx; hybTrans[1] += ty; - } - - // prepare a 'reverse' matrix for the touchscreen - // this matrix undoes the transforms applied to the bottom screen - // and can be used to calculate touchscreen coords from host screen coords - if (BotEnable) - { - M23_Identity(TouchMtx); - - M23_Translate(TouchMtx, -botTrans[2], -botTrans[3]); - M23_Scale(TouchMtx, 1.f / botScale); - M23_Translate(TouchMtx, -botTrans[0], -botTrans[1]); - - float rotmtx[6]; - M23_Identity(rotmtx); - M23_RotateFast(rotmtx, (4-rotation) & 3); - M23_Multiply(TouchMtx, rotmtx, TouchMtx); - - M23_Scale(TouchMtx, 1.f/botAspect, 1); - M23_Translate(TouchMtx, 256/2, 192/2); - - if (HybEnable && HybScreen == 1) - { - M23_Identity(HybTouchMtx); - - M23_Translate(HybTouchMtx, -hybTrans[0], -hybTrans[1]); - M23_Scale(HybTouchMtx, 1.f/hybScale); - M23_Multiply(HybTouchMtx, rotmtx, HybTouchMtx); - } - } -} - -int GetScreenTransforms(float* out, int* kind) -{ - int num = 0; - if (TopEnable) - { - memcpy(out + 6*num, TopScreenMtx, sizeof(TopScreenMtx)); - kind[num++] = 0; - } - if (BotEnable) - { - memcpy(out + 6*num, BotScreenMtx, sizeof(BotScreenMtx)); - kind[num++] = 1; - } - if (HybEnable) - { - memcpy(out + 6*num, HybScreenMtx, sizeof(HybScreenMtx)); - kind[num++] = HybScreen; - } - return num; -} - -bool GetTouchCoords(int& x, int& y, bool clamp) -{ - if (HybEnable && HybScreen == 1) - { - float vx = x; - float vy = y; - float hvx = x; - float hvy = y; - - M23_Transform(TouchMtx, vx, vy); - M23_Transform(HybTouchMtx, hvx, hvy); - - if (clamp) - { - if (HybPrevTouchScreen == 1) - { - x = std::clamp((int)vx, 0, 255); - y = std::clamp((int)vy, 0, 191); - - return true; - } - if (HybPrevTouchScreen == 2) - { - x = std::clamp((int)hvx, 0, 255); - y = std::clamp((int)hvy, 0, 191); - - return true; - } - } - else - { - if (vx >= 0 && vx < 256 && vy >= 0 && vy < 192) - { - HybPrevTouchScreen = 1; - - x = (int)vx; - y = (int)vy; - - return true; - } - if (hvx >= 0 && hvx < 256 && hvy >= 0 && hvy < 192) - { - HybPrevTouchScreen = 2; - - x = (int)hvx; - y = (int)hvy; - - return true; - } - } - } - else if (BotEnable) - { - float vx = x; - float vy = y; - - M23_Transform(TouchMtx, vx, vy); - - if (clamp) - { - x = std::clamp((int)vx, 0, 255); - y = std::clamp((int)vy, 0, 191); - - return true; - } - else - { - if (vx >= 0 && vx < 256 && vy >= 0 && vy < 192) - { - x = (int)vx; - y = (int)vy; - - return true; - } - } - } - - return false; -} - -} - +/* + Copyright 2016-2023 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 +#include +#include + +#include "ScreenLayout.h" + + +void M23_Identity(float* m) +{ + m[0] = 1; m[1] = 0; + m[2] = 0; m[3] = 1; + m[4] = 0; m[5] = 0; +} + +void M23_Scale(float* m, float s) +{ + m[0] *= s; m[1] *= s; + m[2] *= s; m[3] *= s; + m[4] *= s; m[5] *= s; +} + +void M23_Scale(float* m, float x, float y) +{ + m[0] *= x; m[1] *= y; + m[2] *= x; m[3] *= y; + m[4] *= x; m[5] *= y; +} + +void M23_RotateFast(float* m, int angle) +{ + if (angle == 0) return; + + float temp[4]; memcpy(temp, m, sizeof(float)*4); + + switch (angle) + { + case 1: // 90 + m[0] = temp[2]; + m[1] = temp[3]; + m[2] = -temp[0]; + m[3] = -temp[1]; + break; + + case 2: // 180 + m[0] = -temp[0]; + m[1] = -temp[1]; + m[2] = -temp[2]; + m[3] = -temp[3]; + break; + + case 3: // 270 + m[0] = -temp[2]; + m[1] = -temp[3]; + m[2] = temp[0]; + m[3] = temp[1]; + break; + } +} + +void M23_Translate(float* m, float tx, float ty) +{ + m[4] += tx; + m[5] += ty; +} + +void M23_Multiply(float* m, float* _a, float* _b) +{ + float a[6]; memcpy(a, _a, 6*sizeof(float)); + float b[6]; memcpy(b, _b, 6*sizeof(float)); + + m[0] = (a[0] * b[0]) + (a[2] * b[1]); + m[1] = (a[1] * b[0]) + (a[3] * b[1]); + + m[2] = (a[0] * b[2]) + (a[2] * b[3]); + m[3] = (a[1] * b[2]) + (a[3] * b[3]); + + m[4] = (a[0] * b[4]) + (a[2] * b[5]) + a[4]; + m[5] = (a[1] * b[4]) + (a[3] * b[5]) + a[5]; +} + +void M23_Transform(float* m, float& x, float& y) +{ + float vx = x; + float vy = y; + + x = (vx * m[0]) + (vy * m[2]) + m[4]; + y = (vx * m[1]) + (vy * m[3]) + m[5]; +} + + +ScreenLayout::ScreenLayout() +{ + M23_Identity(TopScreenMtx); + M23_Identity(BotScreenMtx); + M23_Identity(HybScreenMtx); + M23_Identity(TouchMtx); + M23_Identity(HybTouchMtx); + TopEnable = true; + BotEnable = true; + HybEnable = false; + HybScreen = 0; + HybPrevTouchScreen = 0; +} + +void ScreenLayout::Setup(int screenWidth, int screenHeight, + ScreenLayoutType screenLayout, + ScreenRotation rotation, + ScreenSizing sizing, + int screenGap, + bool integerScale, + bool swapScreens, + float topAspect, float botAspect) +{ + HybEnable = screenLayout == 3; + if (HybEnable) + { + screenLayout = screenLayout_Natural; + sizing = screenSizing_Even; + HybScreen = swapScreens ? 1 : 0; + swapScreens = false; + topAspect = botAspect = 1; + HybPrevTouchScreen = 0; + } + + float refpoints[6][2] = + { + {0, 0}, {256, 192}, + {0, 0}, {256, 192}, + {0, 0}, {256, 192} + }; + + int layout = screenLayout == screenLayout_Natural + ? rotation % 2 + : screenLayout - 1; + + float botScale = 1; + float hybScale = 1; + float botTrans[4] = {0}; + float hybTrans[2] = {0}; + + M23_Identity(TopScreenMtx); + M23_Identity(BotScreenMtx); + M23_Identity(HybScreenMtx); + + M23_Translate(TopScreenMtx, -256/2, -192/2); + M23_Translate(BotScreenMtx, -256/2, -192/2); + + M23_Scale(TopScreenMtx, topAspect, 1); + M23_Scale(BotScreenMtx, botAspect, 1); + + // rotation + { + float rotmtx[6]; + M23_Identity(rotmtx); + + M23_RotateFast(rotmtx, rotation); + M23_Multiply(TopScreenMtx, rotmtx, TopScreenMtx); + M23_Multiply(BotScreenMtx, rotmtx, BotScreenMtx); + M23_Multiply(HybScreenMtx, rotmtx, HybScreenMtx); + + M23_Transform(TopScreenMtx, refpoints[0][0], refpoints[0][1]); + M23_Transform(TopScreenMtx, refpoints[1][0], refpoints[1][1]); + M23_Transform(BotScreenMtx, refpoints[2][0], refpoints[2][1]); + M23_Transform(BotScreenMtx, refpoints[3][0], refpoints[3][1]); + } + + int posRefPointOffset = 0; + int posRefPointCount = HybEnable ? 6 : 4; + + if (sizing == screenSizing_TopOnly || sizing == screenSizing_BotOnly) + { + float* mtx = sizing == screenSizing_TopOnly ? TopScreenMtx : BotScreenMtx; + int primOffset = sizing == screenSizing_TopOnly ? 0 : 2; + int secOffset = sizing == screenSizing_BotOnly ? 2 : 0; + + float hSize = fabsf(refpoints[primOffset][0] - refpoints[primOffset+1][0]); + float vSize = fabsf(refpoints[primOffset][1] - refpoints[primOffset+1][1]); + + float scale = std::min(screenWidth / hSize, screenHeight / vSize); + if (integerScale) + scale = floorf(scale); + + TopEnable = sizing == screenSizing_TopOnly; + BotEnable = sizing == screenSizing_BotOnly; + botScale = scale; + + M23_Scale(mtx, scale); + refpoints[primOffset][0] *= scale; + refpoints[primOffset][1] *= scale; + refpoints[primOffset+1][0] *= scale; + refpoints[primOffset+1][1] *= scale; + + posRefPointOffset = primOffset; + posRefPointCount = 2; + } + else + { + TopEnable = BotEnable = true; + + // move screens apart + { + int idx = layout == 0 ? 1 : 0; + + bool moveV = rotation % 2 == layout; + + float offsetBot = (moveV ? 192.0 : 256.0 * botAspect) / 2.0 + screenGap / 2.0; + float offsetTop = -((moveV ? 192.0 : 256.0 * topAspect) / 2.0 + screenGap / 2.0); + + if ((rotation == 1 || rotation == 2) ^ swapScreens) + { + offsetTop *= -1; + offsetBot *= -1; + } + + M23_Translate(TopScreenMtx, (idx==0)?offsetTop:0, (idx==1)?offsetTop:0); + M23_Translate(BotScreenMtx, (idx==0)?offsetBot:0, (idx==1)?offsetBot:0); + + refpoints[0][idx] += offsetTop; + refpoints[1][idx] += offsetTop; + refpoints[2][idx] += offsetBot; + refpoints[3][idx] += offsetBot; + + botTrans[idx] = offsetBot; + } + + // scale + { + if (sizing == screenSizing_Even) + { + float minX = refpoints[0][0], maxX = minX; + float minY = refpoints[0][1], maxY = minY; + + for (int i = 1; i < 4; i++) + { + minX = std::min(minX, refpoints[i][0]); + minY = std::min(minY, refpoints[i][1]); + maxX = std::max(maxX, refpoints[i][0]); + maxY = std::max(maxY, refpoints[i][1]); + } + + float hSize = maxX - minX; + float vSize = maxY - minY; + + if (HybEnable) + { + hybScale = layout == 0 + ? (4 * vSize) / (3 * hSize) + : (4 * hSize) / (3 * vSize); + if (layout == 0) + hSize += (vSize * 4) / 3; + else + vSize += (hSize * 4) / 3; + } + + // scale evenly + float scale = std::min(screenWidth / hSize, screenHeight / vSize); + + if (integerScale) + scale = floor(scale); + + hybScale *= scale; + + M23_Scale(TopScreenMtx, scale); + M23_Scale(BotScreenMtx, scale); + M23_Scale(HybScreenMtx, hybScale); + + for (int i = 0; i < 4; i++) + { + refpoints[i][0] *= scale; + refpoints[i][1] *= scale; + } + + botScale = scale; + + // move screens aside + if (HybEnable) + { + float hybWidth = layout == 0 + ? (scale * vSize * 4) / 3 + : (scale * hSize * 4) / 3; + + if (rotation > screenRot_90Deg) + hybWidth *= -1; + + M23_Translate(TopScreenMtx, (layout==0)?hybWidth:0, (layout==1)?hybWidth:0); + M23_Translate(BotScreenMtx, (layout==0)?hybWidth:0, (layout==1)?hybWidth:0); + refpoints[0][layout] += hybWidth; + refpoints[1][layout] += hybWidth; + refpoints[2][layout] += hybWidth; + refpoints[3][layout] += hybWidth; + + botTrans[2+layout] += hybWidth; + + hybTrans[0] = scale * (rotation == screenRot_0Deg || rotation == screenRot_270Deg ? minX : maxX); + hybTrans[1] = scale * (rotation == screenRot_0Deg || rotation == screenRot_90Deg ? minY : maxY); + M23_Translate(HybScreenMtx, hybTrans[0], hybTrans[1]); + + M23_Transform(HybScreenMtx, refpoints[4][0], refpoints[4][1]); + M23_Transform(HybScreenMtx, refpoints[5][0], refpoints[5][1]); + } + } + else + { + int primOffset = (sizing == screenSizing_EmphTop) ? 0 : 2; + int secOffset = (sizing == screenSizing_EmphTop) ? 2 : 0; + float* primMtx = (sizing == screenSizing_EmphTop) ? TopScreenMtx : BotScreenMtx; + float* secMtx = (sizing == screenSizing_EmphTop) ? BotScreenMtx : TopScreenMtx; + + float primMinX = refpoints[primOffset][0], primMaxX = primMinX; + float primMinY = refpoints[primOffset][1], primMaxY = primMinY; + float secMinX = refpoints[secOffset][0], secMaxX = secMinX; + float secMinY = refpoints[secOffset][1], secMaxY = secMinY; + + primMinX = std::min(primMinX, refpoints[primOffset+1][0]); + primMinY = std::min(primMinY, refpoints[primOffset+1][1]); + primMaxX = std::max(primMaxX, refpoints[primOffset+1][0]); + primMaxY = std::max(primMaxY, refpoints[primOffset+1][1]); + + secMinX = std::min(secMinX, refpoints[secOffset+1][0]); + secMinY = std::min(secMinY, refpoints[secOffset+1][1]); + secMaxX = std::max(secMaxX, refpoints[secOffset+1][0]); + secMaxY = std::max(secMaxY, refpoints[secOffset+1][1]); + + float primHSize = layout == 1 ? std::max(primMaxX, -primMinX) : primMaxX - primMinX; + float primVSize = layout == 0 ? std::max(primMaxY, -primMinY) : primMaxY - primMinY; + + float secHSize = layout == 1 ? std::max(secMaxX, -secMinX) : secMaxX - secMinX; + float secVSize = layout == 0 ? std::max(secMaxY, -secMinY) : secMaxY - secMinY; + + float primScale = std::min(screenWidth / primHSize, screenHeight / primVSize); + float secScale = 1.f; + + if (integerScale) + primScale = floorf(primScale); + + if (layout == 0) + { + if (screenHeight - primVSize * primScale < secVSize) + primScale = std::min(screenWidth / primHSize, (screenHeight - secVSize) / primVSize); + else + secScale = std::min((screenHeight - primVSize * primScale) / secVSize, screenWidth / secHSize); + } + else + { + if (screenWidth - primHSize * primScale < secHSize) + primScale = std::min((screenWidth - secHSize) / primHSize, screenHeight / primVSize); + else + secScale = std::min((screenWidth - primHSize * primScale) / secHSize, screenHeight / secVSize); + } + + if (integerScale) + { + primScale = floorf(primScale); + secScale = floorf(secScale); + } + + M23_Scale(primMtx, primScale); + M23_Scale(secMtx, secScale); + + refpoints[primOffset+0][0] *= primScale; + refpoints[primOffset+0][1] *= primScale; + refpoints[primOffset+1][0] *= primScale; + refpoints[primOffset+1][1] *= primScale; + refpoints[secOffset+0][0] *= secScale; + refpoints[secOffset+0][1] *= secScale; + refpoints[secOffset+1][0] *= secScale; + refpoints[secOffset+1][1] *= secScale; + + botScale = (sizing == screenSizing_EmphTop) ? secScale : primScale; + } + } + } + + // position + { + float minX = refpoints[posRefPointOffset][0], maxX = minX; + float minY = refpoints[posRefPointOffset][1], maxY = minY; + + for (int i = posRefPointOffset + 1; i < posRefPointOffset + posRefPointCount; i++) + { + minX = std::min(minX, refpoints[i][0]); + minY = std::min(minY, refpoints[i][1]); + maxX = std::max(maxX, refpoints[i][0]); + maxY = std::max(maxY, refpoints[i][1]); + } + + float width = maxX - minX; + float height = maxY - minY; + + float tx = (screenWidth/2) - (width/2) - minX; + float ty = (screenHeight/2) - (height/2) - minY; + + M23_Translate(TopScreenMtx, tx, ty); + M23_Translate(BotScreenMtx, tx, ty); + M23_Translate(HybScreenMtx, tx, ty); + + botTrans[2] += tx; botTrans[3] += ty; + hybTrans[0] += tx; hybTrans[1] += ty; + } + + // prepare a 'reverse' matrix for the touchscreen + // this matrix undoes the transforms applied to the bottom screen + // and can be used to calculate touchscreen coords from host screen coords + if (BotEnable) + { + M23_Identity(TouchMtx); + + M23_Translate(TouchMtx, -botTrans[2], -botTrans[3]); + M23_Scale(TouchMtx, 1.f / botScale); + M23_Translate(TouchMtx, -botTrans[0], -botTrans[1]); + + float rotmtx[6]; + M23_Identity(rotmtx); + M23_RotateFast(rotmtx, (4-rotation) & 3); + M23_Multiply(TouchMtx, rotmtx, TouchMtx); + + M23_Scale(TouchMtx, 1.f/botAspect, 1); + M23_Translate(TouchMtx, 256/2, 192/2); + + if (HybEnable && HybScreen == 1) + { + M23_Identity(HybTouchMtx); + + M23_Translate(HybTouchMtx, -hybTrans[0], -hybTrans[1]); + M23_Scale(HybTouchMtx, 1.f/hybScale); + M23_Multiply(HybTouchMtx, rotmtx, HybTouchMtx); + } + } +} + +int ScreenLayout::GetScreenTransforms(float* out, int* kind) +{ + int num = 0; + if (TopEnable) + { + memcpy(out + 6*num, TopScreenMtx, sizeof(TopScreenMtx)); + kind[num++] = 0; + } + if (BotEnable) + { + memcpy(out + 6*num, BotScreenMtx, sizeof(BotScreenMtx)); + kind[num++] = 1; + } + if (HybEnable) + { + memcpy(out + 6*num, HybScreenMtx, sizeof(HybScreenMtx)); + kind[num++] = HybScreen; + } + return num; +} + +bool ScreenLayout::GetTouchCoords(int& x, int& y, bool clamp) +{ + if (HybEnable && HybScreen == 1) + { + float vx = x; + float vy = y; + float hvx = x; + float hvy = y; + + M23_Transform(TouchMtx, vx, vy); + M23_Transform(HybTouchMtx, hvx, hvy); + + if (clamp) + { + if (HybPrevTouchScreen == 1) + { + x = std::clamp((int)vx, 0, 255); + y = std::clamp((int)vy, 0, 191); + + return true; + } + if (HybPrevTouchScreen == 2) + { + x = std::clamp((int)hvx, 0, 255); + y = std::clamp((int)hvy, 0, 191); + + return true; + } + } + else + { + if (vx >= 0 && vx < 256 && vy >= 0 && vy < 192) + { + HybPrevTouchScreen = 1; + + x = (int)vx; + y = (int)vy; + + return true; + } + if (hvx >= 0 && hvx < 256 && hvy >= 0 && hvy < 192) + { + HybPrevTouchScreen = 2; + + x = (int)hvx; + y = (int)hvy; + + return true; + } + } + } + else if (BotEnable) + { + float vx = x; + float vy = y; + + M23_Transform(TouchMtx, vx, vy); + + if (clamp) + { + x = std::clamp((int)vx, 0, 255); + y = std::clamp((int)vy, 0, 191); + + return true; + } + else + { + if (vx >= 0 && vx < 256 && vy >= 0 && vy < 192) + { + x = (int)vx; + y = (int)vy; + + return true; + } + } + } + + return false; +} diff --git a/src/frontend/ScreenLayout.h b/src/frontend/ScreenLayout.h new file mode 100644 index 0000000000..7c40526c25 --- /dev/null +++ b/src/frontend/ScreenLayout.h @@ -0,0 +1,104 @@ +/* + Copyright 2016-2023 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 SCREENLAYOUT_H +#define SCREENLAYOUT_H + +enum ScreenLayoutType +{ + screenLayout_Natural, // top screen above bottom screen always + screenLayout_Horizontal, + screenLayout_Vertical, + screenLayout_Hybrid, + screenLayout_MAX, +}; + +enum ScreenRotation +{ + screenRot_0Deg, + screenRot_90Deg, + screenRot_180Deg, + screenRot_270Deg, + screenRot_MAX, +}; + +enum ScreenSizing +{ + screenSizing_Even, // both screens get same size + screenSizing_EmphTop, // make top screen as big as possible, fit bottom screen in remaining space + screenSizing_EmphBot, + screenSizing_Auto, // not applied in SetupScreenLayout + screenSizing_TopOnly, + screenSizing_BotOnly, + screenSizing_MAX, +}; + +const int kMaxScreenTransforms = 3; + +class ScreenLayout +{ +public: + ScreenLayout(); + ~ScreenLayout() {} + + // setup the display layout based on the provided display size and parameters + // * screenWidth/screenHeight: size of the host display + // * screenLayout: how the DS screens are laid out + // * rotation: angle at which the DS screens are presented + // * sizing: how the display size is shared between the two screens + // * screenGap: size of the gap between the two screens in pixels + // * integerScale: force screens to be scaled up at integer scaling factors + // * screenSwap: whether to swap the position of both screens + // * topAspect/botAspect: ratio by which to scale the top and bottom screen respectively + void Setup(int screenWidth, int screenHeight, + ScreenLayoutType screenLayout, + ScreenRotation rotation, + ScreenSizing sizing, + int screenGap, + bool integerScale, + bool swapScreens, + float topAspect, float botAspect); + + // get a 2x3 transform matrix for each screen and whether it's a top or bottom screen + // note: the transform assumes an origin point at the top left of the display, + // X going right and Y going down + // for each screen the source coordinates should be (0,0) and (256,192) + // 'out' should point to an array of 6*MaxScreenTransforms floats + // 'kind' should point to an array of MaxScreenTransforms ints + // (0 = indicates top screen, 1 = bottom screen) + // returns the amount of screens + int GetScreenTransforms(float* out, int* kind); + + // de-transform the provided host display coordinates to get coordinates + // on the bottom screen + bool GetTouchCoords(int& x, int& y, bool clamp); + +private: + float TopScreenMtx[6]; + float BotScreenMtx[6]; + float HybScreenMtx[6]; + float TouchMtx[6]; + float HybTouchMtx[6]; + bool TopEnable; + bool BotEnable; + bool HybEnable; + int HybScreen; + int HybPrevTouchScreen; // 0:unknown, 1:buttom screen, 2:hybrid screen +}; + +#endif // SCREENLAYOUT_H diff --git a/src/frontend/Util_Audio.cpp b/src/frontend/Util_Audio.cpp deleted file mode 100644 index 25e04db380..0000000000 --- a/src/frontend/Util_Audio.cpp +++ /dev/null @@ -1,143 +0,0 @@ -/* - Copyright 2016-2023 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 -#include -#include -#include - -#include "FrontendUtil.h" - -#include "NDS.h" - -#include "mic_blow.h" - -using namespace melonDS; - -namespace Frontend -{ - -int AudioOut_Freq; -float AudioOut_SampleFrac; - -s16* MicBuffer; -u32 MicBufferLength; -u32 MicBufferReadPos; - - -void Init_Audio(int outputfreq) -{ - AudioOut_Freq = outputfreq; - AudioOut_SampleFrac = 0; - - MicBuffer = nullptr; - MicBufferLength = 0; - MicBufferReadPos = 0; -} - - -int AudioOut_GetNumSamples(int outlen) -{ - float f_len_in = (outlen * 32823.6328125) / (float)AudioOut_Freq; - f_len_in += AudioOut_SampleFrac; - int len_in = (int)floor(f_len_in); - AudioOut_SampleFrac = f_len_in - len_in; - - return len_in; -} - -void AudioOut_Resample(s16* inbuf, int inlen, s16* outbuf, int outlen, int volume) -{ - double factor = (double) inlen / (double) outlen; - double inpos = -(factor / 2); - double vol = (double) volume / 256.f; - - for (int i = 0; i < outlen * 2; i += 2) - { - double intpart_d; - double frac = modf(inpos, &intpart_d); - int intpart = (int) intpart_d; - - double l1 = inbuf[ intpart * 2]; - double l2 = inbuf[(intpart * 2) + 2]; - double r1 = inbuf[(intpart * 2) + 1]; - double r2 = inbuf[(intpart * 2) + 3]; - - double ldiff = l2 - l1; - double rdiff = r2 - r1; - - outbuf[i] = (s16) round((l1 + ldiff * frac) * vol); - outbuf[i+1] = (s16) round((r1 + rdiff * frac) * vol); - - inpos += factor; - } -} - - -void Mic_FeedSilence(NDS& nds) -{ - MicBufferReadPos = 0; - nds.MicInputFrame(NULL, 0); -} - -void Mic_FeedNoise(NDS& nds) -{ - int sample_len = sizeof(mic_blow) / sizeof(u16); - static int sample_pos = 0; - - s16 tmp[735]; - - for (int i = 0; i < 735; i++) - { - tmp[i] = mic_blow[sample_pos]; - sample_pos++; - if (sample_pos >= sample_len) sample_pos = 0; - } - - nds.MicInputFrame(tmp, 735); -} - -void Mic_FeedExternalBuffer(NDS& nds) -{ - if (!MicBuffer) return Mic_FeedSilence(nds); - - if ((MicBufferReadPos + 735) > MicBufferLength) - { - s16 tmp[735]; - u32 len1 = MicBufferLength - MicBufferReadPos; - memcpy(&tmp[0], &MicBuffer[MicBufferReadPos], len1*sizeof(s16)); - memcpy(&tmp[len1], &MicBuffer[0], (735 - len1)*sizeof(s16)); - - nds.MicInputFrame(tmp, 735); - MicBufferReadPos = 735 - len1; - } - else - { - nds.MicInputFrame(&MicBuffer[MicBufferReadPos], 735); - MicBufferReadPos += 735; - } -} - -void Mic_SetExternalBuffer(s16* buffer, u32 len) -{ - MicBuffer = buffer; - MicBufferLength = len; - MicBufferReadPos = 0; -} - -} diff --git a/src/frontend/qt_sdl/AudioInOut.cpp b/src/frontend/qt_sdl/AudioInOut.cpp deleted file mode 100644 index 1f1ee1c5c1..0000000000 --- a/src/frontend/qt_sdl/AudioInOut.cpp +++ /dev/null @@ -1,390 +0,0 @@ -/* - Copyright 2016-2023 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 "AudioInOut.h" - -#include - -#include "FrontendUtil.h" -#include "Config.h" -#include "NDS.h" -#include "SPU.h" -#include "Platform.h" -#include "Input.h" -#include "main.h" - -using namespace melonDS; -namespace AudioInOut -{ - -SDL_AudioDeviceID audioDevice; -int audioFreq; -bool audioMuted; -SDL_cond* audioSync; -SDL_mutex* audioSyncLock; - -SDL_AudioDeviceID micDevice; -s16 micExtBuffer[2048]; -u32 micExtBufferWritePos; - -u32 micWavLength; -s16* micWavBuffer; - -void AudioCallback(void* data, Uint8* stream, int len) -{ - len /= (sizeof(s16) * 2); - - // resample incoming audio to match the output sample rate - - int len_in = Frontend::AudioOut_GetNumSamples(len); - s16 buf_in[1024*2]; - int num_in; - - EmuThread* emuThread = (EmuThread*)data; - SDL_LockMutex(audioSyncLock); - num_in = emuThread->NDS->SPU.ReadOutput(buf_in, len_in); - SDL_CondSignal(audioSync); - SDL_UnlockMutex(audioSyncLock); - - if ((num_in < 1) || audioMuted) - { - memset(stream, 0, len*sizeof(s16)*2); - return; - } - - int margin = 6; - if (num_in < len_in-margin) - { - int last = num_in-1; - - for (int i = num_in; i < len_in-margin; i++) - ((u32*)buf_in)[i] = ((u32*)buf_in)[last]; - - num_in = len_in-margin; - } - - Frontend::AudioOut_Resample(buf_in, num_in, (s16*)stream, len, Config::AudioVolume); -} - -void MicCallback(void* data, Uint8* stream, int len) -{ - s16* input = (s16*)stream; - len /= sizeof(s16); - - int maxlen = sizeof(micExtBuffer) / sizeof(s16); - - if ((micExtBufferWritePos + len) > maxlen) - { - u32 len1 = maxlen - micExtBufferWritePos; - memcpy(&micExtBuffer[micExtBufferWritePos], &input[0], len1*sizeof(s16)); - memcpy(&micExtBuffer[0], &input[len1], (len - len1)*sizeof(s16)); - micExtBufferWritePos = len - len1; - } - else - { - memcpy(&micExtBuffer[micExtBufferWritePos], input, len*sizeof(s16)); - micExtBufferWritePos += len; - } -} - -void AudioMute(QMainWindow* mainWindow) -{ - 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 != nullptr) - audioMuted = !mainWindow->isActiveWindow(); - break; - } -} - - -void MicOpen() -{ - if (Config::MicInputType != micInputType_External) - { - micDevice = 0; - return; - } - - int numMics = SDL_GetNumAudioDevices(1); - if (numMics == 0) - return; - - SDL_AudioSpec whatIwant, whatIget; - memset(&whatIwant, 0, sizeof(SDL_AudioSpec)); - whatIwant.freq = 44100; - whatIwant.format = AUDIO_S16LSB; - whatIwant.channels = 1; - whatIwant.samples = 1024; - whatIwant.callback = MicCallback; - const char* mic = NULL; - if (Config::MicDevice != "") - { - mic = Config::MicDevice.c_str(); - } - micDevice = SDL_OpenAudioDevice(mic, 1, &whatIwant, &whatIget, 0); - if (!micDevice) - { - Platform::Log(Platform::LogLevel::Error, "Mic init failed: %s\n", SDL_GetError()); - } - else - { - SDL_PauseAudioDevice(micDevice, 0); - } -} - -void MicClose() -{ - if (micDevice) - SDL_CloseAudioDevice(micDevice); - - micDevice = 0; -} - -void MicLoadWav(const std::string& name) -{ - SDL_AudioSpec format; - memset(&format, 0, sizeof(SDL_AudioSpec)); - - if (micWavBuffer) delete[] micWavBuffer; - micWavBuffer = nullptr; - micWavLength = 0; - - u8* buf; - u32 len; - if (!SDL_LoadWAV(name.c_str(), &format, &buf, &len)) - return; - - const u64 dstfreq = 44100; - - int srcinc = format.channels; - len /= ((SDL_AUDIO_BITSIZE(format.format) / 8) * srcinc); - - micWavLength = (len * dstfreq) / format.freq; - if (micWavLength < 735) micWavLength = 735; - micWavBuffer = new s16[micWavLength]; - - float res_incr = len / (float)micWavLength; - float res_timer = 0; - int res_pos = 0; - - for (int i = 0; i < micWavLength; i++) - { - u16 val = 0; - - switch (SDL_AUDIO_BITSIZE(format.format)) - { - case 8: - val = buf[res_pos] << 8; - break; - - case 16: - if (SDL_AUDIO_ISBIGENDIAN(format.format)) - val = (buf[res_pos*2] << 8) | buf[res_pos*2 + 1]; - else - val = (buf[res_pos*2 + 1] << 8) | buf[res_pos*2]; - break; - - case 32: - if (SDL_AUDIO_ISFLOAT(format.format)) - { - u32 rawval; - if (SDL_AUDIO_ISBIGENDIAN(format.format)) - rawval = (buf[res_pos*4] << 24) | (buf[res_pos*4 + 1] << 16) | (buf[res_pos*4 + 2] << 8) | buf[res_pos*4 + 3]; - else - rawval = (buf[res_pos*4 + 3] << 24) | (buf[res_pos*4 + 2] << 16) | (buf[res_pos*4 + 1] << 8) | buf[res_pos*4]; - - float fval = *(float*)&rawval; - s32 ival = (s32)(fval * 0x8000); - ival = std::clamp(ival, -0x8000, 0x7FFF); - val = (s16)ival; - } - else if (SDL_AUDIO_ISBIGENDIAN(format.format)) - val = (buf[res_pos*4] << 8) | buf[res_pos*4 + 1]; - else - val = (buf[res_pos*4 + 3] << 8) | buf[res_pos*4 + 2]; - break; - } - - if (SDL_AUDIO_ISUNSIGNED(format.format)) - val ^= 0x8000; - - micWavBuffer[i] = val; - - res_timer += res_incr; - while (res_timer >= 1.0) - { - res_timer -= 1.0; - res_pos += srcinc; - } - } - - SDL_FreeWAV(buf); -} - -void MicProcess(melonDS::NDS& nds) -{ - int type = Config::MicInputType; - bool cmd = Input::HotkeyDown(HK_Mic); - - if (type != micInputType_External && !cmd) - { - type = micInputType_Silence; - } - - switch (type) - { - case micInputType_Silence: // no mic - Frontend::Mic_FeedSilence(nds); - break; - - case micInputType_External: // host mic - case micInputType_Wav: // WAV - Frontend::Mic_FeedExternalBuffer(nds); - break; - - case micInputType_Noise: // blowing noise - Frontend::Mic_FeedNoise(nds); - break; - } -} - -void SetupMicInputData() -{ - if (micWavBuffer != nullptr) - { - delete[] micWavBuffer; - micWavBuffer = nullptr; - micWavLength = 0; - } - - switch (Config::MicInputType) - { - case micInputType_Silence: - case micInputType_Noise: - Frontend::Mic_SetExternalBuffer(NULL, 0); - break; - case micInputType_External: - Frontend::Mic_SetExternalBuffer(micExtBuffer, sizeof(micExtBuffer)/sizeof(s16)); - break; - case micInputType_Wav: - MicLoadWav(Config::MicWavPath); - Frontend::Mic_SetExternalBuffer(micWavBuffer, micWavLength); - break; - } -} - -void Init(EmuThread* thread) -{ - audioMuted = false; - audioSync = SDL_CreateCond(); - audioSyncLock = SDL_CreateMutex(); - - audioFreq = 48000; // TODO: make configurable? - SDL_AudioSpec whatIwant, whatIget; - memset(&whatIwant, 0, sizeof(SDL_AudioSpec)); - whatIwant.freq = audioFreq; - whatIwant.format = AUDIO_S16LSB; - whatIwant.channels = 2; - whatIwant.samples = 1024; - whatIwant.callback = AudioCallback; - whatIwant.userdata = thread; - audioDevice = SDL_OpenAudioDevice(NULL, 0, &whatIwant, &whatIget, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE); - if (!audioDevice) - { - Platform::Log(Platform::LogLevel::Error, "Audio init failed: %s\n", SDL_GetError()); - } - else - { - audioFreq = whatIget.freq; - Platform::Log(Platform::LogLevel::Info, "Audio output frequency: %d Hz\n", audioFreq); - SDL_PauseAudioDevice(audioDevice, 1); - } - - micDevice = 0; - - memset(micExtBuffer, 0, sizeof(micExtBuffer)); - micExtBufferWritePos = 0; - micWavBuffer = nullptr; - - Frontend::Init_Audio(audioFreq); - - SetupMicInputData(); -} - -void DeInit() -{ - if (audioDevice) SDL_CloseAudioDevice(audioDevice); - audioDevice = 0; - MicClose(); - - if (audioSync) SDL_DestroyCond(audioSync); - audioSync = nullptr; - - if (audioSyncLock) SDL_DestroyMutex(audioSyncLock); - audioSyncLock = nullptr; - - if (micWavBuffer) delete[] micWavBuffer; - micWavBuffer = nullptr; -} - -void AudioSync(NDS& nds) -{ - if (audioDevice) - { - SDL_LockMutex(audioSyncLock); - while (nds.SPU.GetOutputSize() > 1024) - { - int ret = SDL_CondWaitTimeout(audioSync, audioSyncLock, 500); - if (ret == SDL_MUTEX_TIMEDOUT) break; - } - SDL_UnlockMutex(audioSyncLock); - } -} - -void UpdateSettings(NDS& nds) -{ - MicClose(); - - nds.SPU.SetInterpolation(static_cast(Config::AudioInterp)); - SetupMicInputData(); - - MicOpen(); -} - -void Enable() -{ - if (audioDevice) SDL_PauseAudioDevice(audioDevice, 0); - MicOpen(); -} - -void Disable() -{ - if (audioDevice) SDL_PauseAudioDevice(audioDevice, 1); - MicClose(); -} - -} diff --git a/src/frontend/qt_sdl/AudioSettingsDialog.cpp b/src/frontend/qt_sdl/AudioSettingsDialog.cpp index 8e08ef2bc4..a3230f4da2 100644 --- a/src/frontend/qt_sdl/AudioSettingsDialog.cpp +++ b/src/frontend/qt_sdl/AudioSettingsDialog.cpp @@ -16,7 +16,6 @@ with melonDS. If not, see http://www.gnu.org/licenses/. */ -#include #include #include @@ -25,7 +24,6 @@ #include "Config.h" #include "NDS.h" #include "DSi.h" -#include "DSi_I2C.h" #include "AudioSettingsDialog.h" #include "ui_AudioSettingsDialog.h" @@ -34,47 +32,56 @@ using namespace melonDS; AudioSettingsDialog* AudioSettingsDialog::currentDlg = nullptr; -extern std::string EmuDirectory; - -AudioSettingsDialog::AudioSettingsDialog(QWidget* parent, bool emuActive, EmuThread* emuThread) : QDialog(parent), ui(new Ui::AudioSettingsDialog), emuThread(emuThread) +AudioSettingsDialog::AudioSettingsDialog(QWidget* parent) : QDialog(parent), ui(new Ui::AudioSettingsDialog) { ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose); - oldInterp = Config::AudioInterp; - oldBitDepth = Config::AudioBitDepth; - oldVolume = Config::AudioVolume; - oldDSiSync = Config::DSiVolumeSync; + emuInstance = ((MainWindow*)parent)->getEmuInstance(); + auto& cfg = emuInstance->getGlobalConfig(); + auto& instcfg = emuInstance->getLocalConfig(); + bool emuActive = emuInstance->getEmuThread()->emuIsActive(); + + oldInterp = cfg.GetInt("Audio.Interpolation"); + oldBitDepth = cfg.GetInt("Audio.BitDepth"); + oldVolume = instcfg.GetInt("Audio.Volume"); + oldDSiSync = instcfg.GetBool("Audio.DSiVolumeSync"); + + volume = oldVolume; + dsiSync = oldDSiSync; ui->cbInterpolation->addItem("None"); ui->cbInterpolation->addItem("Linear"); ui->cbInterpolation->addItem("Cosine"); ui->cbInterpolation->addItem("Cubic"); ui->cbInterpolation->addItem("Gaussian (SNES)"); - ui->cbInterpolation->setCurrentIndex(Config::AudioInterp); + ui->cbInterpolation->setCurrentIndex(oldInterp); ui->cbBitDepth->addItem("Automatic"); ui->cbBitDepth->addItem("10-bit"); ui->cbBitDepth->addItem("16-bit"); - ui->cbBitDepth->setCurrentIndex(Config::AudioBitDepth); + ui->cbBitDepth->setCurrentIndex(oldBitDepth); bool state = ui->slVolume->blockSignals(true); - ui->slVolume->setValue(Config::AudioVolume); + ui->slVolume->setValue(oldVolume); ui->slVolume->blockSignals(state); - ui->chkSyncDSiVolume->setChecked(Config::DSiVolumeSync); + ui->chkSyncDSiVolume->setChecked(oldDSiSync); // Setup volume slider accordingly - if (emuActive && emuThread->NDS->ConsoleType == 1) + if (emuActive && emuInstance->getNDS()->ConsoleType == 1) { - on_chkSyncDSiVolume_clicked(Config::DSiVolumeSync); + on_chkSyncDSiVolume_clicked(oldDSiSync); } else { ui->chkSyncDSiVolume->setEnabled(false); } - bool isext = (Config::MicInputType == 1); + + int mictype = cfg.GetInt("Mic.InputType"); + + bool isext = (mictype == micInputType_External); ui->cbMic->setEnabled(isext); const int count = SDL_GetNumAudioDevices(true); @@ -82,12 +89,14 @@ AudioSettingsDialog::AudioSettingsDialog(QWidget* parent, bool emuActive, EmuThr { ui->cbMic->addItem(SDL_GetAudioDeviceName(i, true)); } - if (Config::MicDevice == "" && count > 0) + + QString micdev = cfg.GetQString("Mic.Device"); + if (micdev == "" && count > 0) { - Config::MicDevice = SDL_GetAudioDeviceName(0, true); + micdev = SDL_GetAudioDeviceName(0, true); } - ui->cbMic->setCurrentText(QString::fromStdString(Config::MicDevice)); + ui->cbMic->setCurrentText(micdev); grpMicMode = new QButtonGroup(this); grpMicMode->addButton(ui->rbMicNone, micInputType_Silence); @@ -95,15 +104,15 @@ AudioSettingsDialog::AudioSettingsDialog(QWidget* parent, bool emuActive, EmuThr grpMicMode->addButton(ui->rbMicNoise, micInputType_Noise); grpMicMode->addButton(ui->rbMicWav, micInputType_Wav); connect(grpMicMode, SIGNAL(buttonClicked(int)), this, SLOT(onChangeMicMode(int))); - grpMicMode->button(Config::MicInputType)->setChecked(true); + grpMicMode->button(mictype)->setChecked(true); - ui->txtMicWavPath->setText(QString::fromStdString(Config::MicWavPath)); + ui->txtMicWavPath->setText(cfg.GetQString("Mic.WavPath")); - bool iswav = (Config::MicInputType == micInputType_Wav); + bool iswav = (mictype == micInputType_Wav); ui->txtMicWavPath->setEnabled(iswav); ui->btnMicWavBrowse->setEnabled(iswav); - int inst = Platform::InstanceID(); + int inst = emuInstance->getInstanceID(); if (inst > 0) { ui->lblInstanceNum->setText(QString("Configuring settings for instance %1").arg(inst+1)); @@ -126,26 +135,30 @@ AudioSettingsDialog::~AudioSettingsDialog() void AudioSettingsDialog::onSyncVolumeLevel() { - if (Config::DSiVolumeSync && emuThread->NDS->ConsoleType == 1) + if (dsiSync && emuInstance->getNDS()->ConsoleType == 1) { - auto& dsi = static_cast(*emuThread->NDS); + auto dsi = static_cast(emuInstance->getNDS()); + volume = dsi->I2C.GetBPTWL()->GetVolumeLevel(); + bool state = ui->slVolume->blockSignals(true); - ui->slVolume->setValue(dsi.I2C.GetBPTWL()->GetVolumeLevel()); + ui->slVolume->setValue(volume); ui->slVolume->blockSignals(state); } } void AudioSettingsDialog::onConsoleReset() { - on_chkSyncDSiVolume_clicked(Config::DSiVolumeSync); - ui->chkSyncDSiVolume->setEnabled(emuThread->NDS->ConsoleType == 1); + on_chkSyncDSiVolume_clicked(dsiSync); + ui->chkSyncDSiVolume->setEnabled(emuInstance->getNDS()->ConsoleType == 1); } void AudioSettingsDialog::on_AudioSettingsDialog_accepted() { - Config::MicDevice = ui->cbMic->currentText().toStdString(); - Config::MicInputType = grpMicMode->checkedId(); - Config::MicWavPath = ui->txtMicWavPath->text().toStdString(); + auto& cfg = emuInstance->getGlobalConfig(); + cfg.SetQString("Mic.Device", ui->cbMic->currentText()); + cfg.SetInt("Mic.InputType", grpMicMode->checkedId()); + cfg.SetQString("Mic.WavPath", ui->txtMicWavPath->text()); + Config::Save(); closeDlg(); @@ -153,10 +166,15 @@ void AudioSettingsDialog::on_AudioSettingsDialog_accepted() void AudioSettingsDialog::on_AudioSettingsDialog_rejected() { - Config::AudioInterp = oldInterp; - Config::AudioBitDepth = oldBitDepth; - Config::AudioVolume = oldVolume; - Config::DSiVolumeSync = oldDSiSync; + auto& cfg = emuInstance->getGlobalConfig(); + auto& instcfg = emuInstance->getLocalConfig(); + cfg.SetInt("Audio.Interpolation", oldInterp); + cfg.SetInt("Audio.BitDepth", oldBitDepth); + instcfg.SetInt("Audio.Volume", oldVolume); + instcfg.SetBool("Audio.DSiVolumeSync", oldDSiSync); + + emit updateAudioVolume(oldVolume, oldDSiSync); + emit updateAudioSettings(); closeDlg(); } @@ -166,7 +184,8 @@ void AudioSettingsDialog::on_cbBitDepth_currentIndexChanged(int idx) // prevent a spurious change if (ui->cbBitDepth->count() < 3) return; - Config::AudioBitDepth = ui->cbBitDepth->currentIndex(); + auto& cfg = emuInstance->getGlobalConfig(); + cfg.SetInt("Audio.BitDepth", ui->cbBitDepth->currentIndex()); emit updateAudioSettings(); } @@ -176,45 +195,56 @@ void AudioSettingsDialog::on_cbInterpolation_currentIndexChanged(int idx) // prevent a spurious change if (ui->cbInterpolation->count() < 5) return; - Config::AudioInterp = ui->cbInterpolation->currentIndex(); + auto& cfg = emuInstance->getGlobalConfig(); + cfg.SetInt("Audio.Interpolation", ui->cbInterpolation->currentIndex()); emit updateAudioSettings(); } void AudioSettingsDialog::on_slVolume_valueChanged(int val) { - if (Config::DSiVolumeSync && emuThread->NDS->ConsoleType == 1) + auto& cfg = emuInstance->getLocalConfig(); + + if (dsiSync && emuInstance->getNDS()->ConsoleType == 1) { - auto& dsi = static_cast(*emuThread->NDS); - dsi.I2C.GetBPTWL()->SetVolumeLevel(val); + auto dsi = static_cast(emuInstance->getNDS()); + dsi->I2C.GetBPTWL()->SetVolumeLevel(val); return; } - Config::AudioVolume = val; + volume = val; + cfg.SetInt("Audio.Volume", val); + emit updateAudioVolume(val, dsiSync); } void AudioSettingsDialog::on_chkSyncDSiVolume_clicked(bool checked) { - Config::DSiVolumeSync = checked; + dsiSync = checked; + + auto& cfg = emuInstance->getLocalConfig(); + cfg.SetBool("Audio.DSiVolumeSync", dsiSync); bool state = ui->slVolume->blockSignals(true); - if (Config::DSiVolumeSync && emuThread->NDS->ConsoleType == 1) + if (dsiSync && emuInstance->getNDS()->ConsoleType == 1) { - auto& dsi = static_cast(*emuThread->NDS); + auto dsi = static_cast(emuInstance->getNDS()); ui->slVolume->setMaximum(31); - ui->slVolume->setValue(dsi.I2C.GetBPTWL()->GetVolumeLevel()); + ui->slVolume->setValue(dsi->I2C.GetBPTWL()->GetVolumeLevel()); ui->slVolume->setPageStep(4); ui->slVolume->setTickPosition(QSlider::TicksBelow); } else { - Config::AudioVolume = oldVolume; + volume = oldVolume; + cfg.SetInt("Audio.Volume", oldVolume); ui->slVolume->setMaximum(256); - ui->slVolume->setValue(Config::AudioVolume); + ui->slVolume->setValue(oldVolume); ui->slVolume->setPageStep(16); ui->slVolume->setTickPosition(QSlider::NoTicks); } ui->slVolume->blockSignals(state); + + emit updateAudioVolume(volume, dsiSync); } void AudioSettingsDialog::onChangeMicMode(int mode) @@ -230,7 +260,7 @@ void AudioSettingsDialog::on_btnMicWavBrowse_clicked() { QString file = QFileDialog::getOpenFileName(this, "Select WAV file...", - QString::fromStdString(EmuDirectory), + emuDirectory, "WAV files (*.wav);;Any file (*.*)"); if (file.isEmpty()) return; diff --git a/src/frontend/qt_sdl/AudioSettingsDialog.h b/src/frontend/qt_sdl/AudioSettingsDialog.h index ced9bae90c..ff3c138fa2 100644 --- a/src/frontend/qt_sdl/AudioSettingsDialog.h +++ b/src/frontend/qt_sdl/AudioSettingsDialog.h @@ -24,18 +24,19 @@ namespace Ui { class AudioSettingsDialog; } class AudioSettingsDialog; -class EmuThread; + +class EmuInstance; class AudioSettingsDialog : public QDialog { Q_OBJECT public: - explicit AudioSettingsDialog(QWidget* parent, bool emuActive, EmuThread* emuThread); + explicit AudioSettingsDialog(QWidget* parent); ~AudioSettingsDialog(); static AudioSettingsDialog* currentDlg; - static AudioSettingsDialog* openDlg(QWidget* parent, bool emuActive, EmuThread* emuThread) + static AudioSettingsDialog* openDlg(QWidget* parent) { if (currentDlg) { @@ -43,7 +44,7 @@ class AudioSettingsDialog : public QDialog return currentDlg; } - currentDlg = new AudioSettingsDialog(parent, emuActive, emuThread); + currentDlg = new AudioSettingsDialog(parent); currentDlg->show(); return currentDlg; } @@ -56,6 +57,7 @@ class AudioSettingsDialog : public QDialog void onConsoleReset(); signals: + void updateAudioVolume(int vol, bool dsisync); void updateAudioSettings(); private slots: @@ -70,14 +72,18 @@ private slots: void on_btnMicWavBrowse_clicked(); private: - EmuThread* emuThread; Ui::AudioSettingsDialog* ui; + EmuInstance* emuInstance; + int oldInterp; int oldBitDepth; int oldVolume; bool oldDSiSync; QButtonGroup* grpMicMode; + + int volume; + bool dsiSync; }; #endif // AUDIOSETTINGSDIALOG_H diff --git a/src/frontend/qt_sdl/CMakeLists.txt b/src/frontend/qt_sdl/CMakeLists.txt index 6096232882..f4e1f6e834 100644 --- a/src/frontend/qt_sdl/CMakeLists.txt +++ b/src/frontend/qt_sdl/CMakeLists.txt @@ -7,6 +7,9 @@ set(SOURCES_QT_SDL main_shaders.h Screen.cpp Window.cpp + EmuInstance.cpp + EmuInstanceAudio.cpp + EmuInstanceInput.cpp EmuThread.cpp CheatsDialog.cpp Config.cpp @@ -28,25 +31,22 @@ set(SOURCES_QT_SDL ROMInfoDialog.cpp RAMInfoDialog.cpp TitleManagerDialog.cpp - Input.cpp - LAN_PCap.cpp - LAN_Socket.cpp + PacketDispatcher.cpp + Net.cpp + Net_PCap.cpp + Net_Slirp.cpp LocalMP.cpp OSD_shaders.h font.h Platform.cpp QPathInput.h - ROMManager.cpp SaveManager.cpp CameraManager.cpp - AudioInOut.cpp ArchiveUtil.h ArchiveUtil.cpp - ../Util_Video.cpp - ../Util_Audio.cpp - ../FrontendUtil.h + ../ScreenLayout.cpp ../mic_blow.h ../glad/glad.c diff --git a/src/frontend/qt_sdl/CameraManager.cpp b/src/frontend/qt_sdl/CameraManager.cpp index cc575d2cd1..e576de8725 100644 --- a/src/frontend/qt_sdl/CameraManager.cpp +++ b/src/frontend/qt_sdl/CameraManager.cpp @@ -23,6 +23,8 @@ using namespace melonDS; +const char* kCamConfigPath[] = {"DSi.Camera0", "DSi.Camera1"}; + #if QT_VERSION >= 0x060000 CameraFrameDumper::CameraFrameDumper(QObject* parent) : QVideoSink(parent) @@ -116,10 +118,11 @@ QList CameraFrameDumper::supportedPixelFormats(QAbstra #endif -CameraManager::CameraManager(int num, int width, int height, bool yuv) : QObject() +CameraManager::CameraManager(int num, int width, int height, bool yuv) + : QObject(), + num(num), + config(Config::GetGlobalTable().GetTable(kCamConfigPath[num])) { - this->num = num; - startNum = 0; // QCamera needs to be controlled from the UI thread, hence this @@ -136,7 +139,7 @@ CameraManager::CameraManager(int num, int width, int height, bool yuv) : QObject tempFrameBuffer = new u32[fbsize]; inputType = -1; - xFlip = false; + xFlip = config.GetBool("XFlip"); init(); } @@ -157,9 +160,9 @@ void CameraManager::init() startNum = 0; - inputType = Config::Camera[num].InputType; - imagePath = QString::fromStdString(Config::Camera[num].ImagePath); - camDeviceName = QString::fromStdString(Config::Camera[num].CamDeviceName); + inputType = config.GetInt("InputType"); + imagePath = config.GetQString("ImagePath"); + camDeviceName = config.GetQString("DeviceName"); camDevice = nullptr; diff --git a/src/frontend/qt_sdl/CameraManager.h b/src/frontend/qt_sdl/CameraManager.h index 882b051a15..80adf511e8 100644 --- a/src/frontend/qt_sdl/CameraManager.h +++ b/src/frontend/qt_sdl/CameraManager.h @@ -33,6 +33,7 @@ #include #include "types.h" +#include "Config.h" class CameraManager; @@ -80,6 +81,8 @@ class CameraManager : public QObject CameraManager(int num, int width, int height, bool yuv); ~CameraManager(); + Config::Table& getConfig() { return config; } + void init(); void deInit(); @@ -106,6 +109,8 @@ private slots: private: int num; + Config::Table config; + int startNum; int inputType; diff --git a/src/frontend/qt_sdl/CameraSettingsDialog.cpp b/src/frontend/qt_sdl/CameraSettingsDialog.cpp index cf4041732b..cefb7e9393 100644 --- a/src/frontend/qt_sdl/CameraSettingsDialog.cpp +++ b/src/frontend/qt_sdl/CameraSettingsDialog.cpp @@ -22,6 +22,7 @@ #include #include "types.h" +#include "main.h" #include "CameraSettingsDialog.h" #include "ui_CameraSettingsDialog.h" @@ -30,8 +31,6 @@ using namespace melonDS; CameraSettingsDialog* CameraSettingsDialog::currentDlg = nullptr; -extern std::string EmuDirectory; - extern CameraManager* camManager[2]; @@ -71,9 +70,16 @@ CameraSettingsDialog::CameraSettingsDialog(QWidget* parent) : QDialog(parent), u ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose); + emuInstance = ((MainWindow*)parent)->getEmuInstance(); + for (int i = 0; i < 2; i++) { - oldCamSettings[i] = Config::Camera[i]; + auto& cfg = camManager[i]->getConfig(); + + oldCamSettings[i].InputType = cfg.GetInt("InputType"); + oldCamSettings[i].ImagePath = cfg.GetString("ImagePath"); + oldCamSettings[i].CamDeviceName = cfg.GetString("DeviceName"); + oldCamSettings[i].XFlip = cfg.GetBool("XFlip"); } ui->cbCameraSel->addItem("DSi outer camera"); @@ -161,7 +167,13 @@ void CameraSettingsDialog::on_CameraSettingsDialog_rejected() { camManager[i]->stop(); camManager[i]->deInit(); - Config::Camera[i] = oldCamSettings[i]; + + auto& cfg = camManager[i]->getConfig(); + cfg.SetInt("InputType", oldCamSettings[i].InputType); + cfg.SetString("ImagePath", oldCamSettings[i].ImagePath); + cfg.SetString("DeviceName", oldCamSettings[i].CamDeviceName); + cfg.SetBool("XFlip", oldCamSettings[i].XFlip); + camManager[i]->init(); } @@ -178,7 +190,7 @@ void CameraSettingsDialog::on_cbCameraSel_currentIndexChanged(int id) } currentId = id; - currentCfg = &Config::Camera[id]; + currentCfg = &camManager[id]->getConfig(); //currentCam = camManager[id]; currentCam = nullptr; populateCamControls(id); @@ -198,16 +210,16 @@ void CameraSettingsDialog::onChangeInputType(int type) currentCam->deInit(); } - currentCfg->InputType = type; + currentCfg->SetInt("InputType", type); ui->txtSrcImagePath->setEnabled(type == 1); ui->btnSrcImageBrowse->setEnabled(type == 1); ui->cbPhysicalCamera->setEnabled((type == 2) && (ui->cbPhysicalCamera->count()>0)); - currentCfg->ImagePath = ui->txtSrcImagePath->text().toStdString(); + currentCfg->SetQString("ImagePath", ui->txtSrcImagePath->text()); if (ui->cbPhysicalCamera->count() > 0) - currentCfg->CamDeviceName = ui->cbPhysicalCamera->currentData().toString().toStdString(); + currentCfg->SetQString("DeviceName", ui->cbPhysicalCamera->currentData().toString()); if (currentCam) { @@ -226,7 +238,7 @@ void CameraSettingsDialog::on_txtSrcImagePath_textChanged() currentCam->deInit(); } - currentCfg->ImagePath = ui->txtSrcImagePath->text().toStdString(); + currentCfg->SetQString("ImagePath", ui->txtSrcImagePath->text()); if (currentCam) { @@ -239,7 +251,7 @@ void CameraSettingsDialog::on_btnSrcImageBrowse_clicked() { QString file = QFileDialog::getOpenFileName(this, "Select image file...", - QString::fromStdString(EmuDirectory), + emuDirectory, "Image files (*.png *.jpg *.jpeg *.bmp);;Any file (*.*)"); if (file.isEmpty()) return; @@ -257,7 +269,7 @@ void CameraSettingsDialog::on_cbPhysicalCamera_currentIndexChanged(int id) currentCam->deInit(); } - currentCfg->CamDeviceName = ui->cbPhysicalCamera->itemData(id).toString().toStdString(); + currentCfg->SetQString("DeviceName", ui->cbPhysicalCamera->itemData(id).toString()); if (currentCam) { @@ -268,16 +280,16 @@ void CameraSettingsDialog::on_cbPhysicalCamera_currentIndexChanged(int id) void CameraSettingsDialog::populateCamControls(int id) { - Config::CameraConfig& cfg = Config::Camera[id]; + Config::Table& cfg = camManager[id]->getConfig(); - int type = cfg.InputType; + int type = cfg.GetInt("InputType"); if (type < 0 || type >= grpInputType->buttons().count()) type = 0; grpInputType->button(type)->setChecked(true); - ui->txtSrcImagePath->setText(QString::fromStdString(cfg.ImagePath)); + ui->txtSrcImagePath->setText(cfg.GetQString("ImagePath")); bool deviceset = false; - QString device = QString::fromStdString(cfg.CamDeviceName); + QString device = cfg.GetQString("DeviceName"); for (int i = 0; i < ui->cbPhysicalCamera->count(); i++) { QString itemdev = ui->cbPhysicalCamera->itemData(i).toString(); @@ -293,13 +305,14 @@ void CameraSettingsDialog::populateCamControls(int id) onChangeInputType(type); - ui->chkFlipPicture->setChecked(cfg.XFlip); + ui->chkFlipPicture->setChecked(cfg.GetBool("XFlip")); } void CameraSettingsDialog::on_chkFlipPicture_clicked() { if (!currentCfg) return; - currentCfg->XFlip = ui->chkFlipPicture->isChecked(); - if (currentCam) currentCam->setXFlip(currentCfg->XFlip); + bool xflip = ui->chkFlipPicture->isChecked(); + currentCfg->SetBool("XFlip", xflip); + if (currentCam) currentCam->setXFlip(xflip); } diff --git a/src/frontend/qt_sdl/CameraSettingsDialog.h b/src/frontend/qt_sdl/CameraSettingsDialog.h index a740193a53..784d94fee7 100644 --- a/src/frontend/qt_sdl/CameraSettingsDialog.h +++ b/src/frontend/qt_sdl/CameraSettingsDialog.h @@ -28,6 +28,8 @@ namespace Ui { class CameraSettingsDialog; } class CameraSettingsDialog; +class EmuInstance; + class CameraPreviewPanel : public QWidget { Q_OBJECT @@ -92,15 +94,22 @@ private slots: private: Ui::CameraSettingsDialog* ui; + EmuInstance* emuInstance; QButtonGroup* grpInputType; CameraPreviewPanel* previewPanel; int currentId; - Config::CameraConfig* currentCfg; + Config::Table* currentCfg; CameraManager* currentCam; - Config::CameraConfig oldCamSettings[2]; + struct + { + int InputType; // 0=blank 1=image 2=camera + std::string ImagePath; + std::string CamDeviceName; + bool XFlip; + } oldCamSettings[2]; void populateCamControls(int id); }; diff --git a/src/frontend/qt_sdl/CheatsDialog.cpp b/src/frontend/qt_sdl/CheatsDialog.cpp index df68723082..462eb051cd 100644 --- a/src/frontend/qt_sdl/CheatsDialog.cpp +++ b/src/frontend/qt_sdl/CheatsDialog.cpp @@ -24,7 +24,7 @@ #include "types.h" #include "Platform.h" #include "Config.h" -#include "ROMManager.h" +#include "EmuInstance.h" #include "CheatsDialog.h" #include "ui_CheatsDialog.h" @@ -35,15 +35,15 @@ using Platform::LogLevel; CheatsDialog* CheatsDialog::currentDlg = nullptr; -extern std::string EmuDirectory; - CheatsDialog::CheatsDialog(QWidget* parent) : QDialog(parent), ui(new Ui::CheatsDialog) { ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose); - codeFile = ROMManager::GetCheatFile(); + emuInstance = ((MainWindow*)parent)->getEmuInstance(); + + codeFile = emuInstance->getCheatFile(); QStandardItemModel* model = new QStandardItemModel(); ui->tvCodeList->setModel(model); diff --git a/src/frontend/qt_sdl/CheatsDialog.h b/src/frontend/qt_sdl/CheatsDialog.h index ab2ac30984..6a9455a781 100644 --- a/src/frontend/qt_sdl/CheatsDialog.h +++ b/src/frontend/qt_sdl/CheatsDialog.h @@ -33,6 +33,8 @@ Q_DECLARE_METATYPE(melonDS::ARCodeCatList::iterator) namespace Ui { class CheatsDialog; } class CheatsDialog; +class EmuInstance; + class ARCodeChecker : public QSyntaxHighlighter { Q_OBJECT @@ -87,6 +89,8 @@ private slots: private: Ui::CheatsDialog* ui; + EmuInstance* emuInstance; + melonDS::ARCodeFile* codeFile; ARCodeChecker* codeChecker; }; diff --git a/src/frontend/qt_sdl/Config.cpp b/src/frontend/qt_sdl/Config.cpp index d6d018259c..c261c7a303 100644 --- a/src/frontend/qt_sdl/Config.cpp +++ b/src/frontend/qt_sdl/Config.cpp @@ -20,386 +20,596 @@ #include #include #include +#include +#include +#include +#include "toml/toml.hpp" + #include "Platform.h" #include "Config.h" -#include "GPU.h" +#include "ScreenLayout.h" +#include "main.h" + +using namespace std::string_literals; namespace Config { using namespace melonDS; -int KeyMapping[12]; -int JoyMapping[12]; - -int HKKeyMapping[HK_MAX]; -int HKJoyMapping[HK_MAX]; -int JoystickID; +const char* kConfigFile = "melonDS.toml"; -int WindowWidth; -int WindowHeight; -bool WindowMaximized; +const char* kLegacyConfigFile = "melonDS.ini"; +const char* kLegacyUniqueConfigFile = "melonDS.%d.ini"; -int ScreenRotation; -int ScreenGap; -int ScreenLayout; -bool ScreenSwap; -int ScreenSizing; -bool IntegerScaling; -int ScreenAspectTop; -int ScreenAspectBot; -bool ScreenFilter; +toml::value RootTable; -bool ScreenUseGL; -bool ScreenVSync; -int ScreenVSyncInterval; +DefaultList DefaultInts = +{ + {"Instance*.Keyboard", -1}, + {"Instance*.Joystick", -1}, + {"Instance*.Window*.Width", 256}, + {"Instance*.Window*.Height", 384}, + {"Screen.VSyncInterval", 1}, + {"3D.Renderer", renderer3D_Software}, + {"3D.GL.ScaleFactor", 1}, + {"MaxFPS", 1000}, +#ifdef JIT_ENABLED + {"JIT.MaxBlockSize", 32}, +#endif + {"Instance*.Firmware.Language", 1}, + {"Instance*.Firmware.BirthdayMonth", 1}, + {"Instance*.Firmware.BirthdayDay", 1}, + {"MP.AudioMode", 1}, + {"MP.RecvTimeout", 25}, + {"Audio.Volume", 256}, + {"Mic.InputType", 1}, + {"Mouse.HideSeconds", 5}, + {"Instance*.DSi.Battery.Level", 0xF}, +#ifdef GDBSTUB_ENABLED + {"Instance*.Gdb.ARM7.Port", 3334}, + {"Instance*.Gdb.ARM9.Port", 3333}, +#endif +}; -int _3DRenderer; -bool Threaded3D; +RangeList IntRanges = +{ + {"Emu.ConsoleType", {0, 1}}, + {"3D.Renderer", {0, renderer3D_Max-1}}, + {"Screen.VSyncInterval", {1, 20}}, + {"3D.GL.ScaleFactor", {1, 16}}, + {"Audio.Interpolation", {0, 3}}, + {"Instance*.Audio.Volume", {0, 256}}, + {"Mic.InputType", {0, micInputType_MAX-1}}, + {"Instance*.Window*.ScreenRotation", {0, screenRot_MAX-1}}, + {"Instance*.Window*.ScreenGap", {0, 500}}, + {"Instance*.Window*.ScreenLayout", {0, screenLayout_MAX-1}}, + {"Instance*.Window*.ScreenSizing", {0, screenSizing_MAX-1}}, + {"Instance*.Window*.ScreenAspectTop", {0, AspectRatiosNum-1}}, + {"Instance*.Window*.ScreenAspectBot", {0, AspectRatiosNum-1}}, + {"MP.AudioMode", {0, 2}}, +}; -int GL_ScaleFactor; -bool GL_BetterPolygons; -bool GL_HiresCoordinates; +DefaultList DefaultBools = +{ + {"Screen.Filter", true}, + {"3D.Soft.Threaded", true}, + {"3D.GL.HiresCoordinates", true}, + {"LimitFPS", true}, + {"Window*.ShowOSD", true}, + {"Emu.DirectBoot", true}, +#ifdef JIT_ENABLED + {"JIT.BranchOptimisations", true}, + {"JIT.LiteralOptimisations", true}, +#ifndef __APPLE__ + {"JIT.FastMemory", true}, +#endif +#endif +}; -bool LimitFPS; -int MaxFPS; -bool AudioSync; -bool ShowOSD; +DefaultList DefaultStrings = +{ + {"DLDI.ImagePath", "dldi.bin"}, + {"DSi.SD.ImagePath", "dsisd.bin"}, + {"Instance*.Firmware.Username", "melonDS"} +}; -int ConsoleType; -bool DirectBoot; +LegacyEntry LegacyFile[] = +{ + {"Key_A", 0, "Keyboard.A", true}, + {"Key_B", 0, "Keyboard.B", true}, + {"Key_Select", 0, "Keyboard.Select", true}, + {"Key_Start", 0, "Keyboard.Start", true}, + {"Key_Right", 0, "Keyboard.Right", true}, + {"Key_Left", 0, "Keyboard.Left", true}, + {"Key_Up", 0, "Keyboard.Up", true}, + {"Key_Down", 0, "Keyboard.Down", true}, + {"Key_R", 0, "Keyboard.R", true}, + {"Key_L", 0, "Keyboard.L", true}, + {"Key_X", 0, "Keyboard.X", true}, + {"Key_Y", 0, "Keyboard.Y", true}, + + {"Joy_A", 0, "Joystick.A", true}, + {"Joy_B", 0, "Joystick.B", true}, + {"Joy_Select", 0, "Joystick.Select", true}, + {"Joy_Start", 0, "Joystick.Start", true}, + {"Joy_Right", 0, "Joystick.Right", true}, + {"Joy_Left", 0, "Joystick.Left", true}, + {"Joy_Up", 0, "Joystick.Up", true}, + {"Joy_Down", 0, "Joystick.Down", true}, + {"Joy_R", 0, "Joystick.R", true}, + {"Joy_L", 0, "Joystick.L", true}, + {"Joy_X", 0, "Joystick.X", true}, + {"Joy_Y", 0, "Joystick.Y", true}, + + {"HKKey_Lid", 0, "Keyboard.HK_Lid", true}, + {"HKKey_Mic", 0, "Keyboard.HK_Mic", true}, + {"HKKey_Pause", 0, "Keyboard.HK_Pause", true}, + {"HKKey_Reset", 0, "Keyboard.HK_Reset", true}, + {"HKKey_FastForward", 0, "Keyboard.HK_FastForward", true}, + {"HKKey_FastForwardToggle", 0, "Keyboard.HK_FastForwardToggle", true}, + {"HKKey_FullscreenToggle", 0, "Keyboard.HK_FullscreenToggle", true}, + {"HKKey_SwapScreens", 0, "Keyboard.HK_SwapScreens", true}, + {"HKKey_SwapScreenEmphasis", 0, "Keyboard.HK_SwapScreenEmphasis", true}, + {"HKKey_SolarSensorDecrease", 0, "Keyboard.HK_SolarSensorDecrease", true}, + {"HKKey_SolarSensorIncrease", 0, "Keyboard.HK_SolarSensorIncrease", true}, + {"HKKey_FrameStep", 0, "Keyboard.HK_FrameStep", true}, + {"HKKey_PowerButton", 0, "Keyboard.HK_PowerButton", true}, + {"HKKey_VolumeUp", 0, "Keyboard.HK_VolumeUp", true}, + {"HKKey_VolumeDown", 0, "Keyboard.HK_VolumeDown", true}, + + {"HKJoy_Lid", 0, "Joystick.HK_Lid", true}, + {"HKJoy_Mic", 0, "Joystick.HK_Mic", true}, + {"HKJoy_Pause", 0, "Joystick.HK_Pause", true}, + {"HKJoy_Reset", 0, "Joystick.HK_Reset", true}, + {"HKJoy_FastForward", 0, "Joystick.HK_FastForward", true}, + {"HKJoy_FastForwardToggle", 0, "Joystick.HK_FastForwardToggle", true}, + {"HKJoy_FullscreenToggle", 0, "Joystick.HK_FullscreenToggle", true}, + {"HKJoy_SwapScreens", 0, "Joystick.HK_SwapScreens", true}, + {"HKJoy_SwapScreenEmphasis", 0, "Joystick.HK_SwapScreenEmphasis", true}, + {"HKJoy_SolarSensorDecrease", 0, "Joystick.HK_SolarSensorDecrease", true}, + {"HKJoy_SolarSensorIncrease", 0, "Joystick.HK_SolarSensorIncrease", true}, + {"HKJoy_FrameStep", 0, "Joystick.HK_FrameStep", true}, + {"HKJoy_PowerButton", 0, "Joystick.HK_PowerButton", true}, + {"HKJoy_VolumeUp", 0, "Joystick.HK_VolumeUp", true}, + {"HKJoy_VolumeDown", 0, "Joystick.HK_VolumeDown", true}, + + {"JoystickID", 0, "JoystickID", true}, + + {"ScreenRotation", 0, "Window0.ScreenRotation", true}, + {"ScreenGap", 0, "Window0.ScreenGap", true}, + {"ScreenLayout", 0, "Window0.ScreenLayout", true}, + {"ScreenSwap", 1, "Window0.ScreenSwap", true}, + {"ScreenSizing", 0, "Window0.ScreenSizing", true}, + {"IntegerScaling", 1, "Window0.IntegerScaling", true}, + {"ScreenAspectTop",0, "Window0.ScreenAspectTop", true}, + {"ScreenAspectBot",0, "Window0.ScreenAspectBot", true}, + + {"ScreenFilter", 1, "Screen.Filter", false}, + {"ScreenUseGL", 1, "Screen.UseGL", false}, + {"ScreenVSync", 1, "Screen.VSync", false}, + {"ScreenVSyncInterval", 0, "Screen.VSyncInterval", false}, + + {"3DRenderer", 0, "3D.Renderer", false}, + {"Threaded3D", 1, "3D.Soft.Threaded", false}, + + {"GL_ScaleFactor", 0, "3D.GL.ScaleFactor", false}, + {"GL_BetterPolygons", 1, "3D.GL.BetterPolygons", false}, + {"GL_HiresCoordinates", 1, "3D.GL.HiresCoordinates", false}, + + {"LimitFPS", 1, "LimitFPS", false}, + {"MaxFPS", 0, "MaxFPS", false}, + {"AudioSync", 1, "AudioSync", false}, + {"ShowOSD", 1, "Window0.ShowOSD", true}, + + {"ConsoleType", 0, "Emu.ConsoleType", false}, + {"DirectBoot", 1, "Emu.DirectBoot", false}, #ifdef JIT_ENABLED -bool JIT_Enable = false; -int JIT_MaxBlockSize = 32; -bool JIT_BranchOptimisations = true; -bool JIT_LiteralOptimisations = true; -bool JIT_FastMemory = true; + {"JIT_Enable", 1, "JIT.Enable", false}, + {"JIT_MaxBlockSize", 0, "JIT.MaxBlockSize", false}, + {"JIT_BranchOptimisations", 1, "JIT.BranchOptimisations", false}, + {"JIT_LiteralOptimisations", 1, "JIT.LiteralOptimisations", false}, + {"JIT_FastMemory", 1, "JIT.FastMemory", false}, #endif -bool ExternalBIOSEnable; + {"ExternalBIOSEnable", 1, "Emu.ExternalBIOSEnable", false}, + + {"BIOS9Path", 2, "DS.BIOS9Path", false}, + {"BIOS7Path", 2, "DS.BIOS7Path", false}, + {"FirmwarePath", 2, "DS.FirmwarePath", false}, + + {"DSiBIOS9Path", 2, "DSi.BIOS9Path", false}, + {"DSiBIOS7Path", 2, "DSi.BIOS7Path", false}, + {"DSiFirmwarePath", 2, "DSi.FirmwarePath", false}, + {"DSiNANDPath", 2, "DSi.NANDPath", false}, + + {"DLDIEnable", 1, "DLDI.Enable", false}, + {"DLDISDPath", 2, "DLDI.ImagePath", false}, + {"DLDISize", 0, "DLDI.ImageSize", false}, + {"DLDIReadOnly", 1, "DLDI.ReadOnly", false}, + {"DLDIFolderSync", 1, "DLDI.FolderSync", false}, + {"DLDIFolderPath", 2, "DLDI.FolderPath", false}, + + {"DSiSDEnable", 1, "DSi.SD.Enable", false}, + {"DSiSDPath", 2, "DSi.SD.ImagePath", false}, + {"DSiSDSize", 0, "DSi.SD.ImageSize", false}, + {"DSiSDReadOnly", 1, "DSi.SD.ReadOnly", false}, + {"DSiSDFolderSync", 1, "DSi.SD.FolderSync", false}, + {"DSiSDFolderPath", 2, "DSi.SD.FolderPath", false}, + + {"FirmwareOverrideSettings", 1, "Firmware.OverrideSettings", true}, + {"FirmwareUsername", 2, "Firmware.Username", true}, + {"FirmwareLanguage", 0, "Firmware.Language", true}, + {"FirmwareBirthdayMonth", 0, "Firmware.BirthdayMonth", true}, + {"FirmwareBirthdayDay", 0, "Firmware.BirthdayDay", true}, + {"FirmwareFavouriteColour", 0, "Firmware.FavouriteColour", true}, + {"FirmwareMessage", 2, "Firmware.Message", true}, + {"FirmwareMAC", 2, "Firmware.MAC", true}, + + {"MPAudioMode", 0, "MP.AudioMode", false}, + {"MPRecvTimeout", 0, "MP.RecvTimeout", false}, + + {"LANDevice", 2, "LAN.Device", false}, + {"DirectLAN", 1, "LAN.DirectMode", false}, + + {"SavStaRelocSRAM", 1, "Savestate.RelocSRAM", false}, + + {"AudioInterp", 0, "Audio.Interpolation", false}, + {"AudioBitDepth", 0, "Audio.BitDepth", false}, + {"AudioVolume", 0, "Audio.Volume", true}, + {"DSiVolumeSync", 1, "Audio.DSiVolumeSync", true}, + {"MicInputType", 0, "Mic.InputType", false}, + {"MicDevice", 2, "Mic.Device", false}, + {"MicWavPath", 2, "Mic.WavPath", false}, + + {"LastROMFolder", 2, "LastROMFolder", false}, + {"LastBIOSFolder", 2, "LastBIOSFolder", false}, + + {"RecentROM_0", 4, "RecentROM[0]", false}, + {"RecentROM_1", 4, "RecentROM[1]", false}, + {"RecentROM_2", 4, "RecentROM[2]", false}, + {"RecentROM_3", 4, "RecentROM[3]", false}, + {"RecentROM_4", 4, "RecentROM[4]", false}, + {"RecentROM_5", 4, "RecentROM[5]", false}, + {"RecentROM_6", 4, "RecentROM[6]", false}, + {"RecentROM_7", 4, "RecentROM[7]", false}, + {"RecentROM_8", 4, "RecentROM[8]", false}, + {"RecentROM_9", 4, "RecentROM[9]", false}, + + {"SaveFilePath", 2, "SaveFilePath", true}, + {"SavestatePath", 2, "SavestatePath", true}, + {"CheatFilePath", 2, "CheatFilePath", true}, + + {"EnableCheats", 1, "EnableCheats", true}, + + {"MouseHide", 1, "Mouse.Hide", false}, + {"MouseHideSeconds", 0, "Mouse.HideSeconds", false}, + {"PauseLostFocus", 1, "PauseLostFocus", false}, + {"UITheme", 2, "UITheme", false}, + + {"RTCOffset", 3, "RTC.Offset", true}, + + {"DSBatteryLevelOkay", 1, "DS.Battery.LevelOkay", true}, + {"DSiBatteryLevel", 0, "DSi.Battery.Level", true}, + {"DSiBatteryCharging", 1, "DSi.Battery.Charging", true}, + + {"DSiFullBIOSBoot", 1, "DSi.FullBIOSBoot", true}, + +#ifdef GDBSTUB_ENABLED + {"GdbEnabled", 1, "Gdb.Enabled", false}, + {"GdbPortARM7", 0, "Gdb.ARM7.Port", true}, + {"GdbPortARM9", 0, "Gdb.ARM9.Port", true}, + {"GdbARM7BreakOnStartup", 1, "Gdb.ARM7.BreakOnStartup", true}, + {"GdbARM9BreakOnStartup", 1, "Gdb.ARM9.BreakOnStartup", true}, +#endif -std::string BIOS9Path; -std::string BIOS7Path; -std::string FirmwarePath; + {"Camera0_InputType", 0, "DSi.Camera0.InputType", false}, + {"Camera0_ImagePath", 2, "DSi.Camera0.ImagePath", false}, + {"Camera0_CamDeviceName", 2, "DSi.Camera0.DeviceName", false}, + {"Camera0_XFlip", 1, "DSi.Camera0.XFlip", false}, + {"Camera1_InputType", 0, "DSi.Camera1.InputType", false}, + {"Camera1_ImagePath", 2, "DSi.Camera1.ImagePath", false}, + {"Camera1_CamDeviceName", 2, "DSi.Camera1.DeviceName", false}, + {"Camera1_XFlip", 1, "DSi.Camera1.XFlip", false}, -std::string DSiBIOS9Path; -std::string DSiBIOS7Path; -std::string DSiFirmwarePath; -std::string DSiNANDPath; + {"", -1, "", false} +}; -bool DLDIEnable; -std::string DLDISDPath; -int DLDISize; -bool DLDIReadOnly; -bool DLDIFolderSync; -std::string DLDIFolderPath; -bool DSiSDEnable; -std::string DSiSDPath; -int DSiSDSize; -bool DSiSDReadOnly; -bool DSiSDFolderSync; -std::string DSiSDFolderPath; +Array::Array(toml::value& data) : Data(data) +{ +} -bool FirmwareOverrideSettings; -std::string FirmwareUsername; -int FirmwareLanguage; -int FirmwareBirthdayMonth; -int FirmwareBirthdayDay; -int FirmwareFavouriteColour; -std::string FirmwareMessage; -std::string FirmwareMAC; -std::string WifiSettingsPath = "wfcsettings.bin"; // Should this be configurable? +size_t Array::Size() +{ + return Data.size(); +} -int MPAudioMode; -int MPRecvTimeout; +void Array::Clear() +{ + toml::array newarray; + Data = newarray; +} -std::string LANDevice; -bool DirectLAN; +Array Array::GetArray(const int id) +{ + while (Data.size() < id+1) + Data.push_back(toml::array()); -bool SavestateRelocSRAM; + toml::value& arr = Data[id]; + if (!arr.is_array()) + arr = toml::array(); -int AudioInterp; -int AudioBitDepth; -int AudioVolume; -bool DSiVolumeSync; -int MicInputType; -std::string MicDevice; -std::string MicWavPath; + return Array(arr); +} -std::string LastROMFolder; -std::string LastBIOSFolder; +int Array::GetInt(const int id) +{ + while (Data.size() < id+1) + Data.push_back(0); -std::string RecentROMList[10]; + toml::value& tval = Data[id]; + if (!tval.is_integer()) + tval = 0; -std::string SaveFilePath; -std::string SavestatePath; -std::string CheatFilePath; + return (int)tval.as_integer(); +} -bool EnableCheats; +int64_t Array::GetInt64(const int id) +{ + while (Data.size() < id+1) + Data.push_back(0); -bool MouseHide; -int MouseHideSeconds; + toml::value& tval = Data[id]; + if (!tval.is_integer()) + tval = 0; -bool PauseLostFocus; -std::string UITheme; + return tval.as_integer(); +} -int64_t RTCOffset; +bool Array::GetBool(const int id) +{ + while (Data.size() < id+1) + Data.push_back(false); -bool DSBatteryLevelOkay; -int DSiBatteryLevel; -bool DSiBatteryCharging; + toml::value& tval = Data[id]; + if (!tval.is_boolean()) + tval = false; -bool DSiFullBIOSBoot; + return tval.as_boolean(); +} -#ifdef GDBSTUB_ENABLED -bool GdbEnabled; -int GdbPortARM7; -int GdbPortARM9; -bool GdbARM7BreakOnStartup; -bool GdbARM9BreakOnStartup; -#endif +std::string Array::GetString(const int id) +{ + while (Data.size() < id+1) + Data.push_back(""); -CameraConfig Camera[2]; - - -const char* kConfigFile = "melonDS.ini"; -const char* kUniqueConfigFile = "melonDS.%d.ini"; - -ConfigEntry ConfigFile[] = -{ - {"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_SwapScreenEmphasis", 0, &HKKeyMapping[HK_SwapScreenEmphasis], -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}, - {"HKKey_PowerButton", 0, &HKKeyMapping[HK_PowerButton], -1, true}, - {"HKKey_VolumeUp", 0, &HKKeyMapping[HK_VolumeUp], -1, true}, - {"HKKey_VolumeDown", 0, &HKKeyMapping[HK_VolumeDown], -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_SwapScreenEmphasis", 0, &HKJoyMapping[HK_SwapScreenEmphasis], -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}, - {"HKJoy_PowerButton", 0, &HKJoyMapping[HK_PowerButton], -1, true}, - {"HKJoy_VolumeUp", 0, &HKJoyMapping[HK_VolumeUp], -1, true}, - {"HKJoy_VolumeDown", 0, &HKJoyMapping[HK_VolumeDown], -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, renderer3D_Software, false}, - {"Threaded3D", 1, &Threaded3D, true, false}, - - {"GL_ScaleFactor", 0, &GL_ScaleFactor, 1, false}, - {"GL_BetterPolygons", 1, &GL_BetterPolygons, false, false}, - {"GL_HiresCoordinates", 1, &GL_HiresCoordinates, true, false}, - - {"LimitFPS", 1, &LimitFPS, true, false}, - {"MaxFPS", 0, &MaxFPS, 1000, false}, - {"AudioSync", 1, &AudioSync, false}, - {"ShowOSD", 1, &ShowOSD, true, false}, - - {"ConsoleType", 0, &ConsoleType, 0, false}, - {"DirectBoot", 1, &DirectBoot, true, false}, + toml::value& tval = Data[id]; + if (!tval.is_string()) + tval = ""; -#ifdef JIT_ENABLED - {"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, false}, - #else - {"JIT_FastMemory", 1, &JIT_FastMemory, true, false}, - #endif -#endif + return tval.as_string(); +} - {"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}, - {"AudioBitDepth", 0, &AudioBitDepth, 0, false}, - {"AudioVolume", 0, &AudioVolume, 256, true}, - {"DSiVolumeSync", 1, &DSiVolumeSync, false, true}, - {"MicInputType", 0, &MicInputType, 1, false}, - {"MicDevice", 2, &MicDevice, (std::string)"", false}, - {"MicWavPath", 2, &MicWavPath, (std::string)"", false}, - - {"LastROMFolder", 2, &LastROMFolder, (std::string)"", true}, - {"LastBIOSFolder", 2, &LastBIOSFolder, (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}, - {"UITheme", 2, &UITheme, (std::string)"", false}, - - {"RTCOffset", 3, &RTCOffset, (int64_t)0, true}, - - {"DSBatteryLevelOkay", 1, &DSBatteryLevelOkay, true, true}, - {"DSiBatteryLevel", 0, &DSiBatteryLevel, 0xF, true}, - {"DSiBatteryCharging", 1, &DSiBatteryCharging, true, true}, - - {"DSiFullBIOSBoot", 1, &DSiFullBIOSBoot, false, true}, +void Array::SetInt(const int id, int val) +{ + while (Data.size() < id+1) + Data.push_back(0); -#ifdef GDBSTUB_ENABLED - {"GdbEnabled", 1, &GdbEnabled, false, false}, - {"GdbPortARM7", 0, &GdbPortARM7, 3334, true}, - {"GdbPortARM9", 0, &GdbPortARM9, 3333, true}, - {"GdbARM7BreakOnStartup", 1, &GdbARM7BreakOnStartup, false, true}, - {"GdbARM9BreakOnStartup", 1, &GdbARM9BreakOnStartup, false, true}, -#endif + toml::value& tval = Data[id]; + tval = val; +} - // TODO!! - // we need a more elegant way to deal with this - {"Camera0_InputType", 0, &Camera[0].InputType, 0, false}, - {"Camera0_ImagePath", 2, &Camera[0].ImagePath, (std::string)"", false}, - {"Camera0_CamDeviceName", 2, &Camera[0].CamDeviceName, (std::string)"", false}, - {"Camera0_XFlip", 1, &Camera[0].XFlip, false, false}, - {"Camera1_InputType", 0, &Camera[1].InputType, 0, false}, - {"Camera1_ImagePath", 2, &Camera[1].ImagePath, (std::string)"", false}, - {"Camera1_CamDeviceName", 2, &Camera[1].CamDeviceName, (std::string)"", false}, - {"Camera1_XFlip", 1, &Camera[1].XFlip, false, false}, - - {"", -1, nullptr, 0, false} -}; +void Array::SetInt64(const int id, int64_t val) +{ + while (Data.size() < id+1) + Data.push_back(0); + + toml::value& tval = Data[id]; + tval = val; +} + +void Array::SetBool(const int id, bool val) +{ + while (Data.size() < id+1) + Data.push_back(false); + + toml::value& tval = Data[id]; + tval = val; +} + +void Array::SetString(const int id, const std::string& val) +{ + while (Data.size() < id+1) + Data.push_back(""); + + toml::value& tval = Data[id]; + tval = val; +} + + +/*Table::Table()// : Data(toml::value()) +{ + Data = toml::value(); + PathPrefix = ""; +}*/ + +Table::Table(toml::value& data, const std::string& path) : Data(data) +{ + if (path.empty()) + PathPrefix = ""; + else + PathPrefix = path + "."; +} + +Table& Table::operator=(const Table& b) +{ + Data = b.Data; + PathPrefix = b.PathPrefix; + + return *this; +} + +Array Table::GetArray(const std::string& path) +{ + toml::value& arr = ResolvePath(path); + if (!arr.is_array()) + arr = toml::array(); + + return Array(arr); +} + +Table Table::GetTable(const std::string& path, const std::string& defpath) +{ + toml::value& tbl = ResolvePath(path); + if (!tbl.is_table()) + { + toml::value defval = toml::table(); + if (!defpath.empty()) + defval = ResolvePath(defpath); + + tbl = defval; + } + + return Table(tbl, PathPrefix + path); +} + +int Table::GetInt(const std::string& path) +{ + toml::value& tval = ResolvePath(path); + if (!tval.is_integer()) + tval = FindDefault(path, 0, DefaultInts); + + int ret = (int)tval.as_integer(); + + std::regex rng_re("\\d+"); + std::string rngkey = std::regex_replace(PathPrefix+path, rng_re, "*"); + if (IntRanges.count(rngkey) != 0) + { + auto& range = IntRanges[rngkey]; + ret = std::clamp(ret, std::get<0>(range), std::get<1>(range)); + } + + return ret; +} + +int64_t Table::GetInt64(const std::string& path) +{ + toml::value& tval = ResolvePath(path); + if (!tval.is_integer()) + tval = 0; + + return tval.as_integer(); +} + +bool Table::GetBool(const std::string& path) +{ + toml::value& tval = ResolvePath(path); + if (!tval.is_boolean()) + tval = FindDefault(path, false, DefaultBools); + + return tval.as_boolean(); +} + +std::string Table::GetString(const std::string& path) +{ + toml::value& tval = ResolvePath(path); + if (!tval.is_string()) + tval = FindDefault(path, ""s, DefaultStrings); + + return tval.as_string(); +} + +void Table::SetInt(const std::string& path, int val) +{ + std::regex rng_re("\\d+"); + std::string rngkey = std::regex_replace(PathPrefix+path, rng_re, "*"); + if (IntRanges.count(rngkey) != 0) + { + auto& range = IntRanges[rngkey]; + val = std::clamp(val, std::get<0>(range), std::get<1>(range)); + } + + toml::value& tval = ResolvePath(path); + tval = val; +} + +void Table::SetInt64(const std::string& path, int64_t val) +{ + toml::value& tval = ResolvePath(path); + tval = val; +} + +void Table::SetBool(const std::string& path, bool val) +{ + toml::value& tval = ResolvePath(path); + tval = val; +} + +void Table::SetString(const std::string& path, const std::string& val) +{ + toml::value& tval = ResolvePath(path); + tval = val; +} + +toml::value& Table::ResolvePath(const std::string& path) +{ + toml::value* ret = &Data; + std::string tmp = path; + + size_t sep; + while ((sep = tmp.find('.')) != std::string::npos) + { + ret = &(*ret)[tmp.substr(0, sep)]; + tmp = tmp.substr(sep+1); + } + + return (*ret)[tmp]; +} + +template T Table::FindDefault(const std::string& path, T def, DefaultList list) +{ + std::regex def_re("\\d+"); + std::string defkey = std::regex_replace(PathPrefix+path, def_re, "*"); + + T ret = def; + while (list.count(defkey) == 0) + { + if (defkey.empty()) break; + size_t sep = defkey.rfind('.'); + if (sep == std::string::npos) break; + defkey = defkey.substr(0, sep); + } + if (list.count(defkey) != 0) + ret = list[defkey]; + + return ret; +} -bool LoadFile(int inst, int actualinst) +bool LoadLegacyFile(int inst) { Platform::FileHandle* f; if (inst > 0) { char name[100] = {0}; - snprintf(name, 99, kUniqueConfigFile, inst+1); + snprintf(name, 99, kLegacyUniqueConfigFile, inst+1); f = Platform::OpenLocalFile(name, Platform::FileMode::ReadText); - - if (!Platform::CheckLocalFileWritable(name)) return false; } else { - f = Platform::OpenLocalFile(kConfigFile, Platform::FileMode::ReadText); - - if (actualinst == 0 && !Platform::CheckLocalFileWritable(kConfigFile)) return false; + f = Platform::OpenLocalFile(kLegacyConfigFile, Platform::FileMode::ReadText); } if (!f) return true; +printf("PARSING LEGACY INI %d\n", inst); + toml::value* root;// = GetLocalTable(inst); + if (inst == -1) + root = &RootTable; + else + root = &RootTable["Instance" + std::to_string(inst)]; char linebuf[1024]; char entryname[32]; @@ -413,19 +623,58 @@ bool LoadFile(int inst, int actualinst) entryname[31] = '\0'; if (ret < 2) continue; - for (ConfigEntry* entry = &ConfigFile[0]; entry->Value; entry++) + for (LegacyEntry* entry = &LegacyFile[0]; entry->Type != -1; entry++) { if (!strncmp(entry->Name, entryname, 32)) { - if ((inst > 0) && (!entry->InstanceUnique)) + if (!(entry->InstanceUnique ^ (inst == -1))) break; +printf("entry: %s -> %s, %d\n", entry->Name, entry->TOMLPath, entry->InstanceUnique); + std::string path = entry->TOMLPath; + toml::value* table = root; + size_t sep; + while ((sep = path.find('.')) != std::string::npos) + {printf("%s->", path.substr(0,sep).c_str()); + table = &(*table)[path.substr(0, sep)]; + path = path.substr(sep+1); + } + printf("%s\n", path.c_str()); + + int arrayid = -1; + if (path[path.size()-1] == ']') + { + size_t tmp = path.rfind('['); + arrayid = std::stoi(path.substr(tmp+1, path.size()-tmp-2)); + path = path.substr(0, tmp); + } +printf("path %s id %d\n", path.c_str(), arrayid); + toml::value& val = (*table)[path]; switch (entry->Type) { - case 0: *(int*)entry->Value = strtol(entryval, NULL, 10); break; - case 1: *(bool*)entry->Value = strtol(entryval, NULL, 10) ? true:false; break; - case 2: *(std::string*)entry->Value = entryval; break; - case 3: *(int64_t*)entry->Value = strtoll(entryval, NULL, 10); break; + case 0: + val = strtol(entryval, nullptr, 10); + break; + + case 1: + val = !!strtol(entryval, nullptr, 10); + break; + + case 2: + val = entryval; + break; + + case 3: + val = strtoll(entryval, nullptr, 10); + break; + + case 4: + if (!val.is_array()) val = toml::array(); + while (val.size() < arrayid+1) + val.push_back(""); + val[arrayid] = entryval; + //val.push_back(entryval); + break; } break; @@ -437,60 +686,74 @@ bool LoadFile(int inst, int actualinst) return true; } +bool LoadLegacy() +{ + for (int i = -1; i < 16; i++) + LoadLegacyFile(i); + + return true; +} + bool Load() { + auto cfgpath = Platform::GetLocalFilePath(kConfigFile); + + if (!Platform::CheckFileWritable(cfgpath)) + return false; + + RootTable = toml::value(); + + if (!Platform::FileExists(cfgpath)) + return LoadLegacy(); - for (ConfigEntry* entry = &ConfigFile[0]; entry->Value; entry++) + try { - switch (entry->Type) - { - case 0: *(int*)entry->Value = std::get(entry->Default); break; - case 1: *(bool*)entry->Value = std::get(entry->Default); break; - case 2: *(std::string*)entry->Value = std::get(entry->Default); break; - case 3: *(int64_t*)entry->Value = std::get(entry->Default); break; - } + RootTable = toml::parse(cfgpath); + } + catch (toml::syntax_error& err) + { + //RootTable = toml::table(); } - - int inst = Platform::InstanceID(); - bool ret = LoadFile(0, inst); - if (inst > 0) - ret = LoadFile(inst, inst); + // - return ret; + return true; } void Save() { - int inst = Platform::InstanceID(); - - Platform::FileHandle* f; - if (inst > 0) - { - char name[100] = {0}; - snprintf(name, 99, kUniqueConfigFile, inst+1); - f = Platform::OpenLocalFile(name, Platform::FileMode::WriteText); - } - else - f = Platform::OpenLocalFile(kConfigFile, Platform::FileMode::WriteText); + auto cfgpath = Platform::GetLocalFilePath(kConfigFile); +printf("save\n"); + if (!Platform::CheckFileWritable(cfgpath)) + return; + printf("zirz\n"); + /*RootTable["test"] = 4444; + RootTable["teste.derp"] = 5555; + RootTable["testa"]["fazil"] = 6666;*/ + //std::string derp = "sfsdf"; + //toml::serializer vorp(RootTable); + //toml::serializer zarp; + + //std::cout << RootTable; + printf("blarg\n"); + std::ofstream file; + file.open(cfgpath, std::ofstream::out | std::ofstream::trunc); + file << RootTable; + file.close(); +} - if (!f) return; - for (ConfigEntry* entry = &ConfigFile[0]; entry->Value; entry++) - { - if ((inst > 0) && (!entry->InstanceUnique)) - continue; +Table GetLocalTable(int instance) +{ + if (instance == -1) + return Table(RootTable, ""); - switch (entry->Type) - { - case 0: Platform::FileWriteFormatted(f, "%s=%d\n", entry->Name, *(int*)entry->Value); break; - case 1: Platform::FileWriteFormatted(f, "%s=%d\n", entry->Name, *(bool*)entry->Value ? 1:0); break; - case 2: Platform::FileWriteFormatted(f, "%s=%s\n", entry->Name, (*(std::string*)entry->Value).c_str()); break; - case 3: Platform::FileWriteFormatted(f, "%s=%" PRId64 "\n", entry->Name, *(int64_t*)entry->Value); break; - } - } + std::string key = "Instance" + std::to_string(instance); + toml::value& tbl = RootTable[key]; + if (tbl.is_uninitialized()) + RootTable[key] = RootTable["Instance0"]; - CloseFile(f); + return Table(tbl, key); } } diff --git a/src/frontend/qt_sdl/Config.h b/src/frontend/qt_sdl/Config.h index 38a1c34c75..ffe8cc3cb6 100644 --- a/src/frontend/qt_sdl/Config.h +++ b/src/frontend/qt_sdl/Config.h @@ -16,208 +16,127 @@ with melonDS. If not, see http://www.gnu.org/licenses/. */ -#ifndef PLATFORMCONFIG_H -#define PLATFORMCONFIG_H +#ifndef CONFIG_H +#define CONFIG_H #include #include +#include +#include +#include -enum +#ifndef TOML11_VALUE_HPP +namespace toml { - HK_Lid = 0, - HK_Mic, - HK_Pause, - HK_Reset, - HK_FastForward, - HK_FastForwardToggle, - HK_FullscreenToggle, - HK_SwapScreens, - HK_SwapScreenEmphasis, - HK_SolarSensorDecrease, - HK_SolarSensorIncrease, - HK_FrameStep, - HK_PowerButton, - HK_VolumeUp, - HK_VolumeDown, - HK_MAX -}; - -enum -{ - micInputType_Silence, - micInputType_External, - micInputType_Noise, - micInputType_Wav, - micInputType_MAX, -}; - -enum -{ - renderer3D_Software = 0, -#ifdef OGLRENDERER_ENABLED - renderer3D_OpenGL, - renderer3D_OpenGLCompute, +class value; +} #endif - renderer3D_Max, -}; namespace Config { -struct ConfigEntry +struct LegacyEntry { char Name[32]; int Type; // 0=int 1=bool 2=string 3=64bit int - void* Value; // pointer to the value variable - std::variant Default; + char TOMLPath[64]; bool InstanceUnique; // whether the setting can exist individually for each instance in multiplayer }; -struct CameraConfig -{ - int InputType; // 0=blank 1=image 2=camera - std::string ImagePath; - std::string CamDeviceName; - bool XFlip; -}; - - -extern int KeyMapping[12]; -extern int JoyMapping[12]; - -extern int HKKeyMapping[HK_MAX]; -extern int HKJoyMapping[HK_MAX]; +template +using DefaultList = std::unordered_map; -extern int JoystickID; +using RangeList = std::unordered_map>; -extern int WindowWidth; -extern int WindowHeight; -extern bool WindowMaximized; +class Table; -extern int ScreenRotation; -extern int ScreenGap; -extern int ScreenLayout; -extern bool ScreenSwap; -extern int ScreenSizing; -extern int ScreenAspectTop; -extern int ScreenAspectBot; -extern bool IntegerScaling; -extern bool ScreenFilter; - -extern bool ScreenUseGL; -extern bool ScreenVSync; -extern int ScreenVSyncInterval; - -extern int _3DRenderer; -extern bool Threaded3D; - -extern int GL_ScaleFactor; -extern bool GL_BetterPolygons; -extern bool GL_HiresCoordinates; - -extern bool LimitFPS; -extern int MaxFPS; -extern bool AudioSync; -extern bool ShowOSD; - -extern int ConsoleType; -extern bool DirectBoot; - -#ifdef JIT_ENABLED -extern bool JIT_Enable; -extern int JIT_MaxBlockSize; -extern bool JIT_BranchOptimisations; -extern bool JIT_LiteralOptimisations; -extern bool JIT_FastMemory; -#endif - -extern bool ExternalBIOSEnable; +class Array +{ +public: + Array(toml::value& data); + ~Array() {} -extern std::string BIOS9Path; -extern std::string BIOS7Path; -extern std::string FirmwarePath; + size_t Size(); -extern std::string DSiBIOS9Path; -extern std::string DSiBIOS7Path; -extern std::string DSiFirmwarePath; -extern std::string DSiNANDPath; + void Clear(); -extern bool DLDIEnable; -extern std::string DLDISDPath; -extern int DLDISize; -extern bool DLDIReadOnly; -extern bool DLDIFolderSync; -extern std::string DLDIFolderPath; + Array GetArray(const int id); -extern bool DSiSDEnable; -extern std::string DSiSDPath; -extern int DSiSDSize; -extern bool DSiSDReadOnly; -extern bool DSiSDFolderSync; -extern std::string DSiSDFolderPath; + int GetInt(const int id); + int64_t GetInt64(const int id); + bool GetBool(const int id); + std::string GetString(const int id); -extern bool FirmwareOverrideSettings; -extern std::string FirmwareUsername; -extern int FirmwareLanguage; -extern int FirmwareBirthdayMonth; -extern int FirmwareBirthdayDay; -extern int FirmwareFavouriteColour; -extern std::string FirmwareMessage; -extern std::string FirmwareMAC; -extern std::string WifiSettingsPath; + void SetInt(const int id, int val); + void SetInt64(const int id, int64_t val); + void SetBool(const int id, bool val); + void SetString(const int id, const std::string& val); -extern int MPAudioMode; -extern int MPRecvTimeout; + // convenience -extern std::string LANDevice; -extern bool DirectLAN; + QString GetQString(const int id) + { + return QString::fromStdString(GetString(id)); + } -extern bool SavestateRelocSRAM; + void SetQString(const int id, const QString& val) + { + return SetString(id, val.toStdString()); + } -extern int AudioInterp; -extern int AudioBitDepth; -extern int AudioVolume; -extern bool DSiVolumeSync; -extern int MicInputType; -extern std::string MicDevice; -extern std::string MicWavPath; +private: + toml::value& Data; +}; -extern std::string LastROMFolder; -extern std::string LastBIOSFolder; +class Table +{ +public: + //Table(); + Table(toml::value& data, const std::string& path); + ~Table() {} -extern std::string RecentROMList[10]; + Table& operator=(const Table& b); -extern std::string SaveFilePath; -extern std::string SavestatePath; -extern std::string CheatFilePath; + Array GetArray(const std::string& path); + Table GetTable(const std::string& path, const std::string& defpath = ""); -extern bool EnableCheats; + int GetInt(const std::string& path); + int64_t GetInt64(const std::string& path); + bool GetBool(const std::string& path); + std::string GetString(const std::string& path); -extern bool MouseHide; -extern int MouseHideSeconds; -extern bool PauseLostFocus; -extern std::string UITheme; + void SetInt(const std::string& path, int val); + void SetInt64(const std::string& path, int64_t val); + void SetBool(const std::string& path, bool val); + void SetString(const std::string& path, const std::string& val); -extern int64_t RTCOffset; + // convenience -extern bool DSBatteryLevelOkay; -extern int DSiBatteryLevel; -extern bool DSiBatteryCharging; + QString GetQString(const std::string& path) + { + return QString::fromStdString(GetString(path)); + } -extern bool DSiFullBIOSBoot; + void SetQString(const std::string& path, const QString& val) + { + return SetString(path, val.toStdString()); + } -extern CameraConfig Camera[2]; +private: + toml::value& Data; + std::string PathPrefix; -extern bool GdbEnabled; -extern int GdbPortARM7; -extern int GdbPortARM9; -extern bool GdbARM7BreakOnStartup; -extern bool GdbARM9BreakOnStartup; + toml::value& ResolvePath(const std::string& path); + template T FindDefault(const std::string& path, T def, DefaultList list); +}; bool Load(); void Save(); +Table GetLocalTable(int instance); +inline Table GetGlobalTable() { return GetLocalTable(-1); } + } -#endif // PLATFORMCONFIG_H +#endif // CONFIG_H diff --git a/src/frontend/qt_sdl/DateTimeDialog.cpp b/src/frontend/qt_sdl/DateTimeDialog.cpp index 88ae69421a..45ec211e87 100644 --- a/src/frontend/qt_sdl/DateTimeDialog.cpp +++ b/src/frontend/qt_sdl/DateTimeDialog.cpp @@ -20,6 +20,7 @@ #include "types.h" #include "Config.h" +#include "main.h" #include "DateTimeDialog.h" #include "ui_DateTimeDialog.h" @@ -32,8 +33,11 @@ DateTimeDialog::DateTimeDialog(QWidget* parent) : QDialog(parent), ui(new Ui::Da ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose); + emuInstance = ((MainWindow*)parent)->getEmuInstance(); + + auto& cfg = emuInstance->getLocalConfig(); QDateTime now = QDateTime::currentDateTime(); - customTime = now.addSecs(Config::RTCOffset); + customTime = now.addSecs(cfg.GetInt64("RTC.Offset")); ui->chkChangeTime->setChecked(false); ui->chkResetTime->setChecked(false); @@ -59,13 +63,15 @@ void DateTimeDialog::done(int r) { if (r == QDialog::Accepted) { + auto& cfg = emuInstance->getLocalConfig(); + if (ui->chkChangeTime->isChecked()) { QDateTime now = QDateTime::currentDateTime(); - Config::RTCOffset = now.secsTo(ui->txtNewCustomTime->dateTime()); + cfg.SetInt64("RTC.Offset", now.secsTo(ui->txtNewCustomTime->dateTime())); } else if (ui->chkResetTime->isChecked()) - Config::RTCOffset = 0; + cfg.SetInt64("RTC.Offset", 0); Config::Save(); } diff --git a/src/frontend/qt_sdl/DateTimeDialog.h b/src/frontend/qt_sdl/DateTimeDialog.h index 22dabcd0f3..5721bdab53 100644 --- a/src/frontend/qt_sdl/DateTimeDialog.h +++ b/src/frontend/qt_sdl/DateTimeDialog.h @@ -26,6 +26,8 @@ namespace Ui {class DateTimeDialog; } class DateTimeDialog; +class EmuInstance; + class DateTimeDialog : public QDialog { Q_OBJECT @@ -63,6 +65,7 @@ private slots: private: Ui::DateTimeDialog* ui; + EmuInstance* emuInstance; QDateTime customTime; }; diff --git a/src/frontend/qt_sdl/ROMManager.cpp b/src/frontend/qt_sdl/EmuInstance.cpp similarity index 55% rename from src/frontend/qt_sdl/ROMManager.cpp rename to src/frontend/qt_sdl/EmuInstance.cpp index 9607c8484e..7585c585c5 100644 --- a/src/frontend/qt_sdl/ROMManager.cpp +++ b/src/frontend/qt_sdl/EmuInstance.cpp @@ -35,9 +35,11 @@ #ifdef ARCHIVE_SUPPORT_ENABLED #include "ArchiveUtil.h" #endif -#include "ROMManager.h" +#include "EmuInstance.h" #include "Config.h" #include "Platform.h" +#include "Net.h" +#include "LocalMP.h" #include "NDS.h" #include "DSi.h" @@ -56,32 +58,207 @@ using std::wstring_convert; using namespace melonDS; using namespace melonDS::Platform; -namespace ROMManager + +MainWindow* topWindow = nullptr; + +const string kWifiSettingsPath = "wfcsettings.bin"; + + +EmuInstance::EmuInstance(int inst) : instanceID(inst), + globalCfg(Config::GetGlobalTable()), + localCfg(Config::GetLocalTable(inst)) +{ + cheatFile = nullptr; + cheatsOn = localCfg.GetBool("EnableCheats"); + + doLimitFPS = globalCfg.GetBool("LimitFPS"); + maxFPS = globalCfg.GetInt("MaxFPS"); + doAudioSync = globalCfg.GetBool("AudioSync"); + + mpAudioMode = globalCfg.GetInt("MP.AudioMode"); + + nds = nullptr; + updateConsole(nullptr, nullptr); + + audioInit(); + inputInit(); + + Net::RegisterInstance(instanceID); + + emuThread = new EmuThread(this); + + numWindows = 0; + mainWindow = nullptr; + for (int i = 0; i < kMaxWindows; i++) + windowList[i] = nullptr; + + if (inst == 0) topWindow = nullptr; + createWindow(); + + emuThread->start(); + emuThread->emuPause(); +} + +EmuInstance::~EmuInstance() +{ + // TODO window cleanup and shit? + + LocalMP::End(instanceID); + + emuThread->emuExit(); + emuThread->wait(); + delete emuThread; + + Net::UnregisterInstance(instanceID); + + audioDeInit(); + inputDeInit(); +} + + +std::string EmuInstance::instanceFileSuffix() +{ + if (instanceID == 0) return ""; + + char suffix[16] = {0}; + snprintf(suffix, 15, ".%d", instanceID+1); + return suffix; +} + +void EmuInstance::createWindow() +{ + if (numWindows >= kMaxWindows) + { + // TODO + return; + } + + int id = -1; + for (int i = 0; i < kMaxWindows; i++) + { + if (windowList[i]) continue; + id = i; + break; + } + + if (id == -1) + return; + + MainWindow* win = new MainWindow(id, this, topWindow); + if (!topWindow) topWindow = win; + if (!mainWindow) mainWindow = win; + windowList[id] = win; + numWindows++; + + emuThread->attachWindow(win); +} + + +void EmuInstance::osdAddMessage(unsigned int color, const char* fmt, ...) +{ + if (fmt == nullptr) + return; + + char msg[256]; + va_list args; + va_start(args, fmt); + vsnprintf(msg, 256, fmt, args); + va_end(args); + + for (int i = 0; i < kMaxWindows; i++) + { + if (windowList[i]) + windowList[i]->osdAddMessage(color, msg); + } +} + + +bool EmuInstance::emuIsActive() +{ + return emuThread->emuIsActive(); +} + +void EmuInstance::emuStop(StopReason reason) +{ + if (reason != StopReason::External) + emuThread->emuStop(false); + + switch (reason) + { + case StopReason::GBAModeNotSupported: + osdAddMessage(0xFFA0A0, "GBA mode not supported"); + break; + case StopReason::BadExceptionRegion: + osdAddMessage(0xFFA0A0, "Internal error"); + break; + case StopReason::PowerOff: + case StopReason::External: + osdAddMessage(0xFFC040, "Shutdown"); + default: + break; + } +} + + +bool EmuInstance::usesOpenGL() +{ + return globalCfg.GetBool("Screen.UseGL") || + (globalCfg.GetInt("3D.Renderer") != renderer3D_Software); +} + +void EmuInstance::initOpenGL() { + for (int i = 0; i < kMaxWindows; i++) + { + if (windowList[i]) + windowList[i]->initOpenGL(); + } -int CartType = -1; -std::string BaseROMDir = ""; -std::string BaseROMName = ""; -std::string BaseAssetName = ""; + setVSyncGL(true); +} -int GBACartType = -1; -std::string BaseGBAROMDir = ""; -std::string BaseGBAROMName = ""; -std::string BaseGBAAssetName = ""; +void EmuInstance::deinitOpenGL() +{ + for (int i = 0; i < kMaxWindows; i++) + { + if (windowList[i]) + windowList[i]->deinitOpenGL(); + } +} -std::unique_ptr NDSSave = nullptr; -std::unique_ptr GBASave = nullptr; -std::unique_ptr FirmwareSave = nullptr; +void EmuInstance::setVSyncGL(bool vsync) +{ + int intv; -std::unique_ptr BackupState = nullptr; -bool SavestateLoaded = false; -std::string PreviousSaveFile = ""; + vsync = vsync && globalCfg.GetBool("Screen.VSync"); + if (vsync) + intv = globalCfg.GetInt("Screen.VSyncInterval"); + else + intv = 0; -ARCodeFile* CheatFile = nullptr; -bool CheatsOn = false; + for (int i = 0; i < kMaxWindows; i++) + { + if (windowList[i]) + windowList[i]->setGLSwapInterval(intv); + } +} + +void EmuInstance::makeCurrentGL() +{ + mainWindow->makeCurrentGL(); +} + +void EmuInstance::drawScreenGL() +{ + for (int i = 0; i < kMaxWindows; i++) + { + if (windowList[i]) + windowList[i]->drawScreenGL(); + } +} -int LastSep(const std::string& path) +int EmuInstance::lastSep(const std::string& path) { int i = path.length() - 1; while (i >= 0) @@ -95,12 +272,12 @@ int LastSep(const std::string& path) return -1; } -std::string GetAssetPath(bool gba, const std::string& configpath, const std::string& ext, const std::string& file = "") +string EmuInstance::getAssetPath(bool gba, const string& configpath, const string& ext, const string& file = "") { - std::string result; + string result; if (configpath.empty()) - result = gba ? BaseGBAROMDir : BaseROMDir; + result = gba ? baseGBAROMDir : baseROMDir; else result = configpath; @@ -120,7 +297,7 @@ std::string GetAssetPath(bool gba, const std::string& configpath, const std::str if (file.empty()) { - std::string& baseName = gba ? BaseGBAAssetName : BaseAssetName; + std::string& baseName = gba ? baseGBAAssetName : baseAssetName; if (baseName.empty()) result += "firmware"; else @@ -137,12 +314,12 @@ std::string GetAssetPath(bool gba, const std::string& configpath, const std::str } -QString VerifyDSBIOS() +QString EmuInstance::verifyDSBIOS() { FileHandle* f; long len; - f = Platform::OpenLocalFile(Config::BIOS9Path, FileMode::Read); + f = Platform::OpenLocalFile(globalCfg.GetString("DS.BIOS9Path"), FileMode::Read); if (!f) return "DS ARM9 BIOS was not found or could not be accessed. Check your emu settings."; len = FileLength(f); @@ -154,7 +331,7 @@ QString VerifyDSBIOS() CloseFile(f); - f = Platform::OpenLocalFile(Config::BIOS7Path, FileMode::Read); + f = Platform::OpenLocalFile(globalCfg.GetString("DS.BIOS7Path"), FileMode::Read); if (!f) return "DS ARM7 BIOS was not found or could not be accessed. Check your emu settings."; len = FileLength(f); @@ -169,14 +346,14 @@ QString VerifyDSBIOS() return ""; } -QString VerifyDSiBIOS() +QString EmuInstance::verifyDSiBIOS() { FileHandle* f; long len; // TODO: check the first 32 bytes - f = Platform::OpenLocalFile(Config::DSiBIOS9Path, FileMode::Read); + f = Platform::OpenLocalFile(globalCfg.GetString("DSi.BIOS9Path"), FileMode::Read); if (!f) return "DSi ARM9 BIOS was not found or could not be accessed. Check your emu settings."; len = FileLength(f); @@ -188,7 +365,7 @@ QString VerifyDSiBIOS() CloseFile(f); - f = Platform::OpenLocalFile(Config::DSiBIOS7Path, FileMode::Read); + f = Platform::OpenLocalFile(globalCfg.GetString("DSi.BIOS7Path"), FileMode::Read); if (!f) return "DSi ARM7 BIOS was not found or could not be accessed. Check your emu settings."; len = FileLength(f); @@ -203,15 +380,17 @@ QString VerifyDSiBIOS() return ""; } -QString VerifyDSFirmware() +QString EmuInstance::verifyDSFirmware() { FileHandle* f; long len; - f = Platform::OpenLocalFile(Config::FirmwarePath, FileMode::Read); + std::string fwpath = globalCfg.GetString("DS.FirmwarePath"); + + f = Platform::OpenLocalFile(fwpath, FileMode::Read); if (!f) return "DS firmware was not found or could not be accessed. Check your emu settings."; - if (!Platform::CheckFileWritable(Config::FirmwarePath)) + if (!Platform::CheckFileWritable(fwpath)) return "DS firmware is unable to be written to.\nPlease check file/folder write permissions."; len = FileLength(f); @@ -233,15 +412,17 @@ QString VerifyDSFirmware() return ""; } -QString VerifyDSiFirmware() +QString EmuInstance::verifyDSiFirmware() { FileHandle* f; long len; - f = Platform::OpenLocalFile(Config::DSiFirmwarePath, FileMode::Read); + std::string fwpath = globalCfg.GetString("DSi.FirmwarePath"); + + f = Platform::OpenLocalFile(fwpath, FileMode::Read); if (!f) return "DSi firmware was not found or could not be accessed. Check your emu settings."; - if (!Platform::CheckFileWritable(Config::FirmwarePath)) + if (!Platform::CheckFileWritable(fwpath)) return "DSi firmware is unable to be written to.\nPlease check file/folder write permissions."; len = FileLength(f); @@ -258,15 +439,17 @@ QString VerifyDSiFirmware() return ""; } -QString VerifyDSiNAND() +QString EmuInstance::verifyDSiNAND() { FileHandle* f; long len; - f = Platform::OpenLocalFile(Config::DSiNANDPath, FileMode::ReadWriteExisting); + std::string nandpath = globalCfg.GetString("DSi.NANDPath"); + + f = Platform::OpenLocalFile(nandpath, FileMode::ReadWriteExisting); if (!f) return "DSi NAND was not found or could not be accessed. Check your emu settings."; - if (!Platform::CheckFileWritable(Config::FirmwarePath)) + if (!Platform::CheckFileWritable(nandpath)) return "DSi NAND is unable to be written to.\nPlease check file/folder write permissions."; // TODO: some basic checks @@ -277,35 +460,38 @@ QString VerifyDSiNAND() return ""; } -QString VerifySetup() +QString EmuInstance::verifySetup() { QString res; - if (Config::ExternalBIOSEnable) + bool extbios = globalCfg.GetBool("Emu.ExternalBIOSEnable"); + int console = globalCfg.GetInt("Emu.ConsoleType"); + + if (extbios) { - res = VerifyDSBIOS(); + res = verifyDSBIOS(); if (!res.isEmpty()) return res; } - if (Config::ConsoleType == 1) + if (console == 1) { - res = VerifyDSiBIOS(); + res = verifyDSiBIOS(); if (!res.isEmpty()) return res; - if (Config::ExternalBIOSEnable) + if (extbios) { - res = VerifyDSiFirmware(); + res = verifyDSiFirmware(); if (!res.isEmpty()) return res; } - res = VerifyDSiNAND(); + res = verifyDSiNAND(); if (!res.isEmpty()) return res; } else { - if (Config::ExternalBIOSEnable) + if (extbios) { - res = VerifyDSFirmware(); + res = verifyDSFirmware(); if (!res.isEmpty()) return res; } } @@ -313,43 +499,44 @@ QString VerifySetup() return ""; } -std::string GetEffectiveFirmwareSavePath(EmuThread* thread) + +std::string EmuInstance::getEffectiveFirmwareSavePath() { - if (!Config::ExternalBIOSEnable) + if (!globalCfg.GetBool("Emu.ExternalBIOSEnable")) { - return Config::WifiSettingsPath; + return kWifiSettingsPath; } - if (thread->NDS->ConsoleType == 1) + if (nds->ConsoleType == 1) { - return Config::DSiFirmwarePath; + return globalCfg.GetString("DSi.FirmwarePath"); } else { - return Config::FirmwarePath; + return globalCfg.GetString("DS.FirmwarePath"); } } // Initializes the firmware save manager with the selected firmware image's path // OR the path to the wi-fi settings. -void InitFirmwareSaveManager(EmuThread* thread) noexcept +void EmuInstance::initFirmwareSaveManager() noexcept { - FirmwareSave = std::make_unique(GetEffectiveFirmwareSavePath(thread)); + firmwareSave = std::make_unique(getEffectiveFirmwareSavePath()); } -std::string GetSavestateName(int slot) +std::string EmuInstance::getSavestateName(int slot) { std::string ext = ".ml"; ext += (char)('0'+slot); - return GetAssetPath(false, Config::SavestatePath, ext); + return getAssetPath(false, globalCfg.GetString("SavestatePath"), ext); } -bool SavestateExists(int slot) +bool EmuInstance::savestateExists(int slot) { - std::string ssfile = GetSavestateName(slot); + std::string ssfile = getSavestateName(slot); return Platform::FileExists(ssfile); } -bool LoadState(NDS& nds, const std::string& filename) +bool EmuInstance::loadState(const std::string& filename) { FILE* file = fopen(filename.c_str(), "rb"); if (file == nullptr) @@ -366,7 +553,7 @@ bool LoadState(NDS& nds, const std::string& filename) return false; } - if (!nds.DoSavestate(backup.get()) || backup->Error) + if (!nds->DoSavestate(backup.get()) || backup->Error) { // Back up the emulator's state. If that failed... Platform::Log(Platform::LogLevel::Error, "Failed to back up state, aborting load (from \"%s\")\n", filename.c_str()); fclose(file); @@ -398,32 +585,32 @@ bool LoadState(NDS& nds, const std::string& filename) // Get ready to load the state from the buffer into the emulator std::unique_ptr state = std::make_unique(buffer.data(), size, false); - if (!nds.DoSavestate(state.get()) || state->Error) + if (!nds->DoSavestate(state.get()) || state->Error) { // If we couldn't load the savestate from the buffer... Platform::Log(Platform::LogLevel::Error, "Failed to load state file \"%s\" into emulator\n", filename.c_str()); return false; } // The backup was made and the state was loaded, so we can store the backup now. - BackupState = std::move(backup); // This will clean up any existing backup + backupState = std::move(backup); // This will clean up any existing backup assert(backup == nullptr); - if (Config::SavestateRelocSRAM && NDSSave) + if (globalCfg.GetBool("Savestate.RelocSRAM") && ndsSave) { - PreviousSaveFile = NDSSave->GetPath(); + previousSaveFile = ndsSave->GetPath(); - std::string savefile = filename.substr(LastSep(filename)+1); - savefile = GetAssetPath(false, Config::SaveFilePath, ".sav", savefile); - savefile += Platform::InstanceFileSuffix(); - NDSSave->SetPath(savefile, true); + std::string savefile = filename.substr(lastSep(filename)+1); + savefile = getAssetPath(false, globalCfg.GetString("SaveFilePath"), ".sav", savefile); + savefile += instanceFileSuffix(); + ndsSave->SetPath(savefile, true); } - SavestateLoaded = true; + savestateLoaded = true; return true; } -bool SaveState(NDS& nds, const std::string& filename) +bool EmuInstance::saveState(const std::string& filename) { FILE* file = fopen(filename.c_str(), "wb"); @@ -440,7 +627,7 @@ bool SaveState(NDS& nds, const std::string& filename) } // Write the savestate to the in-memory buffer - nds.DoSavestate(&state); + nds->DoSavestate(&state); if (state.Error) { @@ -451,9 +638,9 @@ bool SaveState(NDS& nds, const std::string& filename) if (fwrite(state.Buffer(), state.Length(), 1, file) == 0) { // Write the Savestate buffer to the file. If that fails... Platform::Log(Platform::Error, - "Failed to write %d-byte savestate to %s\n", - state.Length(), - filename.c_str() + "Failed to write %d-byte savestate to %s\n", + state.Length(), + filename.c_str() ); fclose(file); return false; @@ -461,71 +648,73 @@ bool SaveState(NDS& nds, const std::string& filename) fclose(file); - if (Config::SavestateRelocSRAM && NDSSave) + if (globalCfg.GetBool("Savestate.RelocSRAM") && ndsSave) { - std::string savefile = filename.substr(LastSep(filename)+1); - savefile = GetAssetPath(false, Config::SaveFilePath, ".sav", savefile); - savefile += Platform::InstanceFileSuffix(); - NDSSave->SetPath(savefile, false); + std::string savefile = filename.substr(lastSep(filename)+1); + savefile = getAssetPath(false, globalCfg.GetString("SaveFilePath"), ".sav", savefile); + savefile += instanceFileSuffix(); + ndsSave->SetPath(savefile, false); } return true; } -void UndoStateLoad(NDS& nds) +void EmuInstance::undoStateLoad() { - if (!SavestateLoaded || !BackupState) return; + if (!savestateLoaded || !backupState) return; // Rewind the backup state and put it in load mode - BackupState->Rewind(false); + backupState->Rewind(false); // pray that this works // what do we do if it doesn't??? // but it should work. - nds.DoSavestate(BackupState.get()); + nds->DoSavestate(backupState.get()); - if (NDSSave && (!PreviousSaveFile.empty())) + if (ndsSave && (!previousSaveFile.empty())) { - NDSSave->SetPath(PreviousSaveFile, true); + ndsSave->SetPath(previousSaveFile, true); } } -void UnloadCheats(NDS& nds) +void EmuInstance::unloadCheats() { - if (CheatFile) + if (cheatFile) { - delete CheatFile; - CheatFile = nullptr; - nds.AREngine.SetCodeFile(nullptr); + delete cheatFile; + cheatFile = nullptr; + nds->AREngine.SetCodeFile(nullptr); } } -void LoadCheats(NDS& nds) +void EmuInstance::loadCheats() { - UnloadCheats(nds); + unloadCheats(); - std::string filename = GetAssetPath(false, Config::CheatFilePath, ".mch"); + std::string filename = getAssetPath(false, globalCfg.GetString("CheatFilePath"), ".mch"); // TODO: check for error (malformed cheat file, ...) - CheatFile = new ARCodeFile(filename); + cheatFile = new ARCodeFile(filename); - nds.AREngine.SetCodeFile(CheatsOn ? CheatFile : nullptr); + nds->AREngine.SetCodeFile(cheatsOn ? cheatFile : nullptr); } -std::unique_ptr LoadARM9BIOS() noexcept +std::unique_ptr EmuInstance::loadARM9BIOS() noexcept { - if (!Config::ExternalBIOSEnable) + if (!globalCfg.GetBool("Emu.ExternalBIOSEnable")) { - return Config::ConsoleType == 0 ? std::make_unique(bios_arm9_bin) : nullptr; + return globalCfg.GetInt("Emu.ConsoleType") == 0 ? std::make_unique(bios_arm9_bin) : nullptr; } - if (FileHandle* f = OpenLocalFile(Config::BIOS9Path, Read)) + string path = globalCfg.GetString("DS.BIOS9Path"); + + if (FileHandle* f = OpenLocalFile(path, Read)) { std::unique_ptr bios = std::make_unique(); FileRewind(f); FileRead(bios->data(), bios->size(), 1, f); CloseFile(f); - Log(Info, "ARM9 BIOS loaded from %s\n", Config::BIOS9Path.c_str()); + Log(Info, "ARM9 BIOS loaded from %s\n", path.c_str()); return bios; } @@ -533,19 +722,21 @@ std::unique_ptr LoadARM9BIOS() noexcept return nullptr; } -std::unique_ptr LoadARM7BIOS() noexcept +std::unique_ptr EmuInstance::loadARM7BIOS() noexcept { - if (!Config::ExternalBIOSEnable) + if (!globalCfg.GetBool("Emu.ExternalBIOSEnable")) { - return Config::ConsoleType == 0 ? std::make_unique(bios_arm7_bin) : nullptr; + return globalCfg.GetInt("Emu.ConsoleType") == 0 ? std::make_unique(bios_arm7_bin) : nullptr; } - if (FileHandle* f = OpenLocalFile(Config::BIOS7Path, Read)) + string path = globalCfg.GetString("DS.BIOS7Path"); + + if (FileHandle* f = OpenLocalFile(path, Read)) { std::unique_ptr bios = std::make_unique(); FileRead(bios->data(), bios->size(), 1, f); CloseFile(f); - Log(Info, "ARM7 BIOS loaded from %s\n", Config::BIOS7Path.c_str()); + Log(Info, "ARM7 BIOS loaded from %s\n", path.c_str()); return bios; } @@ -553,15 +744,17 @@ std::unique_ptr LoadARM7BIOS() noexcept return nullptr; } -std::unique_ptr LoadDSiARM9BIOS() noexcept +std::unique_ptr EmuInstance::loadDSiARM9BIOS() noexcept { - if (FileHandle* f = OpenLocalFile(Config::DSiBIOS9Path, Read)) + string path = globalCfg.GetString("DSi.BIOS9Path"); + + if (FileHandle* f = OpenLocalFile(path, Read)) { std::unique_ptr bios = std::make_unique(); FileRead(bios->data(), bios->size(), 1, f); CloseFile(f); - if (!Config::DSiFullBIOSBoot) + if (!globalCfg.GetBool("DSi.FullBIOSBoot")) { // herp *(u32*)bios->data() = 0xEAFFFFFE; // overwrites the reset vector @@ -570,7 +763,7 @@ std::unique_ptr LoadDSiARM9BIOS() noexcept // hax the upper 32K out of the goddamn DSi // done that :) -pcy } - Log(Info, "ARM9i BIOS loaded from %s\n", Config::DSiBIOS9Path.c_str()); + Log(Info, "ARM9i BIOS loaded from %s\n", path.c_str()); return bios; } @@ -578,15 +771,17 @@ std::unique_ptr LoadDSiARM9BIOS() noexcept return nullptr; } -std::unique_ptr LoadDSiARM7BIOS() noexcept +std::unique_ptr EmuInstance::loadDSiARM7BIOS() noexcept { - if (FileHandle* f = OpenLocalFile(Config::DSiBIOS7Path, Read)) + string path = globalCfg.GetString("DSi.BIOS7Path"); + + if (FileHandle* f = OpenLocalFile(path, Read)) { std::unique_ptr bios = std::make_unique(); FileRead(bios->data(), bios->size(), 1, f); CloseFile(f); - if (!Config::DSiFullBIOSBoot) + if (!globalCfg.GetBool("DSi.FullBIOSBoot")) { // herp *(u32*)bios->data() = 0xEAFFFFFE; // overwrites the reset vector @@ -595,7 +790,7 @@ std::unique_ptr LoadDSiARM7BIOS() noexcept // hax the upper 32K out of the goddamn DSi // done that :) -pcy } - Log(Info, "ARM7i BIOS loaded from %s\n", Config::DSiBIOS7Path.c_str()); + Log(Info, "ARM7i BIOS loaded from %s\n", path.c_str()); return bios; } @@ -603,7 +798,7 @@ std::unique_ptr LoadDSiARM7BIOS() noexcept return nullptr; } -Firmware GenerateFirmware(int type) noexcept +Firmware EmuInstance::generateFirmware(int type) noexcept { // Construct the default firmware... string settingspath; @@ -614,44 +809,44 @@ Firmware GenerateFirmware(int type) noexcept // Wi-fi access point data includes Nintendo WFC settings, // and if we didn't keep them then the player would have to reset them in each session. // We don't need to save the whole firmware, just the part that may actually change. - if (FileHandle* f = OpenLocalFile(Config::WifiSettingsPath, Read)) + if (FileHandle* f = OpenLocalFile(kWifiSettingsPath, Read)) {// If we have Wi-fi settings to load... constexpr unsigned TOTAL_WFC_SETTINGS_SIZE = 3 * (sizeof(Firmware::WifiAccessPoint) + sizeof(Firmware::ExtendedWifiAccessPoint)); if (!FileRead(firmware.GetExtendedAccessPointPosition(), TOTAL_WFC_SETTINGS_SIZE, 1, f)) { // If we couldn't read the Wi-fi settings from this file... - Log(Warn, "Failed to read Wi-fi settings from \"%s\"; using defaults instead\n", Config::WifiSettingsPath.c_str()); + Log(Warn, "Failed to read Wi-fi settings from \"%s\"; using defaults instead\n", kWifiSettingsPath.c_str()); // The access point and extended access point segments might // be in different locations depending on the firmware revision, // but our generated firmware always keeps them next to each other. // (Extended access points first, then regular ones.) firmware.GetAccessPoints() = { - Firmware::WifiAccessPoint(type), - Firmware::WifiAccessPoint(), - Firmware::WifiAccessPoint(), + Firmware::WifiAccessPoint(type), + Firmware::WifiAccessPoint(), + Firmware::WifiAccessPoint(), }; firmware.GetExtendedAccessPoints() = { - Firmware::ExtendedWifiAccessPoint(), - Firmware::ExtendedWifiAccessPoint(), - Firmware::ExtendedWifiAccessPoint(), + Firmware::ExtendedWifiAccessPoint(), + Firmware::ExtendedWifiAccessPoint(), + Firmware::ExtendedWifiAccessPoint(), }; firmware.UpdateChecksums(); CloseFile(f); } } - CustomizeFirmware(firmware, true); + customizeFirmware(firmware, true); // If we don't have Wi-fi settings to load, // then the defaults will have already been populated by the constructor. return firmware; } -std::optional LoadFirmware(int type) noexcept +std::optional EmuInstance::loadFirmware(int type) noexcept { - if (!Config::ExternalBIOSEnable) + if (!globalCfg.GetBool("Emu.ExternalBIOSEnable")) { // If we're using built-in firmware... if (type == 1) { @@ -659,9 +854,14 @@ std::optional LoadFirmware(int type) noexcept return std::nullopt; } - return GenerateFirmware(type); + return generateFirmware(type); } - const string& firmwarepath = type == 1 ? Config::DSiFirmwarePath : Config::FirmwarePath; + //const string& firmwarepath = type == 1 ? Config::DSiFirmwarePath : Config::FirmwarePath; + string firmwarepath; + if (type == 1) + firmwarepath = globalCfg.GetString("DSi.FirmwarePath"); + else + firmwarepath = globalCfg.GetString("DS.FirmwarePath"); Log(Debug, "SPI firmware: loading from file %s\n", firmwarepath.c_str()); @@ -681,15 +881,17 @@ std::optional LoadFirmware(int type) noexcept return std::nullopt; } - CustomizeFirmware(firmware, Config::FirmwareOverrideSettings); + customizeFirmware(firmware, localCfg.GetBool("Firmware.OverrideSettings")); return firmware; } -std::optional LoadNAND(const std::array& arm7ibios) noexcept +std::optional EmuInstance::loadNAND(const std::array& arm7ibios) noexcept { - FileHandle* nandfile = OpenLocalFile(Config::DSiNANDPath, ReadWriteExisting); + string path = globalCfg.GetString("DSi.NANDPath"); + + FileHandle* nandfile = OpenLocalFile(path, ReadWriteExisting); if (!nandfile) return std::nullopt; @@ -718,29 +920,31 @@ std::optional LoadNAND(const std::array& a } // override user settings, if needed - if (Config::FirmwareOverrideSettings) + if (localCfg.GetBool("Firmware.OverrideSettings")) { + auto firmcfg = localCfg.GetTable("Firmware"); + // we store relevant strings as UTF-8, so we need to convert them to UTF-16 auto converter = wstring_convert, char16_t>{}; // setting up username - std::u16string username = converter.from_bytes(Config::FirmwareUsername); + std::u16string username = converter.from_bytes(firmcfg.GetString("Username")); size_t usernameLength = std::min(username.length(), (size_t) 10); memset(&settings.Nickname, 0, sizeof(settings.Nickname)); memcpy(&settings.Nickname, username.data(), usernameLength * sizeof(char16_t)); // setting language - settings.Language = static_cast(Config::FirmwareLanguage); + settings.Language = static_cast(firmcfg.GetInt("Language")); // setting up color - settings.FavoriteColor = Config::FirmwareFavouriteColour; + settings.FavoriteColor = firmcfg.GetInt("FavouriteColour"); // setting up birthday - settings.BirthdayMonth = Config::FirmwareBirthdayMonth; - settings.BirthdayDay = Config::FirmwareBirthdayDay; + settings.BirthdayMonth = firmcfg.GetInt("BirthdayMonth"); + settings.BirthdayDay = firmcfg.GetInt("BirthdayDay"); // setup message - std::u16string message = converter.from_bytes(Config::FirmwareMessage); + std::u16string message = converter.from_bytes(firmcfg.GetString("Message")); size_t messageLength = std::min(message.length(), (size_t) 26); memset(&settings.Message, 0, sizeof(settings.Message)); memcpy(&settings.Message, message.data(), messageLength * sizeof(char16_t)); @@ -772,171 +976,314 @@ constexpr u64 MB(u64 i) } constexpr u64 imgsizes[] = {0, MB(256), MB(512), MB(1024), MB(2048), MB(4096)}; -std::optional GetDSiSDCardArgs() noexcept + +std::optional EmuInstance::getSDCardArgs(const string& key) noexcept { - if (!Config::DSiSDEnable) + // key = DSi.SD or DLDI + Config::Table sdopt = globalCfg.GetTable(key); + + if (!sdopt.GetBool("Enable")) return std::nullopt; return FATStorageArgs { - Config::DSiSDPath, - imgsizes[Config::DSiSDSize], - Config::DSiSDReadOnly, - Config::DSiSDFolderSync ? std::make_optional(Config::DSiSDFolderPath) : std::nullopt + sdopt.GetString("ImagePath"), + imgsizes[sdopt.GetInt("ImageSize")], + sdopt.GetBool("ReadOnly"), + sdopt.GetBool("FolderSync") ? std::make_optional(sdopt.GetString("FolderPath")) : std::nullopt }; } -std::optional LoadDSiSDCard() noexcept +std::optional EmuInstance::loadSDCard(const string& key) noexcept { - if (!Config::DSiSDEnable) + auto args = getSDCardArgs(key); + if (!args.has_value()) return std::nullopt; - return FATStorage( - Config::DSiSDPath, - imgsizes[Config::DSiSDSize], - Config::DSiSDReadOnly, - Config::DSiSDFolderSync ? std::make_optional(Config::DSiSDFolderPath) : std::nullopt - ); + return FATStorage(args.value()); } -std::optional GetDLDISDCardArgs() noexcept +void EmuInstance::enableCheats(bool enable) { - if (!Config::DLDIEnable) - return std::nullopt; - - return FATStorageArgs{ - Config::DLDISDPath, - imgsizes[Config::DLDISize], - Config::DLDIReadOnly, - Config::DLDIFolderSync ? std::make_optional(Config::DLDIFolderPath) : std::nullopt - }; + cheatsOn = enable; + if (cheatFile) + nds->AREngine.SetCodeFile(cheatsOn ? cheatFile : nullptr); } -std::optional LoadDLDISDCard() noexcept +ARCodeFile* EmuInstance::getCheatFile() { - if (!Config::DLDIEnable) - return std::nullopt; - - return FATStorage(*GetDLDISDCardArgs()); + return cheatFile; } -void EnableCheats(NDS& nds, bool enable) +void EmuInstance::setBatteryLevels() { - CheatsOn = enable; - if (CheatFile) - nds.AREngine.SetCodeFile(CheatsOn ? CheatFile : nullptr); + if (nds->ConsoleType == 1) + { + auto dsi = static_cast(nds); + dsi->I2C.GetBPTWL()->SetBatteryLevel(localCfg.GetInt("DSi.Battery.Level")); + dsi->I2C.GetBPTWL()->SetBatteryCharging(localCfg.GetBool("DSi.Battery.Charging")); + } + else + { + nds->SPI.GetPowerMan()->SetBatteryLevelOkay(localCfg.GetBool("DS.Battery.LevelOkay")); + } } -ARCodeFile* GetCheatFile() +void EmuInstance::setDateTime() { - return CheatFile; -} + QDateTime hosttime = QDateTime::currentDateTime(); + QDateTime time = hosttime.addSecs(localCfg.GetInt64("RTC.Offset")); + nds->RTC.SetDateTime(time.date().year(), time.date().month(), time.date().day(), + time.time().hour(), time.time().minute(), time.time().second()); +} -void SetBatteryLevels(NDS& nds) +bool EmuInstance::updateConsole(UpdateConsoleNDSArgs&& _ndsargs, UpdateConsoleGBAArgs&& _gbaargs) noexcept { - if (nds.ConsoleType == 1) + // Let's get the cart we want to use; + // if we wnat to keep the cart, we'll eject it from the existing console first. + std::unique_ptr nextndscart; + if (std::holds_alternative(_ndsargs)) + { // If we want to keep the existing cart (if any)... + nextndscart = nds ? nds->EjectCart() : nullptr; + _ndsargs = {}; + } + else if (const auto ptr = std::get_if>(&_ndsargs)) { - auto& dsi = static_cast(nds); - dsi.I2C.GetBPTWL()->SetBatteryLevel(Config::DSiBatteryLevel); - dsi.I2C.GetBPTWL()->SetBatteryCharging(Config::DSiBatteryCharging); + nextndscart = std::move(*ptr); + _ndsargs = {}; } - else + + if (auto* cartsd = dynamic_cast(nextndscart.get())) { - nds.SPI.GetPowerMan()->SetBatteryLevelOkay(Config::DSBatteryLevelOkay); + // LoadDLDISDCard will return nullopt if the SD card is disabled; + // SetSDCard will accept nullopt, which means no SD card + cartsd->SetSDCard(getSDCardArgs("DLDI")); } -} -void SetDateTime(NDS& nds) -{ - QDateTime hosttime = QDateTime::currentDateTime(); - QDateTime time = hosttime.addSecs(Config::RTCOffset); + std::unique_ptr nextgbacart; + if (std::holds_alternative(_gbaargs)) + { + nextgbacart = nds ? nds->EjectGBACart() : nullptr; + } + else if (const auto ptr = std::get_if>(&_gbaargs)) + { + nextgbacart = std::move(*ptr); + _gbaargs = {}; + } + + + int consoletype = globalCfg.GetInt("Emu.ConsoleType"); + + auto arm9bios = loadARM9BIOS(); + if (!arm9bios) + return false; + + auto arm7bios = loadARM7BIOS(); + if (!arm7bios) + return false; + + auto firmware = loadFirmware(consoletype); + if (!firmware) + return false; + +#ifdef JIT_ENABLED + Config::Table jitopt = globalCfg.GetTable("JIT"); + JITArgs _jitargs { + static_cast(jitopt.GetInt("MaxBlockSize")), + jitopt.GetBool("LiteralOptimisations"), + jitopt.GetBool("BranchOptimisations"), + jitopt.GetBool("FastMemory"), + }; + auto jitargs = jitopt.GetBool("Enable") ? std::make_optional(_jitargs) : std::nullopt; +#else + optional jitargs = std::nullopt; +#endif + +#ifdef GDBSTUB_ENABLED + Config::Table gdbopt = globalCfg.GetTable("Gdb"); + GDBArgs _gdbargs { + static_cast(gdbopt.GetInt("ARM7.Port")), + static_cast(gdbopt.GetInt("ARM9.Port")), + gdbopt.GetBool("ARM7.BreakOnStartup"), + gdbopt.GetBool("ARM9.BreakOnStartup"), + }; + auto gdbargs = gdbopt.GetBool("Enable") ? std::make_optional(_gdbargs) : std::nullopt; +#else + optional gdbargs = std::nullopt; +#endif + + NDSArgs ndsargs { + std::move(nextndscart), + std::move(nextgbacart), + std::move(arm9bios), + std::move(arm7bios), + std::move(*firmware), + jitargs, + static_cast(globalCfg.GetInt("Audio.BitDepth")), + static_cast(globalCfg.GetInt("Audio.Interpolation")), + gdbargs, + }; + NDSArgs* args = &ndsargs; + + std::optional dsiargs = std::nullopt; + if (consoletype == 1) + { + ndsargs.GBAROM = nullptr; + + auto arm7ibios = loadDSiARM7BIOS(); + if (!arm7ibios) + return false; + + auto arm9ibios = loadDSiARM9BIOS(); + if (!arm9ibios) + return false; + + auto nand = loadNAND(*arm7ibios); + if (!nand) + return false; + + auto sdcard = loadSDCard("DSi.SD"); + + DSiArgs _dsiargs { + std::move(ndsargs), + std::move(arm9ibios), + std::move(arm7ibios), + std::move(*nand), + std::move(sdcard), + globalCfg.GetBool("DSi.FullBIOSBoot"), + }; + + dsiargs = std::move(_dsiargs); + args = &(*dsiargs); + } + + + if ((!nds) || (consoletype != nds->ConsoleType)) + { + NDS::Current = nullptr; + if (nds) delete nds; - nds.RTC.SetDateTime(time.date().year(), time.date().month(), time.date().day(), - time.time().hour(), time.time().minute(), time.time().second()); + if (consoletype == 1) + nds = new DSi(std::move(dsiargs.value()), this); + else + nds = new NDS(std::move(ndsargs), this); + + NDS::Current = nds; + nds->Reset(); + } + else + { + nds->SetARM7BIOS(*args->ARM7BIOS); + nds->SetARM9BIOS(*args->ARM9BIOS); + nds->SetFirmware(std::move(args->Firmware)); + nds->SetNDSCart(std::move(args->NDSROM)); + nds->SetJITArgs(args->JIT); + // TODO GDB stub shit + nds->SPU.SetInterpolation(args->Interpolation); + nds->SPU.SetDegrade10Bit(args->BitDepth); + + if (consoletype == 1) + { + DSi* dsi = (DSi*)nds; + DSiArgs& _dsiargs = *dsiargs; + + dsi->SetFullBIOSBoot(_dsiargs.FullBIOSBoot); + dsi->ARM7iBIOS = *_dsiargs.ARM7iBIOS; + dsi->ARM9iBIOS = *_dsiargs.ARM9iBIOS; + dsi->SetNAND(std::move(_dsiargs.NANDImage)); + dsi->SetSDCard(std::move(_dsiargs.DSiSDCard)); + // We're moving the optional, not the card + // (inserting std::nullopt here is okay, it means no card) + + dsi->EjectGBACart(); + } + } + + return true; } -void Reset(EmuThread* thread) +void EmuInstance::reset() { - thread->UpdateConsole(Keep {}, Keep {}); + updateConsole(Keep {}, Keep {}); - if (Config::ConsoleType == 1) EjectGBACart(*thread->NDS); + if (nds->ConsoleType == 1) ejectGBACart(); - thread->NDS->Reset(); - SetBatteryLevels(*thread->NDS); - SetDateTime(*thread->NDS); + nds->Reset(); + setBatteryLevels(); + setDateTime(); - if ((CartType != -1) && NDSSave) + if ((cartType != -1) && ndsSave) { - std::string oldsave = NDSSave->GetPath(); - std::string newsave = GetAssetPath(false, Config::SaveFilePath, ".sav"); - newsave += Platform::InstanceFileSuffix(); + std::string oldsave = ndsSave->GetPath(); + std::string newsave = getAssetPath(false, globalCfg.GetString("SaveFilePath"), ".sav"); + newsave += instanceFileSuffix(); if (oldsave != newsave) - NDSSave->SetPath(newsave, false); + ndsSave->SetPath(newsave, false); } - if ((GBACartType != -1) && GBASave) + if ((gbaCartType != -1) && gbaSave) { - std::string oldsave = GBASave->GetPath(); - std::string newsave = GetAssetPath(true, Config::SaveFilePath, ".sav"); - newsave += Platform::InstanceFileSuffix(); + std::string oldsave = gbaSave->GetPath(); + std::string newsave = getAssetPath(true, globalCfg.GetString("SaveFilePath"), ".sav"); + newsave += instanceFileSuffix(); if (oldsave != newsave) - GBASave->SetPath(newsave, false); + gbaSave->SetPath(newsave, false); } - InitFirmwareSaveManager(thread); - if (FirmwareSave) + initFirmwareSaveManager(); + if (firmwareSave) { - std::string oldsave = FirmwareSave->GetPath(); + std::string oldsave = firmwareSave->GetPath(); string newsave; - if (Config::ExternalBIOSEnable) + if (globalCfg.GetBool("Emu.ExternalBIOSEnable")) { - if (Config::ConsoleType == 1) - newsave = Config::DSiFirmwarePath + Platform::InstanceFileSuffix(); + if (nds->ConsoleType == 1) + newsave = globalCfg.GetString("DSi.FirmwarePath") + instanceFileSuffix(); else - newsave = Config::FirmwarePath + Platform::InstanceFileSuffix(); + newsave = globalCfg.GetString("DS.FirmwarePath") + instanceFileSuffix(); } else { - newsave = Config::WifiSettingsPath + Platform::InstanceFileSuffix(); + newsave = kWifiSettingsPath + instanceFileSuffix(); } if (oldsave != newsave) { // If the player toggled the ConsoleType or ExternalBIOSEnable... - FirmwareSave->SetPath(newsave, true); + firmwareSave->SetPath(newsave, true); } } - if (!BaseROMName.empty()) + if (!baseROMName.empty()) { - if (Config::DirectBoot || thread->NDS->NeedsDirectBoot()) + if (globalCfg.GetBool("Emu.DirectBoot") || nds->NeedsDirectBoot()) { - thread->NDS->SetupDirectBoot(BaseROMName); + nds->SetupDirectBoot(baseROMName); } } - thread->NDS->Start(); + nds->Start(); } -bool BootToMenu(EmuThread* thread) +bool EmuInstance::bootToMenu() { // Keep whatever cart is in the console, if any. - if (!thread->UpdateConsole(Keep {}, Keep {})) + if (!updateConsole(Keep {}, Keep {})) // Try to update the console, but keep the existing cart. If that fails... return false; // BIOS and firmware files are loaded, patched, and installed in UpdateConsole - if (thread->NDS->NeedsDirectBoot()) + if (nds->NeedsDirectBoot()) return false; - InitFirmwareSaveManager(thread); - thread->NDS->Reset(); - SetBatteryLevels(*thread->NDS); - SetDateTime(*thread->NDS); + initFirmwareSaveManager(); + nds->Reset(); + setBatteryLevels(); + setDateTime(); return true; } -u32 DecompressROM(const u8* inContent, const u32 inSize, unique_ptr& outContent) +u32 EmuInstance::decompressROM(const u8* inContent, const u32 inSize, unique_ptr& outContent) { u64 realSize = ZSTD_getFrameContentSize(inContent, inSize); const u32 maxSize = 0x40000000; @@ -966,9 +1313,9 @@ u32 DecompressROM(const u8* inContent, const u32 inSize, unique_ptr& outCo ZSTD_initDStream(dStream); ZSTD_inBuffer inBuf = { - .src = inContent, - .size = inSize, - .pos = 0 + .src = inContent, + .size = inSize, + .pos = 0 }; const u32 startSize = 1024 * 1024 * 16; @@ -1022,25 +1369,25 @@ u32 DecompressROM(const u8* inContent, const u32 inSize, unique_ptr& outCo } } -void ClearBackupState() +void EmuInstance::clearBackupState() { - if (BackupState != nullptr) + if (backupState != nullptr) { - BackupState = nullptr; + backupState = nullptr; } } -pair, string> GenerateDefaultFirmware() +pair, string> EmuInstance::generateDefaultFirmware() { // Construct the default firmware... string settingspath; - std::unique_ptr firmware = std::make_unique(Config::ConsoleType); + std::unique_ptr firmware = std::make_unique(nds->ConsoleType); assert(firmware->Buffer() != nullptr); // Try to open the instanced Wi-fi settings, falling back to the regular Wi-fi settings if they don't exist. // We don't need to save the whole firmware, just the part that may actually change. - std::string wfcsettingspath = Config::WifiSettingsPath; - settingspath = wfcsettingspath + Platform::InstanceFileSuffix(); + std::string wfcsettingspath = kWifiSettingsPath; + settingspath = wfcsettingspath + instanceFileSuffix(); FileHandle* f = Platform::OpenLocalFile(settingspath, FileMode::Read); if (!f) { @@ -1065,15 +1412,15 @@ pair, string> GenerateDefaultFirmware() Platform::Log(Platform::LogLevel::Warn, "Failed to read Wi-fi settings from \"%s\"; using defaults instead\n", wfcsettingspath.c_str()); firmware->GetAccessPoints() = { - Firmware::WifiAccessPoint(Config::ConsoleType), - Firmware::WifiAccessPoint(), - Firmware::WifiAccessPoint(), + Firmware::WifiAccessPoint(nds->ConsoleType), + Firmware::WifiAccessPoint(), + Firmware::WifiAccessPoint(), }; firmware->GetExtendedAccessPoints() = { - Firmware::ExtendedWifiAccessPoint(), - Firmware::ExtendedWifiAccessPoint(), - Firmware::ExtendedWifiAccessPoint(), + Firmware::ExtendedWifiAccessPoint(), + Firmware::ExtendedWifiAccessPoint(), + Firmware::ExtendedWifiAccessPoint(), }; } @@ -1087,9 +1434,9 @@ pair, string> GenerateDefaultFirmware() return std::make_pair(std::move(firmware), std::move(wfcsettingspath)); } -bool ParseMacAddress(void* data) +bool EmuInstance::parseMacAddress(void* data) { - const std::string& mac_in = Config::FirmwareMAC; + const std::string mac_in = localCfg.GetString("Firmware.MAC"); u8* mac_out = (u8*)data; int o = 0; @@ -1117,14 +1464,16 @@ bool ParseMacAddress(void* data) return false; } -void CustomizeFirmware(Firmware& firmware, bool overridesettings) noexcept +void EmuInstance::customizeFirmware(Firmware& firmware, bool overridesettings) noexcept { if (overridesettings) { auto ¤tData = firmware.GetEffectiveUserData(); + auto firmcfg = localCfg.GetTable("Firmware"); + // setting up username - std::string orig_username = Config::FirmwareUsername; + std::string orig_username = firmcfg.GetString("Username"); if (!orig_username.empty()) { // If the frontend defines a username, take it. If not, leave the existing one. std::u16string username = std::wstring_convert, char16_t>{}.from_bytes( @@ -1134,7 +1483,7 @@ void CustomizeFirmware(Firmware& firmware, bool overridesettings) noexcept memcpy(currentData.Nickname, username.data(), usernameLength * sizeof(char16_t)); } - auto language = static_cast(Config::FirmwareLanguage); + auto language = static_cast(firmcfg.GetInt("Language")); if (language != Firmware::Language::Reserved) { // If the frontend specifies a language (rather than using the existing value)... currentData.Settings &= ~Firmware::Language::Reserved; // ..clear the existing language... @@ -1142,26 +1491,26 @@ void CustomizeFirmware(Firmware& firmware, bool overridesettings) noexcept } // setting up color - u8 favoritecolor = Config::FirmwareFavouriteColour; + u8 favoritecolor = firmcfg.GetInt("FavouriteColour"); if (favoritecolor != 0xFF) { currentData.FavoriteColor = favoritecolor; } - u8 birthmonth = Config::FirmwareBirthdayMonth; + u8 birthmonth = firmcfg.GetInt("BirthdayMonth"); if (birthmonth != 0) { // If the frontend specifies a birth month (rather than using the existing value)... currentData.BirthdayMonth = birthmonth; } - u8 birthday = Config::FirmwareBirthdayDay; + u8 birthday = firmcfg.GetInt("BirthdayDay"); if (birthday != 0) { // If the frontend specifies a birthday (rather than using the existing value)... currentData.BirthdayDay = birthday; } // setup message - std::string orig_message = Config::FirmwareMessage; + std::string orig_message = firmcfg.GetString("Message"); if (!orig_message.empty()) { std::u16string message = std::wstring_convert, char16_t>{}.from_bytes( @@ -1181,7 +1530,7 @@ void CustomizeFirmware(Firmware& firmware, bool overridesettings) noexcept if (overridesettings) { MacAddress configuredMac; - rep = ParseMacAddress(&configuredMac); + rep = parseMacAddress(&configuredMac); rep &= (configuredMac != MacAddress()); if (rep) @@ -1190,13 +1539,12 @@ void CustomizeFirmware(Firmware& firmware, bool overridesettings) noexcept } } - int inst = Platform::InstanceID(); - if (inst > 0) + if (instanceID > 0) { rep = true; - mac[3] += inst; - mac[4] += inst*0x44; - mac[5] += inst*0x10; + mac[3] += instanceID; + mac[4] += instanceID*0x44; + mac[5] += instanceID*0x10; } if (rep) @@ -1210,7 +1558,7 @@ void CustomizeFirmware(Firmware& firmware, bool overridesettings) noexcept } // Loads ROM data without parsing it. Works for GBA and NDS ROMs. -bool LoadROMData(const QStringList& filepath, std::unique_ptr& filedata, u32& filelen, string& basepath, string& romname) noexcept +bool EmuInstance::loadROMData(const QStringList& filepath, std::unique_ptr& filedata, u32& filelen, string& basepath, string& romname) noexcept { if (filepath.empty()) return false; @@ -1243,7 +1591,7 @@ bool LoadROMData(const QStringList& filepath, std::unique_ptr& filedata, u if (filename.length() > 4 && filename.substr(filename.length() - 4) == ".zst") { - filelen = DecompressROM(filedata.get(), len, filedata); + filelen = decompressROM(filedata.get(), len, filedata); if (filelen > 0) { @@ -1259,7 +1607,7 @@ bool LoadROMData(const QStringList& filepath, std::unique_ptr& filedata, u } } - int pos = LastSep(filename); + int pos = lastSep(filename); if(pos != -1) basepath = filename.substr(0, pos); @@ -1281,10 +1629,10 @@ bool LoadROMData(const QStringList& filepath, std::unique_ptr& filedata, u } std::string std_archivepath = filepath.at(0).toStdString(); - basepath = std_archivepath.substr(0, LastSep(std_archivepath)); + basepath = std_archivepath.substr(0, lastSep(std_archivepath)); std::string std_romname = filepath.at(1).toStdString(); - romname = std_romname.substr(LastSep(std_romname)+1); + romname = std_romname.substr(lastSep(std_romname)+1); return true; } #endif @@ -1292,7 +1640,7 @@ bool LoadROMData(const QStringList& filepath, std::unique_ptr& filedata, u return false; } -QString GetSavErrorString(std::string& filepath, bool gba) +QString EmuInstance::getSavErrorString(std::string& filepath, bool gba) { std::string console = gba ? "GBA" : "DS"; std::string err1 = "Unable to write to "; @@ -1303,38 +1651,38 @@ QString GetSavErrorString(std::string& filepath, bool gba) return QString::fromStdString(err1); } -bool LoadROM(QMainWindow* mainWindow, EmuThread* emuthread, QStringList filepath, bool reset) +bool EmuInstance::loadROM(QStringList filepath, bool reset) { unique_ptr filedata = nullptr; u32 filelen; std::string basepath; std::string romname; - if (!LoadROMData(filepath, filedata, filelen, basepath, romname)) + if (!loadROMData(filepath, filedata, filelen, basepath, romname)) { QMessageBox::critical(mainWindow, "melonDS", "Failed to load the DS ROM."); return false; } - NDSSave = nullptr; + ndsSave = nullptr; - BaseROMDir = basepath; - BaseROMName = romname; - BaseAssetName = romname.substr(0, romname.rfind('.')); + baseROMDir = basepath; + baseROMName = romname; + baseAssetName = romname.substr(0, romname.rfind('.')); u32 savelen = 0; std::unique_ptr savedata = nullptr; - std::string savname = GetAssetPath(false, Config::SaveFilePath, ".sav"); + std::string savname = getAssetPath(false, globalCfg.GetString("SaveFilePath"), ".sav"); std::string origsav = savname; - savname += Platform::InstanceFileSuffix(); + savname += instanceFileSuffix(); FileHandle* sav = Platform::OpenFile(savname, FileMode::Read); if (!sav) { if (!Platform::CheckFileWritable(origsav)) { - QMessageBox::critical(mainWindow, "melonDS", GetSavErrorString(origsav, false)); + QMessageBox::critical(mainWindow, "melonDS", getSavErrorString(origsav, false)); return false; } @@ -1342,7 +1690,7 @@ bool LoadROM(QMainWindow* mainWindow, EmuThread* emuthread, QStringList filepath } else if (!Platform::CheckFileWritable(savname)) { - QMessageBox::critical(mainWindow, "melonDS", GetSavErrorString(savname, false)); + QMessageBox::critical(mainWindow, "melonDS", getSavErrorString(savname, false)); return false; } @@ -1357,15 +1705,15 @@ bool LoadROM(QMainWindow* mainWindow, EmuThread* emuthread, QStringList filepath } NDSCart::NDSCartArgs cartargs { - // Don't load the SD card itself yet, because we don't know if - // the ROM is homebrew or not. - // So this is the card we *would* load if the ROM were homebrew. - .SDCard = GetDLDISDCardArgs(), - .SRAM = std::move(savedata), - .SRAMLength = savelen, + // Don't load the SD card itself yet, because we don't know if + // the ROM is homebrew or not. + // So this is the card we *would* load if the ROM were homebrew. + .SDCard = getSDCardArgs("DLDI"), + .SRAM = std::move(savedata), + .SRAMLength = savelen, }; - auto cart = NDSCart::ParseROM(std::move(filedata), filelen, std::move(cartargs)); + auto cart = NDSCart::ParseROM(std::move(filedata), filelen, this, std::move(cartargs)); if (!cart) { // If we couldn't parse the ROM... @@ -1375,61 +1723,61 @@ bool LoadROM(QMainWindow* mainWindow, EmuThread* emuthread, QStringList filepath if (reset) { - if (!emuthread->UpdateConsole(std::move(cart), Keep {})) + if (!updateConsole(std::move(cart), Keep {})) { QMessageBox::critical(mainWindow, "melonDS", "Failed to load the DS ROM."); return false; } - InitFirmwareSaveManager(emuthread); - emuthread->NDS->Reset(); + initFirmwareSaveManager(); + nds->Reset(); - if (Config::DirectBoot || emuthread->NDS->NeedsDirectBoot()) + if (globalCfg.GetBool("Emu.DirectBoot") || nds->NeedsDirectBoot()) { // If direct boot is enabled or forced... - emuthread->NDS->SetupDirectBoot(romname); + nds->SetupDirectBoot(romname); } - SetBatteryLevels(*emuthread->NDS); - SetDateTime(*emuthread->NDS); + setBatteryLevels(); + setDateTime(); } else { - assert(emuthread->NDS != nullptr); - emuthread->NDS->SetNDSCart(std::move(cart)); + assert(nds != nullptr); + nds->SetNDSCart(std::move(cart)); } - CartType = 0; - NDSSave = std::make_unique(savname); - LoadCheats(*emuthread->NDS); + cartType = 0; + ndsSave = std::make_unique(savname); + loadCheats(); return true; // success } -void EjectCart(NDS& nds) +void EmuInstance::ejectCart() { - NDSSave = nullptr; + ndsSave = nullptr; - UnloadCheats(nds); + unloadCheats(); - nds.EjectCart(); + nds->EjectCart(); - CartType = -1; - BaseROMDir = ""; - BaseROMName = ""; - BaseAssetName = ""; + cartType = -1; + baseROMDir = ""; + baseROMName = ""; + baseAssetName = ""; } -bool CartInserted() +bool EmuInstance::cartInserted() { - return CartType != -1; + return cartType != -1; } -QString CartLabel() +QString EmuInstance::cartLabel() { - if (CartType == -1) + if (cartType == -1) return "(none)"; - QString ret = QString::fromStdString(BaseROMName); + QString ret = QString::fromStdString(baseROMName); int maxlen = 32; if (ret.length() > maxlen) @@ -1439,9 +1787,9 @@ QString CartLabel() } -bool LoadGBAROM(QMainWindow* mainWindow, NDS& nds, QStringList filepath) +bool EmuInstance::loadGBAROM(QStringList filepath) { - if (nds.ConsoleType == 1) + if (nds->ConsoleType == 1) { QMessageBox::critical(mainWindow, "melonDS", "The DSi doesn't have a GBA slot."); return false; @@ -1452,31 +1800,31 @@ bool LoadGBAROM(QMainWindow* mainWindow, NDS& nds, QStringList filepath) std::string basepath; std::string romname; - if (!LoadROMData(filepath, filedata, filelen, basepath, romname)) + if (!loadROMData(filepath, filedata, filelen, basepath, romname)) { QMessageBox::critical(mainWindow, "melonDS", "Failed to load the GBA ROM."); return false; } - GBASave = nullptr; + gbaSave = nullptr; - BaseGBAROMDir = basepath; - BaseGBAROMName = romname; - BaseGBAAssetName = romname.substr(0, romname.rfind('.')); + baseGBAROMDir = basepath; + baseGBAROMName = romname; + baseGBAAssetName = romname.substr(0, romname.rfind('.')); u32 savelen = 0; std::unique_ptr savedata = nullptr; - std::string savname = GetAssetPath(true, Config::SaveFilePath, ".sav"); + std::string savname = getAssetPath(true, globalCfg.GetString("SaveFilePath"), ".sav"); std::string origsav = savname; - savname += Platform::InstanceFileSuffix(); + savname += instanceFileSuffix(); FileHandle* sav = Platform::OpenFile(savname, FileMode::Read); if (!sav) { if (!Platform::CheckFileWritable(origsav)) { - QMessageBox::critical(mainWindow, "melonDS", GetSavErrorString(origsav, true)); + QMessageBox::critical(mainWindow, "melonDS", getSavErrorString(origsav, true)); return false; } @@ -1484,7 +1832,7 @@ bool LoadGBAROM(QMainWindow* mainWindow, NDS& nds, QStringList filepath) } else if (!Platform::CheckFileWritable(savname)) { - QMessageBox::critical(mainWindow, "melonDS", GetSavErrorString(savname, true)); + QMessageBox::critical(mainWindow, "melonDS", getSavErrorString(savname, true)); return false; } @@ -1501,59 +1849,59 @@ bool LoadGBAROM(QMainWindow* mainWindow, NDS& nds, QStringList filepath) CloseFile(sav); } - auto cart = GBACart::ParseROM(std::move(filedata), filelen, std::move(savedata), savelen); + auto cart = GBACart::ParseROM(std::move(filedata), filelen, std::move(savedata), savelen, this); if (!cart) { QMessageBox::critical(mainWindow, "melonDS", "Failed to load the GBA ROM."); return false; } - nds.SetGBACart(std::move(cart)); - GBACartType = 0; - GBASave = std::make_unique(savname); + nds->SetGBACart(std::move(cart)); + gbaCartType = 0; + gbaSave = std::make_unique(savname); return true; } -void LoadGBAAddon(NDS& nds, int type) +void EmuInstance::loadGBAAddon(int type) { - if (Config::ConsoleType == 1) return; + if (nds->ConsoleType == 1) return; - GBASave = nullptr; + gbaSave = nullptr; - nds.LoadGBAAddon(type); + nds->LoadGBAAddon(type); - GBACartType = type; - BaseGBAROMDir = ""; - BaseGBAROMName = ""; - BaseGBAAssetName = ""; + gbaCartType = type; + baseGBAROMDir = ""; + baseGBAROMName = ""; + baseGBAAssetName = ""; } -void EjectGBACart(NDS& nds) +void EmuInstance::ejectGBACart() { - GBASave = nullptr; + gbaSave = nullptr; - nds.EjectGBACart(); + nds->EjectGBACart(); - GBACartType = -1; - BaseGBAROMDir = ""; - BaseGBAROMName = ""; - BaseGBAAssetName = ""; + gbaCartType = -1; + baseGBAROMDir = ""; + baseGBAROMName = ""; + baseGBAAssetName = ""; } -bool GBACartInserted() +bool EmuInstance::gbaCartInserted() { - return GBACartType != -1; + return gbaCartType != -1; } -QString GBACartLabel() +QString EmuInstance::gbaCartLabel() { - if (Config::ConsoleType == 1) return "none (DSi)"; + if (nds->ConsoleType == 1) return "none (DSi)"; - switch (GBACartType) + switch (gbaCartType) { - case 0: + case 0: { - QString ret = QString::fromStdString(BaseGBAROMName); + QString ret = QString::fromStdString(baseGBAROMName); int maxlen = 32; if (ret.length() > maxlen) @@ -1562,15 +1910,15 @@ QString GBACartLabel() return ret; } - case GBAAddon_RAMExpansion: - return "Memory expansion"; + case GBAAddon_RAMExpansion: + return "Memory expansion"; } return "(none)"; } -void ROMIcon(const u8 (&data)[512], const u16 (&palette)[16], u32 (&iconRef)[32*32]) +void EmuInstance::romIcon(const u8 (&data)[512], const u16 (&palette)[16], u32 (&iconRef)[32*32]) { u32 paletteRGBA[16]; for (int i = 0; i < 16; i++) @@ -1606,14 +1954,14 @@ void ROMIcon(const u8 (&data)[512], const u16 (&palette)[16], u32 (&iconRef)[32* #define SEQ_BMP(i) ((i & 0b0000011100000000) >> 8) #define SEQ_DUR(i) ((i & 0b0000000011111111) >> 0) -void AnimatedROMIcon(const u8 (&data)[8][512], const u16 (&palette)[8][16], const u16 (&sequence)[64], u32 (&animatedIconRef)[64][32*32], std::vector &animatedSequenceRef) +void EmuInstance::animatedROMIcon(const u8 (&data)[8][512], const u16 (&palette)[8][16], const u16 (&sequence)[64], u32 (&animatedIconRef)[64][32*32], std::vector &animatedSequenceRef) { for (int i = 0; i < 64; i++) { if (!sequence[i]) break; - ROMIcon(data[SEQ_BMP(sequence[i])], palette[SEQ_PAL(sequence[i])], animatedIconRef[i]); + romIcon(data[SEQ_BMP(sequence[i])], palette[SEQ_PAL(sequence[i])], animatedIconRef[i]); u32* frame = animatedIconRef[i]; if (SEQ_FLIPH(sequence[i])) @@ -1641,5 +1989,3 @@ void AnimatedROMIcon(const u8 (&data)[8][512], const u16 (&palette)[8][16], cons animatedSequenceRef.push_back(i); } } - -} diff --git a/src/frontend/qt_sdl/EmuInstance.h b/src/frontend/qt_sdl/EmuInstance.h new file mode 100644 index 0000000000..917e6e0672 --- /dev/null +++ b/src/frontend/qt_sdl/EmuInstance.h @@ -0,0 +1,304 @@ +/* + Copyright 2016-2023 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 EMUINSTANCE_H +#define EMUINSTANCE_H + +#include + +#include "NDS.h" +#include "EmuThread.h" +#include "Window.h" +#include "Config.h" +#include "SaveManager.h" + +const int kMaxWindows = 16; + +enum +{ + HK_Lid = 0, + HK_Mic, + HK_Pause, + HK_Reset, + HK_FastForward, + HK_FastForwardToggle, + HK_FullscreenToggle, + HK_SwapScreens, + HK_SwapScreenEmphasis, + HK_SolarSensorDecrease, + HK_SolarSensorIncrease, + HK_FrameStep, + HK_PowerButton, + HK_VolumeUp, + HK_VolumeDown, + HK_MAX +}; + +enum +{ + micInputType_Silence, + micInputType_External, + micInputType_Noise, + micInputType_Wav, + micInputType_MAX, +}; + +enum +{ + renderer3D_Software = 0, +#ifdef OGLRENDERER_ENABLED + renderer3D_OpenGL, + renderer3D_OpenGLCompute, +#endif + renderer3D_Max, +}; + +bool isRightModKey(QKeyEvent* event); +int getEventKeyVal(QKeyEvent* event); + +class EmuInstance +{ +public: + EmuInstance(int inst); + ~EmuInstance(); + + int getInstanceID() { return instanceID; } + EmuThread* getEmuThread() { return emuThread; } + melonDS::NDS* getNDS() { return nds; } + + MainWindow* getMainWindow() { return mainWindow; } + MainWindow* getWindow(int id) { return windowList[id]; } + + Config::Table& getGlobalConfig() { return globalCfg; } + Config::Table& getLocalConfig() { return localCfg; } + + std::string instanceFileSuffix(); + + void createWindow(); + + void osdAddMessage(unsigned int color, const char* fmt, ...); + + bool emuIsActive(); + void emuStop(melonDS::Platform::StopReason reason); + + bool usesOpenGL(); + void initOpenGL(); + void deinitOpenGL(); + void setVSyncGL(bool vsync); + void makeCurrentGL(); + void drawScreenGL(); + + // return: empty string = setup OK, non-empty = error message + QString verifySetup(); + + bool updateConsole(UpdateConsoleNDSArgs&& ndsargs, UpdateConsoleGBAArgs&& gbaargs) noexcept; + + void enableCheats(bool enable); + melonDS::ARCodeFile* getCheatFile(); + + void romIcon(const melonDS::u8 (&data)[512], + const melonDS::u16 (&palette)[16], + melonDS::u32 (&iconRef)[32*32]); + void animatedROMIcon(const melonDS::u8 (&data)[8][512], + const melonDS::u16 (&palette)[8][16], + const melonDS::u16 (&sequence)[64], + melonDS::u32 (&animatedIconRef)[64][32*32], + std::vector &animatedSequenceRef); + + static const char* buttonNames[12]; + static const char* hotkeyNames[HK_MAX]; + + void inputInit(); + void inputDeInit(); + void inputLoadConfig(); + + void setJoystick(int id); + int getJoystickID() { return joystickID; } + SDL_Joystick* getJoystick() { return joystick; } + +private: + static int lastSep(const std::string& path); + std::string getAssetPath(bool gba, const std::string& configpath, const std::string& ext, const std::string& file); + + QString verifyDSBIOS(); + QString verifyDSiBIOS(); + QString verifyDSFirmware(); + QString verifyDSiFirmware(); + QString verifyDSiNAND(); + + std::string getEffectiveFirmwareSavePath(); + void initFirmwareSaveManager() noexcept; + std::string getSavestateName(int slot); + bool savestateExists(int slot); + bool loadState(const std::string& filename); + bool saveState(const std::string& filename); + void undoStateLoad(); + void unloadCheats(); + void loadCheats(); + std::unique_ptr loadARM9BIOS() noexcept; + std::unique_ptr loadARM7BIOS() noexcept; + std::unique_ptr loadDSiARM9BIOS() noexcept; + std::unique_ptr loadDSiARM7BIOS() noexcept; + melonDS::Firmware generateFirmware(int type) noexcept; + std::optional loadFirmware(int type) noexcept; + std::optional loadNAND(const std::array& arm7ibios) noexcept; + std::optional getSDCardArgs(const std::string& key) noexcept; + std::optional loadSDCard(const std::string& key) noexcept; + void setBatteryLevels(); + void setDateTime(); + void reset(); + bool bootToMenu(); + melonDS::u32 decompressROM(const melonDS::u8* inContent, const melonDS::u32 inSize, std::unique_ptr& outContent); + void clearBackupState(); + std::pair, std::string> generateDefaultFirmware(); + bool parseMacAddress(void* data); + void customizeFirmware(melonDS::Firmware& firmware, bool overridesettings) noexcept; + bool loadROMData(const QStringList& filepath, std::unique_ptr& filedata, melonDS::u32& filelen, std::string& basepath, std::string& romname) noexcept; + QString getSavErrorString(std::string& filepath, bool gba); + bool loadROM(QStringList filepath, bool reset); + void ejectCart(); + bool cartInserted(); + QString cartLabel(); + bool loadGBAROM(QStringList filepath); + void loadGBAAddon(int type); + void ejectGBACart(); + bool gbaCartInserted(); + QString gbaCartLabel(); + + void audioInit(); + void audioDeInit(); + void audioEnable(); + void audioDisable(); + void audioMute(); + void audioSync(); + void audioUpdateSettings(); + + void micOpen(); + void micClose(); + void micLoadWav(const std::string& name); + void micProcess(); + void setupMicInputData(); + + int audioGetNumSamplesOut(int outlen); + void audioResample(melonDS::s16* inbuf, int inlen, melonDS::s16* outbuf, int outlen, int volume); + + static void audioCallback(void* data, Uint8* stream, int len); + static void micCallback(void* data, Uint8* stream, int len); + + void onKeyPress(QKeyEvent* event); + void onKeyRelease(QKeyEvent* event); + void keyReleaseAll(); + + void openJoystick(); + void closeJoystick(); + bool joystickButtonDown(int val); + + void inputProcess(); + + bool hotkeyDown(int id) { return hotkeyMask & (1< ndsSave; + std::unique_ptr gbaSave; + std::unique_ptr firmwareSave; + + bool doLimitFPS; + int maxFPS; + bool doAudioSync; +private: + + std::unique_ptr backupState; + bool savestateLoaded; + std::string previousSaveFile; + + melonDS::ARCodeFile* cheatFile; + bool cheatsOn; + + SDL_AudioDeviceID audioDevice; + int audioFreq; + float audioSampleFrac; + bool audioMuted; + SDL_cond* audioSyncCond; + SDL_mutex* audioSyncLock; + + int mpAudioMode; + + SDL_AudioDeviceID micDevice; + melonDS::s16 micExtBuffer[2048]; + melonDS::u32 micExtBufferWritePos; + + melonDS::u32 micWavLength; + melonDS::s16* micWavBuffer; + + melonDS::s16* micBuffer; + melonDS::u32 micBufferLength; + melonDS::u32 micBufferReadPos; + + //int audioInterp; + int audioVolume; + bool audioDSiVolumeSync; + int micInputType; + std::string micDeviceName; + std::string micWavPath; + + int keyMapping[12]; + int joyMapping[12]; + int hkKeyMapping[HK_MAX]; + int hkJoyMapping[HK_MAX]; + + int joystickID; + SDL_Joystick* joystick; + + melonDS::u32 keyInputMask, joyInputMask; + melonDS::u32 keyHotkeyMask, joyHotkeyMask; + melonDS::u32 hotkeyMask, lastHotkeyMask; + melonDS::u32 hotkeyPress, hotkeyRelease; + + melonDS::u32 inputMask; + + friend class EmuThread; + friend class MainWindow; +}; + +#endif //EMUINSTANCE_H diff --git a/src/frontend/qt_sdl/EmuInstanceAudio.cpp b/src/frontend/qt_sdl/EmuInstanceAudio.cpp new file mode 100644 index 0000000000..e7bc71dd4b --- /dev/null +++ b/src/frontend/qt_sdl/EmuInstanceAudio.cpp @@ -0,0 +1,457 @@ +/* + Copyright 2016-2023 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 "Config.h" +#include "NDS.h" +#include "SPU.h" +#include "Platform.h" +#include "main.h" + +#include "mic_blow.h" + +using namespace melonDS; + + +int EmuInstance::audioGetNumSamplesOut(int outlen) +{ + float f_len_in = (outlen * 32823.6328125) / (float)audioFreq; + f_len_in += audioSampleFrac; + int len_in = (int)floor(f_len_in); + audioSampleFrac = f_len_in - len_in; + + return len_in; +} + +void EmuInstance::audioResample(s16* inbuf, int inlen, s16* outbuf, int outlen, int volume) +{ + float res_incr = inlen / (float)outlen; + float res_timer = 0; + int res_pos = 0; + + for (int i = 0; i < outlen; i++) + { + outbuf[i*2 ] = (inbuf[res_pos*2 ] * volume) >> 8; + outbuf[i*2+1] = (inbuf[res_pos*2+1] * volume) >> 8; + + res_timer += res_incr; + while (res_timer >= 1.0) + { + res_timer -= 1.0; + res_pos++; + } + } +} + +void EmuInstance::audioCallback(void* data, Uint8* stream, int len) +{ + EmuInstance* inst = (EmuInstance*)data; + len /= (sizeof(s16) * 2); + + // resample incoming audio to match the output sample rate + + int len_in = inst->audioGetNumSamplesOut(len); + s16 buf_in[1024*2]; + int num_in; + + SDL_LockMutex(inst->audioSyncLock); + num_in = inst->nds->SPU.ReadOutput(buf_in, len_in); + SDL_CondSignal(inst->audioSyncCond); + SDL_UnlockMutex(inst->audioSyncLock); + + if ((num_in < 1) || inst->audioMuted) + { + memset(stream, 0, len*sizeof(s16)*2); + return; + } + + int margin = 6; + if (num_in < len_in-margin) + { + int last = num_in-1; + + for (int i = num_in; i < len_in-margin; i++) + ((u32*)buf_in)[i] = ((u32*)buf_in)[last]; + + num_in = len_in-margin; + } + + inst->audioResample(buf_in, num_in, (s16*)stream, len, inst->audioVolume); +} + +void EmuInstance::micCallback(void* data, Uint8* stream, int len) +{ + EmuInstance* inst = (EmuInstance*)data; + s16* input = (s16*)stream; + len /= sizeof(s16); + + int maxlen = sizeof(micExtBuffer) / sizeof(s16); + + if ((inst->micExtBufferWritePos + len) > maxlen) + { + u32 len1 = maxlen - inst->micExtBufferWritePos; + memcpy(&inst->micExtBuffer[inst->micExtBufferWritePos], &input[0], len1*sizeof(s16)); + memcpy(&inst->micExtBuffer[0], &input[len1], (len - len1)*sizeof(s16)); + inst->micExtBufferWritePos = len - len1; + } + else + { + memcpy(&inst->micExtBuffer[inst->micExtBufferWritePos], input, len*sizeof(s16)); + inst->micExtBufferWritePos += len; + } +} + +void EmuInstance::audioMute() +{ + audioMuted = false; + + switch (mpAudioMode) + { + case 1: // only instance 1 + if (instanceID > 0) audioMuted = true; + break; + + case 2: // only currently focused instance + //if (mainWindow != nullptr) + // audioMuted = !mainWindow->isActiveWindow(); + // TODO!! + printf("TODO!! audioMute mode 2\n"); + break; + } +} + + +void EmuInstance::micOpen() +{ + if (micInputType != micInputType_External) + { + micDevice = 0; + return; + } + + int numMics = SDL_GetNumAudioDevices(1); + if (numMics == 0) + return; + + SDL_AudioSpec whatIwant, whatIget; + memset(&whatIwant, 0, sizeof(SDL_AudioSpec)); + whatIwant.freq = 44100; + whatIwant.format = AUDIO_S16LSB; + whatIwant.channels = 1; + whatIwant.samples = 1024; + whatIwant.callback = micCallback; + whatIwant.userdata = this; + const char* mic = NULL; + if (micDeviceName != "") + { + mic = micDeviceName.c_str(); + } + micDevice = SDL_OpenAudioDevice(mic, 1, &whatIwant, &whatIget, 0); + if (!micDevice) + { + Platform::Log(Platform::LogLevel::Error, "Mic init failed: %s\n", SDL_GetError()); + } + else + { + SDL_PauseAudioDevice(micDevice, 0); + } +} + +void EmuInstance::micClose() +{ + if (micDevice) + SDL_CloseAudioDevice(micDevice); + + micDevice = 0; +} + +void EmuInstance::micLoadWav(const std::string& name) +{ + SDL_AudioSpec format; + memset(&format, 0, sizeof(SDL_AudioSpec)); + + if (micWavBuffer) delete[] micWavBuffer; + micWavBuffer = nullptr; + micWavLength = 0; + + u8* buf; + u32 len; + if (!SDL_LoadWAV(name.c_str(), &format, &buf, &len)) + return; + + const u64 dstfreq = 44100; + + int srcinc = format.channels; + len /= ((SDL_AUDIO_BITSIZE(format.format) / 8) * srcinc); + + micWavLength = (len * dstfreq) / format.freq; + if (micWavLength < 735) micWavLength = 735; + micWavBuffer = new s16[micWavLength]; + + float res_incr = len / (float)micWavLength; + float res_timer = 0; + int res_pos = 0; + + for (int i = 0; i < micWavLength; i++) + { + u16 val = 0; + + switch (SDL_AUDIO_BITSIZE(format.format)) + { + case 8: + val = buf[res_pos] << 8; + break; + + case 16: + if (SDL_AUDIO_ISBIGENDIAN(format.format)) + val = (buf[res_pos*2] << 8) | buf[res_pos*2 + 1]; + else + val = (buf[res_pos*2 + 1] << 8) | buf[res_pos*2]; + break; + + case 32: + if (SDL_AUDIO_ISFLOAT(format.format)) + { + u32 rawval; + if (SDL_AUDIO_ISBIGENDIAN(format.format)) + rawval = (buf[res_pos*4] << 24) | (buf[res_pos*4 + 1] << 16) | (buf[res_pos*4 + 2] << 8) | buf[res_pos*4 + 3]; + else + rawval = (buf[res_pos*4 + 3] << 24) | (buf[res_pos*4 + 2] << 16) | (buf[res_pos*4 + 1] << 8) | buf[res_pos*4]; + + float fval = *(float*)&rawval; + s32 ival = (s32)(fval * 0x8000); + ival = std::clamp(ival, -0x8000, 0x7FFF); + val = (s16)ival; + } + else if (SDL_AUDIO_ISBIGENDIAN(format.format)) + val = (buf[res_pos*4] << 8) | buf[res_pos*4 + 1]; + else + val = (buf[res_pos*4 + 3] << 8) | buf[res_pos*4 + 2]; + break; + } + + if (SDL_AUDIO_ISUNSIGNED(format.format)) + val ^= 0x8000; + + micWavBuffer[i] = val; + + res_timer += res_incr; + while (res_timer >= 1.0) + { + res_timer -= 1.0; + res_pos += srcinc; + } + } + + SDL_FreeWAV(buf); +} + +void EmuInstance::micProcess() +{ + int type = micInputType; + bool cmd = hotkeyDown(HK_Mic); + + if (type != micInputType_External && !cmd) + { + type = micInputType_Silence; + } + + switch (type) + { + case micInputType_Silence: // no mic + micBufferReadPos = 0; + nds->MicInputFrame(nullptr, 0); + break; + + case micInputType_External: // host mic + case micInputType_Wav: // WAV + if (micBuffer) + { + if ((micBufferReadPos + 735) > micBufferLength) + { + s16 tmp[735]; + u32 len1 = micBufferLength - micBufferReadPos; + memcpy(&tmp[0], &micBuffer[micBufferReadPos], len1*sizeof(s16)); + memcpy(&tmp[len1], &micBuffer[0], (735 - len1)*sizeof(s16)); + + nds->MicInputFrame(tmp, 735); + micBufferReadPos = 735 - len1; + } + else + { + nds->MicInputFrame(&micBuffer[micBufferReadPos], 735); + micBufferReadPos += 735; + } + } + else + { + micBufferReadPos = 0; + nds->MicInputFrame(nullptr, 0); + } + break; + + case micInputType_Noise: // blowing noise + { + int sample_len = sizeof(mic_blow) / sizeof(u16); + static int sample_pos = 0; + + s16 tmp[735]; + + for (int i = 0; i < 735; i++) + { + tmp[i] = mic_blow[sample_pos]; + sample_pos++; + if (sample_pos >= sample_len) sample_pos = 0; + } + + nds->MicInputFrame(tmp, 735); + } + break; + } +} + +void EmuInstance::setupMicInputData() +{ + if (micWavBuffer != nullptr) + { + delete[] micWavBuffer; + micWavBuffer = nullptr; + micWavLength = 0; + } + + micInputType = globalCfg.GetInt("Mic.InputType"); + micDeviceName = globalCfg.GetString("Mic.Device"); + micWavPath = globalCfg.GetString("Mic.WavPath"); + + switch (micInputType) + { + case micInputType_Silence: + case micInputType_Noise: + micBuffer = nullptr; + micBufferLength = 0; + break; + case micInputType_External: + micBuffer = micExtBuffer; + micBufferLength = sizeof(micExtBuffer)/sizeof(s16); + break; + case micInputType_Wav: + micLoadWav(micWavPath); + micBuffer = micWavBuffer; + micBufferLength = micWavLength; + break; + } + + micBufferReadPos = 0; +} + +void EmuInstance::audioInit() +{ + audioVolume = localCfg.GetInt("Audio.Volume"); + audioDSiVolumeSync = localCfg.GetBool("Audio.DSiVolumeSync"); + + audioMuted = false; + audioSyncCond = SDL_CreateCond(); + audioSyncLock = SDL_CreateMutex(); + + audioFreq = 48000; // TODO: make configurable? + SDL_AudioSpec whatIwant, whatIget; + memset(&whatIwant, 0, sizeof(SDL_AudioSpec)); + whatIwant.freq = audioFreq; + whatIwant.format = AUDIO_S16LSB; + whatIwant.channels = 2; + whatIwant.samples = 1024; + whatIwant.callback = audioCallback; + whatIwant.userdata = this; + audioDevice = SDL_OpenAudioDevice(NULL, 0, &whatIwant, &whatIget, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE); + if (!audioDevice) + { + Platform::Log(Platform::LogLevel::Error, "Audio init failed: %s\n", SDL_GetError()); + } + else + { + audioFreq = whatIget.freq; + Platform::Log(Platform::LogLevel::Info, "Audio output frequency: %d Hz\n", audioFreq); + SDL_PauseAudioDevice(audioDevice, 1); + } + + audioSampleFrac = 0; + + micDevice = 0; + + memset(micExtBuffer, 0, sizeof(micExtBuffer)); + micExtBufferWritePos = 0; + micWavBuffer = nullptr; + + micBuffer = nullptr; + micBufferLength = 0; + micBufferReadPos = 0; + + setupMicInputData(); +} + +void EmuInstance::audioDeInit() +{ + if (audioDevice) SDL_CloseAudioDevice(audioDevice); + audioDevice = 0; + micClose(); + + if (audioSyncCond) SDL_DestroyCond(audioSyncCond); + audioSyncCond = nullptr; + + if (audioSyncLock) SDL_DestroyMutex(audioSyncLock); + audioSyncLock = nullptr; + + if (micWavBuffer) delete[] micWavBuffer; + micWavBuffer = nullptr; +} + +void EmuInstance::audioSync() +{ + if (audioDevice) + { + SDL_LockMutex(audioSyncLock); + while (nds->SPU.GetOutputSize() > 1024) + { + int ret = SDL_CondWaitTimeout(audioSyncCond, audioSyncLock, 500); + if (ret == SDL_MUTEX_TIMEDOUT) break; + } + SDL_UnlockMutex(audioSyncLock); + } +} + +void EmuInstance::audioUpdateSettings() +{ + micClose(); + + int audiointerp = globalCfg.GetInt("Audio.Interpolation"); + nds->SPU.SetInterpolation(static_cast(audiointerp)); + setupMicInputData(); + + micOpen(); +} + +void EmuInstance::audioEnable() +{ + if (audioDevice) SDL_PauseAudioDevice(audioDevice, 0); + micOpen(); +} + +void EmuInstance::audioDisable() +{ + if (audioDevice) SDL_PauseAudioDevice(audioDevice, 1); + micClose(); +} diff --git a/src/frontend/qt_sdl/EmuInstanceInput.cpp b/src/frontend/qt_sdl/EmuInstanceInput.cpp new file mode 100644 index 0000000000..f15fa686c2 --- /dev/null +++ b/src/frontend/qt_sdl/EmuInstanceInput.cpp @@ -0,0 +1,307 @@ +/* + Copyright 2016-2023 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 +#include + +#include "main.h" +#include "Config.h" + +using namespace melonDS; + +const char* EmuInstance::buttonNames[12] = +{ + "A", + "B", + "Select", + "Start", + "Right", + "Left", + "Up", + "Down", + "R", + "L", + "X", + "Y" +}; + +const char* EmuInstance::hotkeyNames[HK_MAX] = +{ + "HK_Lid", + "HK_Mic", + "HK_Pause", + "HK_Reset", + "HK_FastForward", + "HK_FastForwardToggle", + "HK_FullscreenToggle", + "HK_SwapScreens", + "HK_SwapScreenEmphasis", + "HK_SolarSensorDecrease", + "HK_SolarSensorIncrease", + "HK_FrameStep", + "HK_PowerButton", + "HK_VolumeUp", + "HK_VolumeDown" +}; + + +void EmuInstance::inputInit() +{ + keyInputMask = 0xFFF; + joyInputMask = 0xFFF; + inputMask = 0xFFF; + + keyHotkeyMask = 0; + joyHotkeyMask = 0; + hotkeyMask = 0; + lastHotkeyMask = 0; + + joystick = nullptr; + inputLoadConfig(); +} + +void EmuInstance::inputDeInit() +{ + closeJoystick(); +} + +void EmuInstance::inputLoadConfig() +{ + Config::Table keycfg = localCfg.GetTable("Keyboard"); + Config::Table joycfg = localCfg.GetTable("Joystick"); + + for (int i = 0; i < 12; i++) + { + keyMapping[i] = keycfg.GetInt(buttonNames[i]); + joyMapping[i] = joycfg.GetInt(buttonNames[i]); + } + + for (int i = 0; i < HK_MAX; i++) + { + hkKeyMapping[i] = keycfg.GetInt(hotkeyNames[i]); + hkJoyMapping[i] = joycfg.GetInt(hotkeyNames[i]); + } + + setJoystick(localCfg.GetInt("JoystickID")); +} + + +void EmuInstance::setJoystick(int id) +{ + joystickID = id; + openJoystick(); +} + +void EmuInstance::openJoystick() +{ + if (joystick) SDL_JoystickClose(joystick); + + int num = SDL_NumJoysticks(); + if (num < 1) + { + joystick = nullptr; + return; + } + + if (joystickID >= num) + joystickID = 0; + + joystick = SDL_JoystickOpen(joystickID); +} + +void EmuInstance::closeJoystick() +{ + if (joystick) + { + SDL_JoystickClose(joystick); + joystick = nullptr; + } +} + + +// distinguish between left and right modifier keys (Ctrl, Alt, Shift) +// Qt provides no real cross-platform way to do this, so here we go +// for Windows and Linux we can distinguish via scancodes (but both +// provide different scancodes) +bool isRightModKey(QKeyEvent* event) +{ +#ifdef __WIN32__ + quint32 scan = event->nativeScanCode(); + return (scan == 0x11D || scan == 0x138 || scan == 0x36); +#elif __APPLE__ + quint32 scan = event->nativeVirtualKey(); + return (scan == 0x36 || scan == 0x3C || scan == 0x3D || scan == 0x3E); +#else + quint32 scan = event->nativeScanCode(); + return (scan == 0x69 || scan == 0x6C || scan == 0x3E); +#endif +} + +int getEventKeyVal(QKeyEvent* event) +{ + int key = event->key(); + int mod = event->modifiers(); + bool ismod = (key == Qt::Key_Control || + key == Qt::Key_Alt || + key == Qt::Key_AltGr || + key == Qt::Key_Shift || + key == Qt::Key_Meta); + + if (!ismod) + key |= mod; + else if (isRightModKey(event)) + key |= (1<<31); + + return key; +} + + +void EmuInstance::onKeyPress(QKeyEvent* event) +{ + int keyHK = getEventKeyVal(event); + int keyKP = keyHK; + if (event->modifiers() != Qt::KeypadModifier) + keyKP &= ~event->modifiers(); + + for (int i = 0; i < 12; i++) + if (keyKP == keyMapping[i]) + keyInputMask &= ~(1<modifiers() != Qt::KeypadModifier) + keyKP &= ~event->modifiers(); + + for (int i = 0; i < 12; i++) + if (keyKP == keyMapping[i]) + keyInputMask |= (1<> 4) & 0xF; + int hatdir = val & 0xF; + Uint8 hatval = SDL_JoystickGetHat(joystick, hatnum); + + bool pressed = false; + if (hatdir == 0x1) pressed = (hatval & SDL_HAT_UP); + else if (hatdir == 0x4) pressed = (hatval & SDL_HAT_DOWN); + else if (hatdir == 0x2) pressed = (hatval & SDL_HAT_RIGHT); + else if (hatdir == 0x8) pressed = (hatval & SDL_HAT_LEFT); + + if (pressed) return true; + } + else + { + int btnnum = val & 0xFFFF; + Uint8 btnval = SDL_JoystickGetButton(joystick, btnnum); + + if (btnval) return true; + } + } + + if (val & 0x10000) + { + int axisnum = (val >> 24) & 0xF; + int axisdir = (val >> 20) & 0xF; + Sint16 axisval = SDL_JoystickGetAxis(joystick, axisnum); + + switch (axisdir) + { + case 0: // positive + if (axisval > 16384) return true; + break; + + case 1: // negative + if (axisval < -16384) return true; + break; + + case 2: // trigger + if (axisval > 0) return true; + break; + } + } + + return false; +} + +void EmuInstance::inputProcess() +{ + SDL_JoystickUpdate(); + + if (joystick) + { + if (!SDL_JoystickGetAttached(joystick)) + { + SDL_JoystickClose(joystick); + joystick = nullptr; + } + } + if (!joystick && (SDL_NumJoysticks() > 0)) + { + openJoystick(); + } + + joyInputMask = 0xFFF; + if (joystick) + { + for (int i = 0; i < 12; i++) + if (joystickButtonDown(joyMapping[i])) + joyInputMask &= ~(1 << i); + } + + inputMask = keyInputMask & joyInputMask; + + joyHotkeyMask = 0; + if (joystick) + { + for (int i = 0; i < HK_MAX; i++) + if (joystickButtonDown(hkJoyMapping[i])) + joyHotkeyMask |= (1 << i); + } + + hotkeyMask = keyHotkeyMask | joyHotkeyMask; + hotkeyPress = hotkeyMask & ~lastHotkeyMask; + hotkeyRelease = lastHotkeyMask & ~hotkeyMask; + lastHotkeyMask = hotkeyMask; +} diff --git a/src/frontend/qt_sdl/EmuSettingsDialog.cpp b/src/frontend/qt_sdl/EmuSettingsDialog.cpp index ca9c67160c..f907f258ad 100644 --- a/src/frontend/qt_sdl/EmuSettingsDialog.cpp +++ b/src/frontend/qt_sdl/EmuSettingsDialog.cpp @@ -16,11 +16,8 @@ with melonDS. If not, see http://www.gnu.org/licenses/. */ -#include #include #include -#include -#include #include "types.h" #include "Platform.h" @@ -28,17 +25,16 @@ #include "EmuSettingsDialog.h" #include "ui_EmuSettingsDialog.h" +#include "main.h" using namespace melonDS::Platform; using namespace melonDS; EmuSettingsDialog* EmuSettingsDialog::currentDlg = nullptr; -extern bool RunningSomething; - bool EmuSettingsDialog::needsReset = false; -inline void updateLastBIOSFolder(QString& filename) +inline void EmuSettingsDialog::updateLastBIOSFolder(QString& filename) { int pos = filename.lastIndexOf("/"); if (pos == -1) @@ -47,9 +43,11 @@ inline void updateLastBIOSFolder(QString& filename) } QString path_dir = filename.left(pos); - QString path_file = filename.mid(pos+1); + //QString path_file = filename.mid(pos+1); - Config::LastBIOSFolder = path_dir.toStdString(); + Config::Table cfg = Config::GetGlobalTable(); + cfg.SetQString("LastBIOSFolder", path_dir); + lastBIOSFolder = path_dir; } EmuSettingsDialog::EmuSettingsDialog(QWidget* parent) : QDialog(parent), ui(new Ui::EmuSettingsDialog) @@ -57,31 +55,37 @@ EmuSettingsDialog::EmuSettingsDialog(QWidget* parent) : QDialog(parent), ui(new ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose); - ui->chkExternalBIOS->setChecked(Config::ExternalBIOSEnable); - ui->txtBIOS9Path->setText(QString::fromStdString(Config::BIOS9Path)); - ui->txtBIOS7Path->setText(QString::fromStdString(Config::BIOS7Path)); - ui->txtFirmwarePath->setText(QString::fromStdString(Config::FirmwarePath)); + emuInstance = ((MainWindow*)parent)->getEmuInstance(); + auto& cfg = emuInstance->getGlobalConfig(); + auto& instcfg = emuInstance->getLocalConfig(); + + lastBIOSFolder = cfg.GetQString("LastBIOSFolder"); - ui->txtDSiBIOS9Path->setText(QString::fromStdString(Config::DSiBIOS9Path)); - ui->txtDSiBIOS7Path->setText(QString::fromStdString(Config::DSiBIOS7Path)); - ui->txtDSiFirmwarePath->setText(QString::fromStdString(Config::DSiFirmwarePath)); - ui->txtDSiNANDPath->setText(QString::fromStdString(Config::DSiNANDPath)); + ui->chkExternalBIOS->setChecked(cfg.GetBool("Emu.ExternalBIOSEnable")); + ui->txtBIOS9Path->setText(cfg.GetQString("DS.BIOS9Path")); + ui->txtBIOS7Path->setText(cfg.GetQString("DS.BIOS7Path")); + ui->txtFirmwarePath->setText(cfg.GetQString("DS.FirmwarePath")); + + ui->txtDSiBIOS9Path->setText(cfg.GetQString("DSi.BIOS9Path")); + ui->txtDSiBIOS7Path->setText(cfg.GetQString("DSi.BIOS7Path")); + ui->txtDSiFirmwarePath->setText(cfg.GetQString("DSi.FirmwarePath")); + ui->txtDSiNANDPath->setText(cfg.GetQString("DSi.NANDPath")); ui->cbxConsoleType->addItem("DS"); ui->cbxConsoleType->addItem("DSi (experimental)"); - ui->cbxConsoleType->setCurrentIndex(Config::ConsoleType); + ui->cbxConsoleType->setCurrentIndex(cfg.GetInt("Emu.ConsoleType")); - ui->chkDirectBoot->setChecked(Config::DirectBoot); + ui->chkDirectBoot->setChecked(cfg.GetBool("Emu.DirectBoot")); #ifdef JIT_ENABLED - ui->chkEnableJIT->setChecked(Config::JIT_Enable); - ui->chkJITBranchOptimisations->setChecked(Config::JIT_BranchOptimisations); - ui->chkJITLiteralOptimisations->setChecked(Config::JIT_LiteralOptimisations); - ui->chkJITFastMemory->setChecked(Config::JIT_FastMemory); + ui->chkEnableJIT->setChecked(cfg.GetBool("JIT.Enable")); + ui->chkJITBranchOptimisations->setChecked(cfg.GetBool("JIT.BranchOptimisations")); + ui->chkJITLiteralOptimisations->setChecked(cfg.GetBool("JIT.LiteralOptimisations")); + ui->chkJITFastMemory->setChecked(cfg.GetBool("JIT.FastMemory")); #ifdef __APPLE__ ui->chkJITFastMemory->setDisabled(true); #endif - ui->spnJITMaximumBlockSize->setValue(Config::JIT_MaxBlockSize); + ui->spnJITMaximumBlockSize->setValue(cfg.GetInt("JIT.MaxBlockSize")); #else ui->chkEnableJIT->setDisabled(true); ui->chkJITBranchOptimisations->setDisabled(true); @@ -91,11 +95,11 @@ EmuSettingsDialog::EmuSettingsDialog(QWidget* parent) : QDialog(parent), ui(new #endif #ifdef GDBSTUB_ENABLED - ui->cbGdbEnabled->setChecked(Config::GdbEnabled); - ui->intGdbPortA7->setValue(Config::GdbPortARM7); - ui->intGdbPortA9->setValue(Config::GdbPortARM9); - ui->cbGdbBOSA7->setChecked(Config::GdbARM7BreakOnStartup); - ui->cbGdbBOSA9->setChecked(Config::GdbARM9BreakOnStartup); + ui->cbGdbEnabled->setChecked(cfg.GetBool("Gdb.Enabled")); + ui->intGdbPortA7->setValue(instcfg.GetInt("Gdb.ARM7.Port")); + ui->intGdbPortA9->setValue(instcfg.GetInt("Gdb.ARM9.Port")); + ui->cbGdbBOSA7->setChecked(instcfg.GetBool("Gdb.ARM7.BreakOnStartup")); + ui->cbGdbBOSA9->setChecked(instcfg.GetBool("Gdb.ARM9.BreakOnStartup")); #else ui->cbGdbEnabled->setDisabled(true); ui->intGdbPortA7->setDisabled(true); @@ -131,23 +135,34 @@ EmuSettingsDialog::EmuSettingsDialog(QWidget* parent) : QDialog(parent), ui(new ui->cbxDSiSDSize->addItem(sizelbl); } - ui->cbDLDIEnable->setChecked(Config::DLDIEnable); - ui->txtDLDISDPath->setText(QString::fromStdString(Config::DLDISDPath)); - ui->cbxDLDISize->setCurrentIndex(Config::DLDISize); - ui->cbDLDIReadOnly->setChecked(Config::DLDIReadOnly); - ui->cbDLDIFolder->setChecked(Config::DLDIFolderSync); - ui->txtDLDIFolder->setText(QString::fromStdString(Config::DLDIFolderPath)); + ui->cbDLDIEnable->setChecked(cfg.GetBool("DLDI.Enable")); + ui->txtDLDISDPath->setText(cfg.GetQString("DLDI.ImagePath")); + ui->cbxDLDISize->setCurrentIndex(cfg.GetInt("DLDI.ImageSize")); + ui->cbDLDIReadOnly->setChecked(cfg.GetBool("DLDI.ReadOnly")); + ui->cbDLDIFolder->setChecked(cfg.GetBool("DLDI.FolderSync")); + ui->txtDLDIFolder->setText(cfg.GetQString("DLDI.FolderPath")); on_cbDLDIEnable_toggled(); - ui->cbDSiFullBIOSBoot->setChecked(Config::DSiFullBIOSBoot); + ui->cbDSiFullBIOSBoot->setChecked(cfg.GetBool("DSi.FullBIOSBoot")); - ui->cbDSiSDEnable->setChecked(Config::DSiSDEnable); - ui->txtDSiSDPath->setText(QString::fromStdString(Config::DSiSDPath)); - ui->cbxDSiSDSize->setCurrentIndex(Config::DSiSDSize); - ui->cbDSiSDReadOnly->setChecked(Config::DSiSDReadOnly); - ui->cbDSiSDFolder->setChecked(Config::DSiSDFolderSync); - ui->txtDSiSDFolder->setText(QString::fromStdString(Config::DSiSDFolderPath)); + ui->cbDSiSDEnable->setChecked(cfg.GetBool("DSi.SD.Enable")); + ui->txtDSiSDPath->setText(cfg.GetQString("DSi.SD.ImagePath")); + ui->cbxDSiSDSize->setCurrentIndex(cfg.GetInt("DSi.SD.ImageSize")); + ui->cbDSiSDReadOnly->setChecked(cfg.GetBool("DSi.SD.ReadOnly")); + ui->cbDSiSDFolder->setChecked(cfg.GetBool("DSi.SD.FolderSync")); + ui->txtDSiSDFolder->setText(cfg.GetQString("DSi.SD.FolderPath")); on_cbDSiSDEnable_toggled(); + +#define SET_ORIGVAL(type, val) \ + for (type* w : findChildren(nullptr)) \ + w->setProperty("user_originalValue", w->val()); + + SET_ORIGVAL(QLineEdit, text); + SET_ORIGVAL(QSpinBox, value); + SET_ORIGVAL(QComboBox, currentIndex); + SET_ORIGVAL(QCheckBox, isChecked); + +#undef SET_ORIGVAL } EmuSettingsDialog::~EmuSettingsDialog() @@ -155,6 +170,7 @@ EmuSettingsDialog::~EmuSettingsDialog() delete ui; } + void EmuSettingsDialog::verifyFirmware() { // verify the firmware @@ -203,134 +219,82 @@ void EmuSettingsDialog::done(int r) if (r == QDialog::Accepted) { - verifyFirmware(); - - int consoleType = ui->cbxConsoleType->currentIndex(); - bool directBoot = ui->chkDirectBoot->isChecked(); - - bool jitEnable = ui->chkEnableJIT->isChecked(); - int jitMaxBlockSize = ui->spnJITMaximumBlockSize->value(); - bool jitBranchOptimisations = ui->chkJITBranchOptimisations->isChecked(); - bool jitLiteralOptimisations = ui->chkJITLiteralOptimisations->isChecked(); - bool jitFastMemory = ui->chkJITFastMemory->isChecked(); - - bool externalBiosEnable = ui->chkExternalBIOS->isChecked(); - std::string bios9Path = ui->txtBIOS9Path->text().toStdString(); - std::string bios7Path = ui->txtBIOS7Path->text().toStdString(); - std::string firmwarePath = ui->txtFirmwarePath->text().toStdString(); - - bool dldiEnable = ui->cbDLDIEnable->isChecked(); - std::string dldiSDPath = ui->txtDLDISDPath->text().toStdString(); - int dldiSize = ui->cbxDLDISize->currentIndex(); - bool dldiReadOnly = ui->cbDLDIReadOnly->isChecked(); - bool dldiFolderSync = ui->cbDLDIFolder->isChecked(); - std::string dldiFolderPath = ui->txtDLDIFolder->text().toStdString(); - - std::string dsiBios9Path = ui->txtDSiBIOS9Path->text().toStdString(); - std::string dsiBios7Path = ui->txtDSiBIOS7Path->text().toStdString(); - std::string dsiFirmwarePath = ui->txtDSiFirmwarePath->text().toStdString(); - std::string dsiNANDPath = ui->txtDSiNANDPath->text().toStdString(); - bool dsiFullBiosBoot = ui->cbDSiFullBIOSBoot->isChecked(); - - bool dsiSDEnable = ui->cbDSiSDEnable->isChecked(); - std::string dsiSDPath = ui->txtDSiSDPath->text().toStdString(); - int dsiSDSize = ui->cbxDSiSDSize->currentIndex(); - bool dsiSDReadOnly = ui->cbDSiSDReadOnly->isChecked(); - bool dsiSDFolderSync = ui->cbDSiSDFolder->isChecked(); - std::string dsiSDFolderPath = ui->txtDSiSDFolder->text().toStdString(); - - bool gdbEnabled = ui->cbGdbEnabled->isChecked(); - int gdbPortA7 = ui->intGdbPortA7->value(); - int gdbPortA9 = ui->intGdbPortA9->value(); - bool gdbBOSA7 = ui->cbGdbBOSA7->isChecked(); - bool gdbBOSA9 = ui->cbGdbBOSA9->isChecked(); - - if (consoleType != Config::ConsoleType - || directBoot != Config::DirectBoot -#ifdef JIT_ENABLED - || jitEnable != Config::JIT_Enable - || jitMaxBlockSize != Config::JIT_MaxBlockSize - || jitBranchOptimisations != Config::JIT_BranchOptimisations - || jitLiteralOptimisations != Config::JIT_LiteralOptimisations - || jitFastMemory != Config::JIT_FastMemory -#endif -#ifdef GDBSTUB_ENABLED - || gdbEnabled != Config::GdbEnabled - || gdbPortA7 != Config::GdbPortARM7 - || gdbPortA9 != Config::GdbPortARM9 - || gdbBOSA7 != Config::GdbARM7BreakOnStartup - || gdbBOSA9 != Config::GdbARM9BreakOnStartup -#endif - || externalBiosEnable != Config::ExternalBIOSEnable - || bios9Path != Config::BIOS9Path - || bios7Path != Config::BIOS7Path - || firmwarePath != Config::FirmwarePath - || dldiEnable != Config::DLDIEnable - || dldiSDPath != Config::DLDISDPath - || dldiSize != Config::DLDISize - || dldiReadOnly != Config::DLDIReadOnly - || dldiFolderSync != Config::DLDIFolderSync - || dldiFolderPath != Config::DLDIFolderPath - || dsiBios9Path != Config::DSiBIOS9Path - || dsiBios7Path != Config::DSiBIOS7Path - || dsiFirmwarePath != Config::DSiFirmwarePath - || dsiNANDPath != Config::DSiNANDPath - || dsiFullBiosBoot != Config::DSiFullBIOSBoot - || dsiSDEnable != Config::DSiSDEnable - || dsiSDPath != Config::DSiSDPath - || dsiSDSize != Config::DSiSDSize - || dsiSDReadOnly != Config::DSiSDReadOnly - || dsiSDFolderSync != Config::DSiSDFolderSync - || dsiSDFolderPath != Config::DSiSDFolderPath) + bool modified = false; + +#define CHECK_ORIGVAL(type, val) \ + if (!modified) for (type* w : findChildren(nullptr)) \ + { \ + QVariant v = w->val(); \ + if (v != w->property("user_originalValue")) \ + { \ + modified = true; \ + break; \ + }\ + } + + CHECK_ORIGVAL(QLineEdit, text); + CHECK_ORIGVAL(QSpinBox, value); + CHECK_ORIGVAL(QComboBox, currentIndex); + CHECK_ORIGVAL(QCheckBox, isChecked); + +#undef CHECK_ORIGVAL + + if (QVariant(ui->txtFirmwarePath->text()) != ui->txtFirmwarePath->property("user_originalValue")) + verifyFirmware(); + + if (modified) { - if (RunningSomething + if (emuInstance->emuIsActive() && QMessageBox::warning(this, "Reset necessary to apply changes", "The emulation will be reset for the changes to take place.", QMessageBox::Ok, QMessageBox::Cancel) != QMessageBox::Ok) return; - Config::ExternalBIOSEnable = externalBiosEnable; - Config::BIOS9Path = bios9Path; - Config::BIOS7Path = bios7Path; - Config::FirmwarePath = firmwarePath; - - Config::DLDIEnable = dldiEnable; - Config::DLDISDPath = dldiSDPath; - Config::DLDISize = dldiSize; - Config::DLDIReadOnly = dldiReadOnly; - Config::DLDIFolderSync = dldiFolderSync; - Config::DLDIFolderPath = dldiFolderPath; - - Config::DSiBIOS9Path = dsiBios9Path; - Config::DSiBIOS7Path = dsiBios7Path; - Config::DSiFirmwarePath = dsiFirmwarePath; - Config::DSiNANDPath = dsiNANDPath; - Config::DSiFullBIOSBoot = dsiFullBiosBoot; - - Config::DSiSDEnable = dsiSDEnable; - Config::DSiSDPath = dsiSDPath; - Config::DSiSDSize = dsiSDSize; - Config::DSiSDReadOnly = dsiSDReadOnly; - Config::DSiSDFolderSync = dsiSDFolderSync; - Config::DSiSDFolderPath = dsiSDFolderPath; + auto& cfg = emuInstance->getGlobalConfig(); + auto& instcfg = emuInstance->getLocalConfig(); + + cfg.SetBool("Emu.ExternalBIOSEnable", ui->chkExternalBIOS->isChecked()); + cfg.SetQString("DS.BIOS9Path", ui->txtBIOS9Path->text()); + cfg.SetQString("DS.BIOS7Path", ui->txtBIOS7Path->text()); + cfg.SetQString("DS.FirmwarePath", ui->txtFirmwarePath->text()); + + cfg.SetBool("DLDI.Enable", ui->cbDLDIEnable->isChecked()); + cfg.SetQString("DLDI.ImagePath", ui->txtDLDISDPath->text()); + cfg.SetInt("DLDI.ImageSize", ui->cbxDLDISize->currentIndex()); + cfg.SetBool("DLDI.ReadOnly", ui->cbDLDIReadOnly->isChecked()); + cfg.SetBool("DLDI.FolderSync", ui->cbDLDIFolder->isChecked()); + cfg.SetQString("DLDI.FolderPath", ui->txtDLDIFolder->text()); + + cfg.SetQString("DSi.BIOS9Path", ui->txtDSiBIOS9Path->text()); + cfg.SetQString("DSi.BIOS7Path", ui->txtDSiBIOS7Path->text()); + cfg.SetQString("DSi.FirmwarePath", ui->txtDSiFirmwarePath->text()); + cfg.SetQString("DSi.NANDPath", ui->txtDSiNANDPath->text()); + cfg.SetBool("DSi.FullBIOSBoot", ui->cbDSiFullBIOSBoot->isChecked()); + + cfg.SetBool("DSi.SD.Enable", ui->cbDSiSDEnable->isChecked()); + cfg.SetQString("DSi.SD.ImagePath", ui->txtDSiSDPath->text()); + cfg.SetInt("DSi.SD.ImageSize", ui->cbxDSiSDSize->currentIndex()); + cfg.SetBool("DSi.SD.ReadOnly", ui->cbDSiSDReadOnly->isChecked()); + cfg.SetBool("DSi.SD.FolderSync", ui->cbDSiSDFolder->isChecked()); + cfg.SetQString("DSi.SD.FolderPath", ui->txtDSiSDFolder->text()); #ifdef JIT_ENABLED - Config::JIT_Enable = jitEnable; - Config::JIT_MaxBlockSize = jitMaxBlockSize; - Config::JIT_BranchOptimisations = jitBranchOptimisations; - Config::JIT_LiteralOptimisations = jitLiteralOptimisations; - Config::JIT_FastMemory = jitFastMemory; + cfg.SetBool("JIT.Enable", ui->chkEnableJIT->isChecked()); + cfg.SetInt("JIT.MaxBlockSize", ui->spnJITMaximumBlockSize->value()); + cfg.SetBool("JIT.BranchOptimisations", ui->chkJITBranchOptimisations->isChecked()); + cfg.SetBool("JIT.LiteralOptimisations", ui->chkJITLiteralOptimisations->isChecked()); + cfg.SetBool("JIT.FastMemory", ui->chkJITFastMemory->isChecked()); #endif #ifdef GDBSTUB_ENABLED - Config::GdbEnabled = gdbEnabled; - Config::GdbPortARM7 = gdbPortA7; - Config::GdbPortARM9 = gdbPortA9; - Config::GdbARM7BreakOnStartup = gdbBOSA7; - Config::GdbARM9BreakOnStartup = gdbBOSA9; + cfg.SetBool("Gdb.Enabled", ui->cbGdbEnabled->isChecked()); + instcfg.SetInt("Gdb.ARM7.Port", ui->intGdbPortA7->value()); + instcfg.SetInt("Gdb.ARM9.Port", ui->intGdbPortA9->value()); + instcfg.SetBool("Gdb.ARM7.BreakOnStartup", ui->cbGdbBOSA7->isChecked()); + instcfg.SetBool("Gdb.ARM9.BreakOnStartup", ui->cbGdbBOSA9->isChecked()); #endif - Config::ConsoleType = consoleType; - Config::DirectBoot = directBoot; + cfg.SetInt("Emu.ConsoleType", ui->cbxConsoleType->currentIndex()); + cfg.SetBool("Emu.DirectBoot", ui->chkDirectBoot->isChecked()); Config::Save(); @@ -347,7 +311,7 @@ void EmuSettingsDialog::on_btnBIOS9Browse_clicked() { QString file = QFileDialog::getOpenFileName(this, "Select DS-mode ARM9 BIOS...", - QString::fromStdString(Config::LastBIOSFolder), + lastBIOSFolder, "BIOS files (*.bin *.rom);;Any file (*.*)"); if (file.isEmpty()) return; @@ -361,7 +325,7 @@ void EmuSettingsDialog::on_btnBIOS7Browse_clicked() { QString file = QFileDialog::getOpenFileName(this, "Select DS-mode ARM7 BIOS...", - QString::fromStdString(Config::LastBIOSFolder), + lastBIOSFolder, "BIOS files (*.bin *.rom);;Any file (*.*)"); if (file.isEmpty()) return; @@ -375,7 +339,7 @@ void EmuSettingsDialog::on_btnFirmwareBrowse_clicked() { QString file = QFileDialog::getOpenFileName(this, "Select DS-mode firmware...", - QString::fromStdString(Config::LastBIOSFolder), + lastBIOSFolder, "Firmware files (*.bin *.rom);;Any file (*.*)"); if (file.isEmpty()) return; @@ -395,7 +359,7 @@ void EmuSettingsDialog::on_btnDSiBIOS9Browse_clicked() { QString file = QFileDialog::getOpenFileName(this, "Select DSi-mode ARM9 BIOS...", - QString::fromStdString(Config::LastBIOSFolder), + lastBIOSFolder, "BIOS files (*.bin *.rom);;Any file (*.*)"); if (file.isEmpty()) return; @@ -409,7 +373,7 @@ void EmuSettingsDialog::on_btnDSiBIOS7Browse_clicked() { QString file = QFileDialog::getOpenFileName(this, "Select DSi-mode ARM7 BIOS...", - QString::fromStdString(Config::LastBIOSFolder), + lastBIOSFolder, "BIOS files (*.bin *.rom);;Any file (*.*)"); if (file.isEmpty()) return; @@ -437,7 +401,7 @@ void EmuSettingsDialog::on_btnDLDISDBrowse_clicked() { QString file = QFileDialog::getOpenFileName(this, "Select DLDI SD image...", - QString::fromStdString(Config::LastBIOSFolder), + lastBIOSFolder, "Image files (*.bin *.rom *.img *.dmg);;Any file (*.*)"); if (file.isEmpty()) return; @@ -464,7 +428,7 @@ void EmuSettingsDialog::on_btnDLDIFolderBrowse_clicked() { QString dir = QFileDialog::getExistingDirectory(this, "Select DLDI SD folder...", - QString::fromStdString(Config::LastBIOSFolder)); + lastBIOSFolder); if (dir.isEmpty()) return; @@ -475,7 +439,7 @@ void EmuSettingsDialog::on_btnDSiFirmwareBrowse_clicked() { QString file = QFileDialog::getOpenFileName(this, "Select DSi DS-mode firmware...", - QString::fromStdString(Config::LastBIOSFolder), + lastBIOSFolder, "Firmware files (*.bin *.rom);;Any file (*.*)"); if (file.isEmpty()) return; @@ -496,7 +460,7 @@ void EmuSettingsDialog::on_btnDSiNANDBrowse_clicked() { QString file = QFileDialog::getOpenFileName(this, "Select DSi NAND...", - QString::fromStdString(Config::LastBIOSFolder), + lastBIOSFolder, "NAND files (*.bin *.mmc *.rom);;Any file (*.*)"); if (file.isEmpty()) return; @@ -531,7 +495,7 @@ void EmuSettingsDialog::on_btnDSiSDBrowse_clicked() { QString file = QFileDialog::getOpenFileName(this, "Select DSi SD image...", - QString::fromStdString(Config::LastBIOSFolder), + lastBIOSFolder, "Image files (*.bin *.rom *.img *.sd *.dmg);;Any file (*.*)"); if (file.isEmpty()) return; @@ -558,7 +522,7 @@ void EmuSettingsDialog::on_btnDSiSDFolderBrowse_clicked() { QString dir = QFileDialog::getExistingDirectory(this, "Select DSi SD folder...", - QString::fromStdString(Config::LastBIOSFolder)); + lastBIOSFolder); if (dir.isEmpty()) return; diff --git a/src/frontend/qt_sdl/EmuSettingsDialog.h b/src/frontend/qt_sdl/EmuSettingsDialog.h index b53d090b90..1d4995e54e 100644 --- a/src/frontend/qt_sdl/EmuSettingsDialog.h +++ b/src/frontend/qt_sdl/EmuSettingsDialog.h @@ -24,6 +24,8 @@ namespace Ui { class EmuSettingsDialog; } class EmuSettingsDialog; +class EmuInstance; + class EmuSettingsDialog : public QDialog { Q_OBJECT @@ -81,8 +83,11 @@ private slots: private: void verifyFirmware(); + void updateLastBIOSFolder(QString& filename); Ui::EmuSettingsDialog* ui; + EmuInstance* emuInstance; + QString lastBIOSFolder; }; #endif // EMUSETTINGSDIALOG_H diff --git a/src/frontend/qt_sdl/EmuThread.cpp b/src/frontend/qt_sdl/EmuThread.cpp index ee25dbd45d..49891c5216 100644 --- a/src/frontend/qt_sdl/EmuThread.cpp +++ b/src/frontend/qt_sdl/EmuThread.cpp @@ -29,13 +29,11 @@ #include #include "main.h" -#include "Input.h" -#include "AudioInOut.h" #include "types.h" #include "version.h" -#include "FrontendUtil.h" +#include "ScreenLayout.h" #include "Args.h" #include "NDS.h" @@ -56,256 +54,57 @@ #include "Savestate.h" -#include "ROMManager.h" -#include "EmuThread.h" -//#include "ArchiveUtil.h" -//#include "CameraManager.h" +#include "EmuInstance.h" -//#include "CLI.h" - -// TODO: uniform variable spelling using namespace melonDS; -// TEMP -extern bool RunningSomething; -extern MainWindow* mainWindow; -extern int autoScreenSizing; -extern int videoRenderer; -extern bool videoSettingsDirty; - -EmuThread::EmuThread(QObject* parent) : QThread(parent) +EmuThread::EmuThread(EmuInstance* inst, QObject* parent) : QThread(parent) { - EmuStatus = emuStatus_Exit; - EmuRunning = emuStatus_Paused; - EmuPauseStack = EmuPauseStackRunning; - RunningSomething = false; - - connect(this, SIGNAL(windowUpdate()), mainWindow->panel, SLOT(repaint())); - connect(this, SIGNAL(windowTitleChange(QString)), mainWindow, SLOT(onTitleUpdate(QString))); - connect(this, SIGNAL(windowEmuStart()), mainWindow, SLOT(onEmuStart())); - connect(this, SIGNAL(windowEmuStop()), mainWindow, SLOT(onEmuStop())); - connect(this, SIGNAL(windowEmuPause()), mainWindow->actPause, SLOT(trigger())); - connect(this, SIGNAL(windowEmuReset()), mainWindow->actReset, SLOT(trigger())); - connect(this, SIGNAL(windowEmuFrameStep()), mainWindow->actFrameStep, SLOT(trigger())); - connect(this, SIGNAL(windowLimitFPSChange()), mainWindow->actLimitFramerate, SLOT(trigger())); - connect(this, SIGNAL(screenLayoutChange()), mainWindow->panel, SLOT(onScreenLayoutChanged())); - connect(this, SIGNAL(windowFullscreenToggle()), mainWindow, SLOT(onFullscreenToggled())); - connect(this, SIGNAL(swapScreensToggle()), mainWindow->actScreenSwap, SLOT(trigger())); - connect(this, SIGNAL(screenEmphasisToggle()), mainWindow, SLOT(onScreenEmphasisToggled())); + emuInstance = inst; + + emuStatus = emuStatus_Paused; + emuPauseStack = emuPauseStackRunning; + emuActive = false; } -std::unique_ptr EmuThread::CreateConsole( - std::unique_ptr &&ndscart, - std::unique_ptr &&gbacart) noexcept +void EmuThread::attachWindow(MainWindow* window) { - auto arm7bios = ROMManager::LoadARM7BIOS(); - if (!arm7bios) - return nullptr; - - auto arm9bios = ROMManager::LoadARM9BIOS(); - if (!arm9bios) - return nullptr; - - auto firmware = ROMManager::LoadFirmware(Config::ConsoleType); - if (!firmware) - return nullptr; - -#ifdef JIT_ENABLED - JITArgs jitargs { - static_cast(Config::JIT_MaxBlockSize), - Config::JIT_LiteralOptimisations, - Config::JIT_BranchOptimisations, - Config::JIT_FastMemory, - }; -#endif - -#ifdef GDBSTUB_ENABLED - GDBArgs gdbargs { - static_cast(Config::GdbPortARM7), - static_cast(Config::GdbPortARM9), - Config::GdbARM7BreakOnStartup, - Config::GdbARM9BreakOnStartup, - }; -#endif - - NDSArgs ndsargs { - std::move(ndscart), - std::move(gbacart), - std::move(arm9bios), - std::move(arm7bios), - std::move(*firmware), -#ifdef JIT_ENABLED - Config::JIT_Enable ? std::make_optional(jitargs) : std::nullopt, -#else - std::nullopt, -#endif - static_cast(Config::AudioBitDepth), - static_cast(Config::AudioInterp), -#ifdef GDBSTUB_ENABLED - Config::GdbEnabled ? std::make_optional(gdbargs) : std::nullopt, -#else - std::nullopt, -#endif - }; - - if (Config::ConsoleType == 1) - { - auto arm7ibios = ROMManager::LoadDSiARM7BIOS(); - if (!arm7ibios) - return nullptr; - - auto arm9ibios = ROMManager::LoadDSiARM9BIOS(); - if (!arm9ibios) - return nullptr; - - auto nand = ROMManager::LoadNAND(*arm7ibios); - if (!nand) - return nullptr; - - auto sdcard = ROMManager::LoadDSiSDCard(); - DSiArgs args { - std::move(ndsargs), - std::move(arm9ibios), - std::move(arm7ibios), - std::move(*nand), - std::move(sdcard), - Config::DSiFullBIOSBoot, - }; - - args.GBAROM = nullptr; - - return std::make_unique(std::move(args)); - } - - return std::make_unique(std::move(ndsargs)); + connect(this, SIGNAL(windowUpdate()), window->panel, SLOT(repaint())); + connect(this, SIGNAL(windowTitleChange(QString)), window, SLOT(onTitleUpdate(QString))); + connect(this, SIGNAL(windowEmuStart()), window, SLOT(onEmuStart())); + connect(this, SIGNAL(windowEmuStop()), window, SLOT(onEmuStop())); + connect(this, SIGNAL(windowEmuPause(bool)), window, SLOT(onEmuPause(bool))); + connect(this, SIGNAL(windowEmuReset()), window, SLOT(onEmuReset())); + connect(this, SIGNAL(windowLimitFPSChange()), window->actLimitFramerate, SLOT(trigger())); + connect(this, SIGNAL(autoScreenSizingChange(int)), window->panel, SLOT(onAutoScreenSizingChanged(int))); + connect(this, SIGNAL(windowFullscreenToggle()), window, SLOT(onFullscreenToggled())); + connect(this, SIGNAL(swapScreensToggle()), window->actScreenSwap, SLOT(trigger())); + connect(this, SIGNAL(screenEmphasisToggle()), window, SLOT(onScreenEmphasisToggled())); } -bool EmuThread::UpdateConsole(UpdateConsoleNDSArgs&& ndsargs, UpdateConsoleGBAArgs&& gbaargs) noexcept +void EmuThread::detachWindow(MainWindow* window) { - // Let's get the cart we want to use; - // if we wnat to keep the cart, we'll eject it from the existing console first. - std::unique_ptr nextndscart; - if (std::holds_alternative(ndsargs)) - { // If we want to keep the existing cart (if any)... - nextndscart = NDS ? NDS->EjectCart() : nullptr; - ndsargs = {}; - } - else if (const auto ptr = std::get_if>(&ndsargs)) - { - nextndscart = std::move(*ptr); - ndsargs = {}; - } - - if (auto* cartsd = dynamic_cast(nextndscart.get())) - { - // LoadDLDISDCard will return nullopt if the SD card is disabled; - // SetSDCard will accept nullopt, which means no SD card - cartsd->SetSDCard(ROMManager::GetDLDISDCardArgs()); - } - - std::unique_ptr nextgbacart; - if (std::holds_alternative(gbaargs)) - { - nextgbacart = NDS ? NDS->EjectGBACart() : nullptr; - } - else if (const auto ptr = std::get_if>(&gbaargs)) - { - nextgbacart = std::move(*ptr); - gbaargs = {}; - } - - if (!NDS || NDS->ConsoleType != Config::ConsoleType) - { // If we're switching between DS and DSi mode, or there's no console... - // To ensure the destructor is called before a new one is created, - // as the presence of global signal handlers still complicates things a bit - NDS = nullptr; - NDS::Current = nullptr; - - NDS = CreateConsole(std::move(nextndscart), std::move(nextgbacart)); - - if (NDS == nullptr) - return false; - - NDS->Reset(); - NDS::Current = NDS.get(); - - return true; - } - - auto arm9bios = ROMManager::LoadARM9BIOS(); - if (!arm9bios) - return false; - - auto arm7bios = ROMManager::LoadARM7BIOS(); - if (!arm7bios) - return false; - - auto firmware = ROMManager::LoadFirmware(NDS->ConsoleType); - if (!firmware) - return false; - - if (NDS->ConsoleType == 1) - { // If the console we're updating is a DSi... - DSi& dsi = static_cast(*NDS); - - auto arm9ibios = ROMManager::LoadDSiARM9BIOS(); - if (!arm9ibios) - return false; - - auto arm7ibios = ROMManager::LoadDSiARM7BIOS(); - if (!arm7ibios) - return false; - - auto nandimage = ROMManager::LoadNAND(*arm7ibios); - if (!nandimage) - return false; - - auto dsisdcard = ROMManager::LoadDSiSDCard(); - - dsi.SetFullBIOSBoot(Config::DSiFullBIOSBoot); - dsi.ARM7iBIOS = *arm7ibios; - dsi.ARM9iBIOS = *arm9ibios; - dsi.SetNAND(std::move(*nandimage)); - dsi.SetSDCard(std::move(dsisdcard)); - // We're moving the optional, not the card - // (inserting std::nullopt here is okay, it means no card) - - dsi.EjectGBACart(); - } - - if (NDS->ConsoleType == 0) - { - NDS->SetGBACart(std::move(nextgbacart)); - } - -#ifdef JIT_ENABLED - JITArgs jitargs { - static_cast(Config::JIT_MaxBlockSize), - Config::JIT_LiteralOptimisations, - Config::JIT_BranchOptimisations, - Config::JIT_FastMemory, - }; - NDS->SetJITArgs(Config::JIT_Enable ? std::make_optional(jitargs) : std::nullopt); -#endif - NDS->SetARM7BIOS(*arm7bios); - NDS->SetARM9BIOS(*arm9bios); - NDS->SetFirmware(std::move(*firmware)); - NDS->SetNDSCart(std::move(nextndscart)); - NDS->SPU.SetInterpolation(static_cast(Config::AudioInterp)); - NDS->SPU.SetDegrade10Bit(static_cast(Config::AudioBitDepth)); - - NDS::Current = NDS.get(); - - return true; + disconnect(this, SIGNAL(windowUpdate()), window->panel, SLOT(repaint())); + disconnect(this, SIGNAL(windowTitleChange(QString)), window, SLOT(onTitleUpdate(QString))); + disconnect(this, SIGNAL(windowEmuStart()), window, SLOT(onEmuStart())); + disconnect(this, SIGNAL(windowEmuStop()), window, SLOT(onEmuStop())); + disconnect(this, SIGNAL(windowEmuPause(bool)), window, SLOT(onEmuPause(bool))); + disconnect(this, SIGNAL(windowEmuReset()), window, SLOT(onEmuReset())); + disconnect(this, SIGNAL(windowLimitFPSChange()), window->actLimitFramerate, SLOT(trigger())); + disconnect(this, SIGNAL(autoScreenSizingChange(int)), window->panel, SLOT(onAutoScreenSizingChanged(int))); + disconnect(this, SIGNAL(windowFullscreenToggle()), window, SLOT(onFullscreenToggled())); + disconnect(this, SIGNAL(swapScreensToggle()), window->actScreenSwap, SLOT(trigger())); + disconnect(this, SIGNAL(screenEmphasisToggle()), window, SLOT(onScreenEmphasisToggled())); } void EmuThread::run() { + Config::Table& globalCfg = emuInstance->getGlobalConfig(); u32 mainScreenPos[3]; Platform::FileHandle* file; - UpdateConsole(nullptr, nullptr); + emuInstance->updateConsole(nullptr, nullptr); // No carts are inserted when melonDS first boots mainScreenPos[0] = 0; @@ -315,24 +114,23 @@ void EmuThread::run() videoSettingsDirty = false; - if (mainWindow->hasOGL) + if (emuInstance->usesOpenGL()) { - screenGL = static_cast(mainWindow->panel); - screenGL->initOpenGL(); - videoRenderer = Config::_3DRenderer; + emuInstance->initOpenGL(); + + useOpenGL = true; + videoRenderer = globalCfg.GetInt("3D.Renderer"); } else { - screenGL = nullptr; + useOpenGL = false; videoRenderer = 0; } updateRenderer(); - Input::Init(); - u32 nframes = 0; - perfCountsSec = 1.0 / SDL_GetPerformanceFrequency(); + double perfCountsSec = 1.0 / SDL_GetPerformanceFrequency(); double lastTime = SDL_GetPerformanceCounter() * perfCountsSec; double frameLimitError = 0.0; double lastMeasureTime = lastTime; @@ -346,95 +144,94 @@ void EmuThread::run() RTC::StateData state; Platform::FileRead(&state, sizeof(state), 1, file); Platform::CloseFile(file); - NDS->RTC.SetState(state); + emuInstance->nds->RTC.SetState(state); } char melontitle[100]; - while (EmuRunning != emuStatus_Exit) + while (emuStatus != emuStatus_Exit) { - Input::Process(); + emuInstance->inputProcess(); - if (Input::HotkeyPressed(HK_FastForwardToggle)) emit windowLimitFPSChange(); + if (emuInstance->hotkeyPressed(HK_FastForwardToggle)) emit windowLimitFPSChange(); - if (Input::HotkeyPressed(HK_Pause)) emit windowEmuPause(); - if (Input::HotkeyPressed(HK_Reset)) emit windowEmuReset(); - if (Input::HotkeyPressed(HK_FrameStep)) emit windowEmuFrameStep(); + if (emuInstance->hotkeyPressed(HK_Pause)) emuTogglePause(); + if (emuInstance->hotkeyPressed(HK_Reset)) emuReset(); + if (emuInstance->hotkeyPressed(HK_FrameStep)) emuFrameStep(); - if (Input::HotkeyPressed(HK_FullscreenToggle)) emit windowFullscreenToggle(); + if (emuInstance->hotkeyPressed(HK_FullscreenToggle)) emit windowFullscreenToggle(); - if (Input::HotkeyPressed(HK_SwapScreens)) emit swapScreensToggle(); - if (Input::HotkeyPressed(HK_SwapScreenEmphasis)) emit screenEmphasisToggle(); + if (emuInstance->hotkeyPressed(HK_SwapScreens)) emit swapScreensToggle(); + if (emuInstance->hotkeyPressed(HK_SwapScreenEmphasis)) emit screenEmphasisToggle(); - if (EmuRunning == emuStatus_Running || EmuRunning == emuStatus_FrameStep) + if (emuStatus == emuStatus_Running || emuStatus == emuStatus_FrameStep) { - EmuStatus = emuStatus_Running; - if (EmuRunning == emuStatus_FrameStep) EmuRunning = emuStatus_Paused; + if (emuStatus == emuStatus_FrameStep) emuStatus = emuStatus_Paused; - if (Input::HotkeyPressed(HK_SolarSensorDecrease)) + if (emuInstance->hotkeyPressed(HK_SolarSensorDecrease)) { - int level = NDS->GBACartSlot.SetInput(GBACart::Input_SolarSensorDown, true); + int level = emuInstance->nds->GBACartSlot.SetInput(GBACart::Input_SolarSensorDown, true); if (level != -1) { - mainWindow->osdAddMessage(0, "Solar sensor level: %d", level); + emuInstance->osdAddMessage(0, "Solar sensor level: %d", level); } } - if (Input::HotkeyPressed(HK_SolarSensorIncrease)) + if (emuInstance->hotkeyPressed(HK_SolarSensorIncrease)) { - int level = NDS->GBACartSlot.SetInput(GBACart::Input_SolarSensorUp, true); + int level = emuInstance->nds->GBACartSlot.SetInput(GBACart::Input_SolarSensorUp, true); if (level != -1) { - mainWindow->osdAddMessage(0, "Solar sensor level: %d", level); + emuInstance->osdAddMessage(0, "Solar sensor level: %d", level); } } - if (NDS->ConsoleType == 1) + if (emuInstance->nds->ConsoleType == 1) { - DSi& dsi = static_cast(*NDS); + DSi* dsi = static_cast(emuInstance->nds); double currentTime = SDL_GetPerformanceCounter() * perfCountsSec; // Handle power button - if (Input::HotkeyDown(HK_PowerButton)) + if (emuInstance->hotkeyDown(HK_PowerButton)) { - dsi.I2C.GetBPTWL()->SetPowerButtonHeld(currentTime); + dsi->I2C.GetBPTWL()->SetPowerButtonHeld(currentTime); } - else if (Input::HotkeyReleased(HK_PowerButton)) + else if (emuInstance->hotkeyReleased(HK_PowerButton)) { - dsi.I2C.GetBPTWL()->SetPowerButtonReleased(currentTime); + dsi->I2C.GetBPTWL()->SetPowerButtonReleased(currentTime); } // Handle volume buttons - if (Input::HotkeyDown(HK_VolumeUp)) + if (emuInstance->hotkeyDown(HK_VolumeUp)) { - dsi.I2C.GetBPTWL()->SetVolumeSwitchHeld(DSi_BPTWL::volumeKey_Up); + dsi->I2C.GetBPTWL()->SetVolumeSwitchHeld(DSi_BPTWL::volumeKey_Up); } - else if (Input::HotkeyReleased(HK_VolumeUp)) + else if (emuInstance->hotkeyReleased(HK_VolumeUp)) { - dsi.I2C.GetBPTWL()->SetVolumeSwitchReleased(DSi_BPTWL::volumeKey_Up); + dsi->I2C.GetBPTWL()->SetVolumeSwitchReleased(DSi_BPTWL::volumeKey_Up); } - if (Input::HotkeyDown(HK_VolumeDown)) + if (emuInstance->hotkeyDown(HK_VolumeDown)) { - dsi.I2C.GetBPTWL()->SetVolumeSwitchHeld(DSi_BPTWL::volumeKey_Down); + dsi->I2C.GetBPTWL()->SetVolumeSwitchHeld(DSi_BPTWL::volumeKey_Down); } - else if (Input::HotkeyReleased(HK_VolumeDown)) + else if (emuInstance->hotkeyReleased(HK_VolumeDown)) { - dsi.I2C.GetBPTWL()->SetVolumeSwitchReleased(DSi_BPTWL::volumeKey_Down); + dsi->I2C.GetBPTWL()->SetVolumeSwitchReleased(DSi_BPTWL::volumeKey_Down); } - dsi.I2C.GetBPTWL()->ProcessVolumeSwitchInput(currentTime); + dsi->I2C.GetBPTWL()->ProcessVolumeSwitchInput(currentTime); } + if (useOpenGL) + emuInstance->makeCurrentGL(); + // update render settings if needed - // HACK: - // once the fast forward hotkey is released, we need to update vsync - // to the old setting again - if (videoSettingsDirty || Input::HotkeyReleased(HK_FastForward)) + if (videoSettingsDirty) { - if (screenGL) + if (useOpenGL) { - screenGL->setSwapInterval(Config::ScreenVSync ? Config::ScreenVSyncInterval : 0); - videoRenderer = Config::_3DRenderer; + emuInstance->setVSyncGL(true); + videoRenderer = globalCfg.GetInt("3D.Renderer"); } #ifdef OGLRENDERER_ENABLED else @@ -449,24 +246,23 @@ void EmuThread::run() } // process input and hotkeys - NDS->SetKeyMask(Input::InputMask); + emuInstance->nds->SetKeyMask(emuInstance->inputMask); - if (Input::HotkeyPressed(HK_Lid)) + if (emuInstance->hotkeyPressed(HK_Lid)) { - bool lid = !NDS->IsLidClosed(); - NDS->SetLidClosed(lid); - mainWindow->osdAddMessage(0, lid ? "Lid closed" : "Lid opened"); + bool lid = !emuInstance->nds->IsLidClosed(); + emuInstance->nds->SetLidClosed(lid); + emuInstance->osdAddMessage(0, lid ? "Lid closed" : "Lid opened"); } // microphone input - AudioInOut::MicProcess(*NDS); + emuInstance->micProcess(); // auto screen layout - if (Config::ScreenSizing == Frontend::screenSizing_Auto) { mainScreenPos[2] = mainScreenPos[1]; mainScreenPos[1] = mainScreenPos[0]; - mainScreenPos[0] = NDS->PowerControl9 >> 15; + mainScreenPos[0] = emuInstance->nds->PowerControl9 >> 15; int guess; if (mainScreenPos[0] == mainScreenPos[2] && @@ -474,99 +270,105 @@ void EmuThread::run() { // constant flickering, likely displaying 3D on both screens // TODO: when both screens are used for 2D only...??? - guess = Frontend::screenSizing_Even; + guess = screenSizing_Even; } else { if (mainScreenPos[0] == 1) - guess = Frontend::screenSizing_EmphTop; + guess = screenSizing_EmphTop; else - guess = Frontend::screenSizing_EmphBot; + guess = screenSizing_EmphBot; } if (guess != autoScreenSizing) { autoScreenSizing = guess; - emit screenLayoutChange(); + emit autoScreenSizingChange(autoScreenSizing); } } // emulate u32 nlines; - if (NDS->GPU.GetRenderer3D().NeedsShaderCompile()) + if (emuInstance->nds->GPU.GetRenderer3D().NeedsShaderCompile()) { compileShaders(); nlines = 1; } else { - nlines = NDS->RunFrame(); + nlines = emuInstance->nds->RunFrame(); } - if (ROMManager::NDSSave) - ROMManager::NDSSave->CheckFlush(); + if (emuInstance->ndsSave) + emuInstance->ndsSave->CheckFlush(); - if (ROMManager::GBASave) - ROMManager::GBASave->CheckFlush(); + if (emuInstance->gbaSave) + emuInstance->gbaSave->CheckFlush(); - if (ROMManager::FirmwareSave) - ROMManager::FirmwareSave->CheckFlush(); + if (emuInstance->firmwareSave) + emuInstance->firmwareSave->CheckFlush(); - if (!screenGL) + if (!useOpenGL) { FrontBufferLock.lock(); - FrontBuffer = NDS->GPU.FrontBuffer; + FrontBuffer = emuInstance->nds->GPU.FrontBuffer; FrontBufferLock.unlock(); } else { - FrontBuffer = NDS->GPU.FrontBuffer; - screenGL->drawScreenGL(); + FrontBuffer = emuInstance->nds->GPU.FrontBuffer; + emuInstance->drawScreenGL(); } #ifdef MELONCAP MelonCap::Update(); #endif // MELONCAP - if (EmuRunning == emuStatus_Exit) break; - winUpdateCount++; - if (winUpdateCount >= winUpdateFreq && !screenGL) + if (winUpdateCount >= winUpdateFreq && !useOpenGL) { emit windowUpdate(); winUpdateCount = 0; } - bool fastforward = Input::HotkeyDown(HK_FastForward); + bool fastforward = emuInstance->hotkeyDown(HK_FastForward); - if (fastforward && screenGL && Config::ScreenVSync) + if (useOpenGL) { - screenGL->setSwapInterval(0); + // when using OpenGL: when toggling fast-forward, change the vsync interval + if (emuInstance->hotkeyPressed(HK_FastForward)) + { + emuInstance->setVSyncGL(false); + } + else if (emuInstance->hotkeyReleased(HK_FastForward)) + { + emuInstance->setVSyncGL(true); + } } - if (Config::DSiVolumeSync && NDS->ConsoleType == 1) + if (emuInstance->audioDSiVolumeSync && emuInstance->nds->ConsoleType == 1) { - DSi& dsi = static_cast(*NDS); - u8 volumeLevel = dsi.I2C.GetBPTWL()->GetVolumeLevel(); + DSi* dsi = static_cast(emuInstance->nds); + u8 volumeLevel = dsi->I2C.GetBPTWL()->GetVolumeLevel(); if (volumeLevel != dsiVolumeLevel) { dsiVolumeLevel = volumeLevel; emit syncVolumeLevel(); } - Config::AudioVolume = volumeLevel * (256.0 / 31.0); + emuInstance->audioVolume = volumeLevel * (256.0 / 31.0); } - if (Config::AudioSync && !fastforward) - AudioInOut::AudioSync(*this->NDS); + if (emuInstance->doAudioSync && !fastforward) + emuInstance->audioSync(); double frametimeStep = nlines / (60.0 * 263.0); { - bool limitfps = Config::LimitFPS && !fastforward; + bool limitfps = emuInstance->doLimitFPS && !fastforward; - double practicalFramelimit = limitfps ? frametimeStep : 1.0 / Config::MaxFPS; + double practicalFramelimit = limitfps ? frametimeStep : 1.0 / emuInstance->maxFPS; double curtime = SDL_GetPerformanceCounter() * perfCountsSec; @@ -603,7 +405,7 @@ void EmuThread::run() if (winUpdateFreq < 1) winUpdateFreq = 1; - int inst = Platform::InstanceID(); + int inst = emuInstance->instanceID; if (inst == 0) sprintf(melontitle, "[%d/%.0f] melonDS " MELONDS_VERSION, fps, fpstarget); else @@ -620,9 +422,7 @@ void EmuThread::run() emit windowUpdate(); - EmuStatus = EmuRunning; - - int inst = Platform::InstanceID(); + int inst = emuInstance->instanceID; if (inst == 0) sprintf(melontitle, "melonDS " MELONDS_VERSION); else @@ -631,114 +431,219 @@ void EmuThread::run() SDL_Delay(75); - if (screenGL) - screenGL->drawScreenGL(); - - ContextRequestKind contextRequest = ContextRequest; - if (contextRequest == contextRequest_InitGL) + if (useOpenGL) { - screenGL = static_cast(mainWindow->panel); - screenGL->initOpenGL(); - ContextRequest = contextRequest_None; - } - else if (contextRequest == contextRequest_DeInitGL) - { - screenGL->deinitOpenGL(); - screenGL = nullptr; - ContextRequest = contextRequest_None; + emuInstance->drawScreenGL(); } } + + handleMessages(); } file = Platform::OpenLocalFile("rtc.bin", Platform::FileMode::Write); if (file) { RTC::StateData state; - NDS->RTC.GetState(state); + emuInstance->nds->RTC.GetState(state); Platform::FileWrite(&state, sizeof(state), 1, file); Platform::CloseFile(file); } - EmuStatus = emuStatus_Exit; - NDS::Current = nullptr; - // nds is out of scope, so unique_ptr cleans it up for us } -void EmuThread::changeWindowTitle(char* title) +void EmuThread::sendMessage(Message msg) { - emit windowTitleChange(QString(title)); + msgMutex.lock(); + msgQueue.enqueue(msg); + msgMutex.unlock(); } -void EmuThread::emuRun() +void EmuThread::waitMessage(int num) { - EmuRunning = emuStatus_Running; - EmuPauseStack = EmuPauseStackRunning; - RunningSomething = true; + if (QThread::currentThread() == this) return; + msgSemaphore.acquire(num); +} - // checkme - emit windowEmuStart(); - AudioInOut::Enable(); +void EmuThread::waitAllMessages() +{ + if (QThread::currentThread() == this) return; + msgSemaphore.acquire(msgSemaphore.available()); +} + +void EmuThread::handleMessages() +{ + msgMutex.lock(); + while (!msgQueue.empty()) + { + Message msg = msgQueue.dequeue(); + switch (msg.type) + { + case msg_Exit: + emuStatus = emuStatus_Exit; + emuPauseStack = emuPauseStackRunning; + + emuInstance->audioDisable(); + break; + + case msg_EmuRun: + emuStatus = emuStatus_Running; + emuPauseStack = emuPauseStackRunning; + emuActive = true; + + emuInstance->audioEnable(); + emit windowEmuStart(); + break; + + case msg_EmuPause: + emuPauseStack++; + if (emuPauseStack > emuPauseStackPauseThreshold) break; + + prevEmuStatus = emuStatus; + emuStatus = emuStatus_Paused; + + if (prevEmuStatus != emuStatus_Paused) + { + emuInstance->audioDisable(); + emit windowEmuPause(true); + emuInstance->osdAddMessage(0, "Paused"); + } + break; + + case msg_EmuUnpause: + if (emuPauseStack < emuPauseStackPauseThreshold) break; + + emuPauseStack--; + if (emuPauseStack >= emuPauseStackPauseThreshold) break; + + emuStatus = prevEmuStatus; + + if (emuStatus != emuStatus_Paused) + { + emuInstance->audioEnable(); + emit windowEmuPause(false); + emuInstance->osdAddMessage(0, "Resumed"); + } + break; + + case msg_EmuStop: + if (msg.stopExternal) emuInstance->nds->Stop(); + emuStatus = emuStatus_Paused; + emuActive = false; + + emuInstance->audioDisable(); + emit windowEmuStop(); + break; + + case msg_EmuFrameStep: + emuStatus = emuStatus_FrameStep; + break; + + case msg_EmuReset: + emuInstance->reset(); + + emuStatus = emuStatus_Running; + emuPauseStack = emuPauseStackRunning; + emuActive = true; + + emuInstance->audioEnable(); + emit windowEmuReset(); + emuInstance->osdAddMessage(0, "Reset"); + break; + + case msg_InitGL: + emuInstance->initOpenGL(); + useOpenGL = true; + break; + + case msg_DeInitGL: + emuInstance->deinitOpenGL(); + useOpenGL = false; + break; + } + + msgSemaphore.release(); + } + msgMutex.unlock(); +} + +void EmuThread::changeWindowTitle(char* title) +{ + emit windowTitleChange(QString(title)); } void EmuThread::initContext() { - ContextRequest = contextRequest_InitGL; - while (ContextRequest != contextRequest_None); + sendMessage(msg_InitGL); + waitMessage(); } void EmuThread::deinitContext() { - ContextRequest = contextRequest_DeInitGL; - while (ContextRequest != contextRequest_None); + sendMessage(msg_DeInitGL); + waitMessage(); } -void EmuThread::emuPause() +void EmuThread::emuRun() { - EmuPauseStack++; - if (EmuPauseStack > EmuPauseStackPauseThreshold) return; - - PrevEmuStatus = EmuRunning; - EmuRunning = emuStatus_Paused; - while (EmuStatus != emuStatus_Paused); + sendMessage(msg_EmuRun); + waitMessage(); +} - AudioInOut::Disable(); +void EmuThread::emuPause() +{ + sendMessage(msg_EmuPause); + waitMessage(); } void EmuThread::emuUnpause() { - if (EmuPauseStack < EmuPauseStackPauseThreshold) return; - - EmuPauseStack--; - if (EmuPauseStack >= EmuPauseStackPauseThreshold) return; - - EmuRunning = PrevEmuStatus; + sendMessage(msg_EmuUnpause); + waitMessage(); +} - AudioInOut::Enable(); +void EmuThread::emuTogglePause() +{ + if (emuStatus == emuStatus_Paused) + emuUnpause(); + else + emuPause(); } -void EmuThread::emuStop() +void EmuThread::emuStop(bool external) { - EmuRunning = emuStatus_Exit; - EmuPauseStack = EmuPauseStackRunning; + sendMessage({.type = msg_EmuStop, .stopExternal = external}); + waitMessage(); +} - AudioInOut::Disable(); +void EmuThread::emuExit() +{ + sendMessage(msg_Exit); + waitAllMessages(); } void EmuThread::emuFrameStep() { - if (EmuPauseStack < EmuPauseStackPauseThreshold) emit windowEmuPause(); - EmuRunning = emuStatus_FrameStep; + if (emuPauseStack < emuPauseStackPauseThreshold) + sendMessage(msg_EmuPause); + sendMessage(msg_EmuFrameStep); + waitAllMessages(); +} + +void EmuThread::emuReset() +{ + sendMessage(msg_EmuReset); + waitMessage(); } bool EmuThread::emuIsRunning() { - return EmuRunning == emuStatus_Running; + return emuStatus == emuStatus_Running; } bool EmuThread::emuIsActive() { - return (RunningSomething == 1); + return emuActive; } void EmuThread::updateRenderer() @@ -748,32 +653,39 @@ void EmuThread::updateRenderer() printf("creating renderer %d\n", videoRenderer); switch (videoRenderer) { + case renderer3D_Software: + emuInstance->nds->GPU.SetRenderer3D(std::make_unique()); + break; + case renderer3D_OpenGL: + emuInstance->nds->GPU.SetRenderer3D(GLRenderer::New()); + break; + case renderer3D_OpenGLCompute: + emuInstance->nds->GPU.SetRenderer3D(ComputeRenderer::New()); + break; + default: __builtin_unreachable(); + } + } + lastVideoRenderer = videoRenderer; + + auto& cfg = emuInstance->getGlobalConfig(); + switch (videoRenderer) + { case renderer3D_Software: - NDS->GPU.SetRenderer3D(std::make_unique()); + static_cast(emuInstance->nds->GPU.GetRenderer3D()).SetThreaded( + cfg.GetBool("3D.Soft.Threaded"), + emuInstance->nds->GPU); break; case renderer3D_OpenGL: - NDS->GPU.SetRenderer3D(GLRenderer::New()); + static_cast(emuInstance->nds->GPU.GetRenderer3D()).SetRenderSettings( + cfg.GetBool("3D.GL.BetterPolygons"), + cfg.GetInt("3D.GL.ScaleFactor")); break; case renderer3D_OpenGLCompute: - NDS->GPU.SetRenderer3D(ComputeRenderer::New()); + static_cast(emuInstance->nds->GPU.GetRenderer3D()).SetRenderSettings( + cfg.GetInt("3D.GL.ScaleFactor"), + cfg.GetBool("3D.GL.HiresCoordinates")); break; default: __builtin_unreachable(); - } - } - lastVideoRenderer = videoRenderer; - - switch (videoRenderer) - { - case renderer3D_Software: - static_cast(NDS->GPU.GetRenderer3D()).SetThreaded(Config::Threaded3D, NDS->GPU); - break; - case renderer3D_OpenGL: - static_cast(NDS->GPU.GetRenderer3D()).SetRenderSettings(Config::GL_BetterPolygons, Config::GL_ScaleFactor); - break; - case renderer3D_OpenGLCompute: - static_cast(NDS->GPU.GetRenderer3D()).SetRenderSettings(Config::GL_ScaleFactor, Config::GL_HiresCoordinates); - break; - default: __builtin_unreachable(); } } @@ -785,8 +697,8 @@ void EmuThread::compileShaders() // than disabling vsync do { - NDS->GPU.GetRenderer3D().ShaderCompileStep(currentShader, shadersCount); - } while (NDS->GPU.GetRenderer3D().NeedsShaderCompile() && - (SDL_GetPerformanceCounter() - startTime) * perfCountsSec < 1.0 / 6.0); - mainWindow->osdAddMessage(0, "Compiling shader %d/%d", currentShader+1, shadersCount); + emuInstance->nds->GPU.GetRenderer3D().ShaderCompileStep(currentShader, shadersCount); + } while (emuInstance->nds->GPU.GetRenderer3D().NeedsShaderCompile() && + (SDL_GetPerformanceCounter() - startTime) * perfCountsSec < 1.0 / 6.0); + emuInstance->osdAddMessage(0, "Compiling shader %d/%d", currentShader+1, shadersCount); } diff --git a/src/frontend/qt_sdl/EmuThread.h b/src/frontend/qt_sdl/EmuThread.h index 4b19acf9af..51b1856203 100644 --- a/src/frontend/qt_sdl/EmuThread.h +++ b/src/frontend/qt_sdl/EmuThread.h @@ -21,10 +21,13 @@ #include #include +#include +#include #include #include #include +#include #include "NDSCart.h" #include "GBACart.h" @@ -37,6 +40,8 @@ namespace melonDS class NDS; } +class EmuInstance; +class MainWindow; class ScreenPanelGL; class EmuThread : public QThread @@ -45,7 +50,43 @@ class EmuThread : public QThread void run() override; public: - explicit EmuThread(QObject* parent = nullptr); + explicit EmuThread(EmuInstance* inst, QObject* parent = nullptr); + + void attachWindow(MainWindow* window); + void detachWindow(MainWindow* window); + + enum MessageType + { + msg_Exit, + + msg_EmuRun, + msg_EmuPause, + msg_EmuUnpause, + msg_EmuStop, + msg_EmuFrameStep, + msg_EmuReset, + + msg_InitGL, + msg_DeInitGL, + }; + + struct Message + { + MessageType type; + union + { + bool stopExternal; + }; + }; + + void sendMessage(Message msg); + void waitMessage(int num = 1); + void waitAllMessages(); + + void sendMessage(MessageType type) + { + return sendMessage({.type = type}); + } void changeWindowTitle(char* title); @@ -53,38 +94,34 @@ class EmuThread : public QThread void emuRun(); void emuPause(); void emuUnpause(); - void emuStop(); + void emuTogglePause(); + void emuStop(bool external); + void emuExit(); void emuFrameStep(); + void emuReset(); bool emuIsRunning(); bool emuIsActive(); void initContext(); void deinitContext(); + void updateVideoSettings() { videoSettingsDirty = true; } int FrontBuffer = 0; QMutex FrontBufferLock; - /// Applies the config in args. - /// Creates a new NDS console if needed, - /// modifies the existing one if possible. - /// @return \c true if the console was updated. - /// If this returns \c false, then the existing NDS console is not modified. - bool UpdateConsole(UpdateConsoleNDSArgs&& ndsargs, UpdateConsoleGBAArgs&& gbaargs) noexcept; - std::unique_ptr NDS; // TODO: Proper encapsulation and synchronization signals: void windowUpdate(); void windowTitleChange(QString title); void windowEmuStart(); void windowEmuStop(); - void windowEmuPause(); + void windowEmuPause(bool pause); void windowEmuReset(); - void windowEmuFrameStep(); void windowLimitFPSChange(); - void screenLayoutChange(); + void autoScreenSizingChange(int sizing); void windowFullscreenToggle(); @@ -94,14 +131,11 @@ class EmuThread : public QThread void syncVolumeLevel(); private: + void handleMessages(); + void updateRenderer(); void compileShaders(); - std::unique_ptr CreateConsole( - std::unique_ptr&& ndscart, - std::unique_ptr&& gbacart - ) noexcept; - enum EmuStatusKind { emuStatus_Exit, @@ -109,30 +143,30 @@ class EmuThread : public QThread emuStatus_Paused, emuStatus_FrameStep, }; - std::atomic EmuStatus; - EmuStatusKind PrevEmuStatus; - EmuStatusKind EmuRunning; + EmuStatusKind prevEmuStatus; + EmuStatusKind emuStatus; + bool emuActive; - constexpr static int EmuPauseStackRunning = 0; - constexpr static int EmuPauseStackPauseThreshold = 1; - int EmuPauseStack; + constexpr static int emuPauseStackRunning = 0; + constexpr static int emuPauseStackPauseThreshold = 1; + int emuPauseStack; - enum ContextRequestKind - { - contextRequest_None = 0, - contextRequest_InitGL, - contextRequest_DeInitGL - }; - std::atomic ContextRequest = contextRequest_None; + QMutex msgMutex; + QSemaphore msgSemaphore; + QQueue msgQueue; - ScreenPanelGL* screenGL; + EmuInstance* emuInstance; int autoScreenSizing; int lastVideoRenderer = -1; double perfCountsSec; + + bool useOpenGL; + int videoRenderer; + bool videoSettingsDirty; }; #endif // EMUTHREAD_H diff --git a/src/frontend/qt_sdl/FirmwareSettingsDialog.cpp b/src/frontend/qt_sdl/FirmwareSettingsDialog.cpp index 1ec2e8c4c7..27bb8f4d71 100644 --- a/src/frontend/qt_sdl/FirmwareSettingsDialog.cpp +++ b/src/frontend/qt_sdl/FirmwareSettingsDialog.cpp @@ -20,6 +20,7 @@ #include "Platform.h" #include "Config.h" +#include "main.h" #include "FirmwareSettingsDialog.h" #include "ui_FirmwareSettingsDialog.h" @@ -29,8 +30,6 @@ namespace Platform = melonDS::Platform; FirmwareSettingsDialog* FirmwareSettingsDialog::currentDlg = nullptr; -extern bool RunningSomething; - bool FirmwareSettingsDialog::needsReset = false; FirmwareSettingsDialog::FirmwareSettingsDialog(QWidget* parent) : QDialog(parent), ui(new Ui::FirmwareSettingsDialog) @@ -38,10 +37,15 @@ FirmwareSettingsDialog::FirmwareSettingsDialog(QWidget* parent) : QDialog(parent ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose); - ui->usernameEdit->setText(QString::fromStdString(Config::FirmwareUsername)); + emuInstance = ((MainWindow*)parent)->getEmuInstance(); + + auto& cfg = emuInstance->getLocalConfig(); + auto firmcfg = cfg.GetTable("Firmware"); + + ui->usernameEdit->setText(firmcfg.GetQString("Username")); ui->languageBox->addItems(languages); - ui->languageBox->setCurrentIndex(Config::FirmwareLanguage); + ui->languageBox->setCurrentIndex(firmcfg.GetInt("Language")); for (int i = 1; i <= 31; i++) { @@ -49,9 +53,9 @@ FirmwareSettingsDialog::FirmwareSettingsDialog(QWidget* parent) : QDialog(parent } ui->cbxBirthdayMonth->addItems(months); - ui->cbxBirthdayMonth->setCurrentIndex(Config::FirmwareBirthdayMonth - 1); + ui->cbxBirthdayMonth->setCurrentIndex(firmcfg.GetInt("BirthdayMonth") - 1); - ui->cbxBirthdayDay->setCurrentIndex(Config::FirmwareBirthdayDay - 1); + ui->cbxBirthdayDay->setCurrentIndex(firmcfg.GetInt("BirthdayDay") - 1); for (int i = 0; i < 16; i++) { @@ -60,21 +64,31 @@ FirmwareSettingsDialog::FirmwareSettingsDialog(QWidget* parent) : QDialog(parent QIcon icon(QPixmap::fromImage(image.copy())); ui->colorsEdit->addItem(icon, colornames[i]); } - ui->colorsEdit->setCurrentIndex(Config::FirmwareFavouriteColour); + ui->colorsEdit->setCurrentIndex(firmcfg.GetInt("FavouriteColour")); - ui->messageEdit->setText(QString::fromStdString(Config::FirmwareMessage)); + ui->messageEdit->setText(firmcfg.GetQString("Message")); - ui->overrideFirmwareBox->setChecked(Config::FirmwareOverrideSettings); + ui->overrideFirmwareBox->setChecked(firmcfg.GetBool("OverrideSettings")); - ui->txtMAC->setText(QString::fromStdString(Config::FirmwareMAC)); + ui->txtMAC->setText(firmcfg.GetQString("MAC")); on_overrideFirmwareBox_toggled(); - int inst = Platform::InstanceID(); + int inst = emuInstance->getInstanceID(); if (inst > 0) ui->lblInstanceNum->setText(QString("Configuring settings for instance %1").arg(inst+1)); else ui->lblInstanceNum->hide(); + +#define SET_ORIGVAL(type, val) \ + for (type* w : findChildren(nullptr)) \ + w->setProperty("user_originalValue", w->val()); + + SET_ORIGVAL(QLineEdit, text); + SET_ORIGVAL(QComboBox, currentIndex); + SET_ORIGVAL(QCheckBox, isChecked); + +#undef SET_ORIGVAL } FirmwareSettingsDialog::~FirmwareSettingsDialog() @@ -132,42 +146,46 @@ void FirmwareSettingsDialog::done(int r) return; } - bool newOverride = ui->overrideFirmwareBox->isChecked(); - - std::string newName = ui->usernameEdit->text().toStdString(); - int newLanguage = ui->languageBox->currentIndex(); - int newFavColor = ui->colorsEdit->currentIndex(); - int newBirthdayDay = ui->cbxBirthdayDay->currentIndex() + 1; - int newBirthdayMonth = ui->cbxBirthdayMonth->currentIndex() + 1; - std::string newMessage = ui->messageEdit->text().toStdString(); - - std::string newMAC = ui->txtMAC->text().toStdString(); - - if ( newOverride != Config::FirmwareOverrideSettings - || newName != Config::FirmwareUsername - || newLanguage != Config::FirmwareLanguage - || newFavColor != Config::FirmwareFavouriteColour - || newBirthdayDay != Config::FirmwareBirthdayDay - || newBirthdayMonth != Config::FirmwareBirthdayMonth - || newMessage != Config::FirmwareMessage - || newMAC != Config::FirmwareMAC) + bool modified = false; + +#define CHECK_ORIGVAL(type, val) \ + if (!modified) for (type* w : findChildren(nullptr)) \ + { \ + QVariant v = w->val(); \ + if (v != w->property("user_originalValue")) \ + { \ + modified = true; \ + break; \ + }\ + } + + CHECK_ORIGVAL(QLineEdit, text); + CHECK_ORIGVAL(QComboBox, currentIndex); + CHECK_ORIGVAL(QCheckBox, isChecked); + +#undef CHECK_ORIGVAL + + if (modified) { - if (RunningSomething + if (emuInstance->emuIsActive() && QMessageBox::warning(this, "Reset necessary to apply changes", "The emulation will be reset for the changes to take place.", QMessageBox::Ok, QMessageBox::Cancel) != QMessageBox::Ok) return; - Config::FirmwareOverrideSettings = newOverride; + auto& cfg = emuInstance->getLocalConfig(); + auto firmcfg = cfg.GetTable("Firmware"); + + firmcfg.SetBool("OverrideSettings", ui->overrideFirmwareBox->isChecked()); - Config::FirmwareUsername = newName; - Config::FirmwareLanguage = newLanguage; - Config::FirmwareFavouriteColour = newFavColor; - Config::FirmwareBirthdayDay = newBirthdayDay; - Config::FirmwareBirthdayMonth = newBirthdayMonth; - Config::FirmwareMessage = newMessage; + firmcfg.SetQString("Username", ui->usernameEdit->text()); + firmcfg.SetInt("Language", ui->languageBox->currentIndex()); + firmcfg.SetInt("FavouriteColour", ui->colorsEdit->currentIndex()); + firmcfg.SetInt("BirthdayDay", ui->cbxBirthdayDay->currentIndex() + 1); + firmcfg.SetInt("BirthdayMonth", ui->cbxBirthdayMonth->currentIndex() + 1); + firmcfg.SetQString("Message", ui->messageEdit->text()); - Config::FirmwareMAC = newMAC; + firmcfg.SetQString("MAC", ui->txtMAC->text()); Config::Save(); diff --git a/src/frontend/qt_sdl/FirmwareSettingsDialog.h b/src/frontend/qt_sdl/FirmwareSettingsDialog.h index d22ce3a220..13ab6f1fc7 100644 --- a/src/frontend/qt_sdl/FirmwareSettingsDialog.h +++ b/src/frontend/qt_sdl/FirmwareSettingsDialog.h @@ -25,6 +25,8 @@ namespace Ui { class FirmwareSettingsDialog; } class FirmwareSettingsDialog; +class EmuInstance; + class FirmwareSettingsDialog : public QDialog { Q_OBJECT @@ -129,6 +131,7 @@ private slots: bool verifyMAC(); Ui::FirmwareSettingsDialog* ui; + EmuInstance* emuInstance; }; #endif // FIRMWARESETTINGSDIALOG_H diff --git a/src/frontend/qt_sdl/Input.cpp b/src/frontend/qt_sdl/Input.cpp deleted file mode 100644 index 7ebd7e2aba..0000000000 --- a/src/frontend/qt_sdl/Input.cpp +++ /dev/null @@ -1,265 +0,0 @@ -/* - Copyright 2016-2023 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 -#include - -#include "Input.h" -#include "Config.h" - -using namespace melonDS; - -namespace Input -{ - -int JoystickID; -SDL_Joystick* Joystick = nullptr; - -u32 KeyInputMask, JoyInputMask; -u32 KeyHotkeyMask, JoyHotkeyMask; -u32 HotkeyMask, LastHotkeyMask; -u32 HotkeyPress, HotkeyRelease; - -u32 InputMask; - - -void Init() -{ - KeyInputMask = 0xFFF; - JoyInputMask = 0xFFF; - InputMask = 0xFFF; - - KeyHotkeyMask = 0; - JoyHotkeyMask = 0; - HotkeyMask = 0; - LastHotkeyMask = 0; -} - - -void OpenJoystick() -{ - if (Joystick) SDL_JoystickClose(Joystick); - - int num = SDL_NumJoysticks(); - if (num < 1) - { - Joystick = nullptr; - return; - } - - if (JoystickID >= num) - JoystickID = 0; - - Joystick = SDL_JoystickOpen(JoystickID); -} - -void CloseJoystick() -{ - if (Joystick) - { - SDL_JoystickClose(Joystick); - Joystick = nullptr; - } -} - - -int GetEventKeyVal(QKeyEvent* event) -{ - int key = event->key(); - int mod = event->modifiers(); - bool ismod = (key == Qt::Key_Control || - key == Qt::Key_Alt || - key == Qt::Key_AltGr || - key == Qt::Key_Shift || - key == Qt::Key_Meta); - - if (!ismod) - key |= mod; - else if (Input::IsRightModKey(event)) - key |= (1<<31); - - return key; -} - -void KeyPress(QKeyEvent* event) -{ - int keyHK = GetEventKeyVal(event); - int keyKP = keyHK; - if (event->modifiers() != Qt::KeypadModifier) - keyKP &= ~event->modifiers(); - - for (int i = 0; i < 12; i++) - if (keyKP == Config::KeyMapping[i]) - KeyInputMask &= ~(1<modifiers() != Qt::KeypadModifier) - keyKP &= ~event->modifiers(); - - for (int i = 0; i < 12; i++) - if (keyKP == Config::KeyMapping[i]) - KeyInputMask |= (1<> 4) & 0xF; - int hatdir = val & 0xF; - Uint8 hatval = SDL_JoystickGetHat(Joystick, hatnum); - - bool pressed = false; - if (hatdir == 0x1) pressed = (hatval & SDL_HAT_UP); - else if (hatdir == 0x4) pressed = (hatval & SDL_HAT_DOWN); - else if (hatdir == 0x2) pressed = (hatval & SDL_HAT_RIGHT); - else if (hatdir == 0x8) pressed = (hatval & SDL_HAT_LEFT); - - if (pressed) return true; - } - else - { - int btnnum = val & 0xFFFF; - Uint8 btnval = SDL_JoystickGetButton(Joystick, btnnum); - - if (btnval) return true; - } - } - - if (val & 0x10000) - { - int axisnum = (val >> 24) & 0xF; - int axisdir = (val >> 20) & 0xF; - Sint16 axisval = SDL_JoystickGetAxis(Joystick, axisnum); - - switch (axisdir) - { - case 0: // positive - if (axisval > 16384) return true; - break; - - case 1: // negative - if (axisval < -16384) return true; - break; - - case 2: // trigger - if (axisval > 0) return true; - break; - } - } - - return false; -} - -void Process() -{ - SDL_JoystickUpdate(); - - if (Joystick) - { - if (!SDL_JoystickGetAttached(Joystick)) - { - SDL_JoystickClose(Joystick); - Joystick = NULL; - } - } - if (!Joystick && (SDL_NumJoysticks() > 0)) - { - JoystickID = Config::JoystickID; - OpenJoystick(); - } - - JoyInputMask = 0xFFF; - if (Joystick) - { - for (int i = 0; i < 12; i++) - if (JoystickButtonDown(Config::JoyMapping[i])) - JoyInputMask &= ~(1 << i); - } - - InputMask = KeyInputMask & JoyInputMask; - - JoyHotkeyMask = 0; - if (Joystick) - { - for (int i = 0; i < HK_MAX; i++) - if (JoystickButtonDown(Config::HKJoyMapping[i])) - JoyHotkeyMask |= (1 << i); - } - - HotkeyMask = KeyHotkeyMask | JoyHotkeyMask; - HotkeyPress = HotkeyMask & ~LastHotkeyMask; - HotkeyRelease = LastHotkeyMask & ~HotkeyMask; - LastHotkeyMask = HotkeyMask; -} - - -bool HotkeyDown(int id) { return HotkeyMask & (1<nativeScanCode(); - return (scan == 0x11D || scan == 0x138 || scan == 0x36); -} -#elif __APPLE__ -bool IsRightModKey(QKeyEvent* event) -{ - quint32 scan = event->nativeVirtualKey(); - return (scan == 0x36 || scan == 0x3C || scan == 0x3D || scan == 0x3E); -} -#else -bool IsRightModKey(QKeyEvent* event) -{ - quint32 scan = event->nativeScanCode(); - return (scan == 0x69 || scan == 0x6C || scan == 0x3E); -} -#endif - -} diff --git a/src/frontend/qt_sdl/InputConfig/InputConfigDialog.cpp b/src/frontend/qt_sdl/InputConfig/InputConfigDialog.cpp index 02a76bb718..fd433f3416 100644 --- a/src/frontend/qt_sdl/InputConfig/InputConfigDialog.cpp +++ b/src/frontend/qt_sdl/InputConfig/InputConfigDialog.cpp @@ -26,10 +26,9 @@ #include "types.h" #include "Platform.h" -#include "MapButton.h" -#include "Input.h" #include "InputConfigDialog.h" #include "ui_InputConfigDialog.h" +#include "MapButton.h" using namespace melonDS; @@ -43,31 +42,42 @@ InputConfigDialog::InputConfigDialog(QWidget* parent) : QDialog(parent), ui(new ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose); + emuInstance = ((MainWindow*)parent)->getEmuInstance(); + + Config::Table& instcfg = emuInstance->getLocalConfig(); + Config::Table keycfg = instcfg.GetTable("Keyboard"); + Config::Table joycfg = instcfg.GetTable("Joystick"); + for (int i = 0; i < keypad_num; i++) { - keypadKeyMap[i] = Config::KeyMapping[dskeyorder[i]]; - keypadJoyMap[i] = Config::JoyMapping[dskeyorder[i]]; + const char* btn = EmuInstance::buttonNames[dskeyorder[i]]; + keypadKeyMap[i] = keycfg.GetInt(btn); + keypadJoyMap[i] = joycfg.GetInt(btn); } int i = 0; for (int hotkey : hk_addons) { - addonsKeyMap[i] = Config::HKKeyMapping[hotkey]; - addonsJoyMap[i] = Config::HKJoyMapping[hotkey]; + const char* btn = EmuInstance::hotkeyNames[hotkey]; + addonsKeyMap[i] = keycfg.GetInt(btn); + addonsJoyMap[i] = joycfg.GetInt(btn); i++; } i = 0; for (int hotkey : hk_general) { - hkGeneralKeyMap[i] = Config::HKKeyMapping[hotkey]; - hkGeneralJoyMap[i] = Config::HKJoyMapping[hotkey]; + const char* btn = EmuInstance::hotkeyNames[hotkey]; + hkGeneralKeyMap[i] = keycfg.GetInt(btn); + hkGeneralJoyMap[i] = joycfg.GetInt(btn); i++; } populatePage(ui->tabAddons, hk_addons_labels, addonsKeyMap, addonsJoyMap); populatePage(ui->tabHotkeysGeneral, hk_general_labels, hkGeneralKeyMap, hkGeneralJoyMap); + joystickID = instcfg.GetInt("JoystickID"); + int njoy = SDL_NumJoysticks(); if (njoy > 0) { @@ -76,7 +86,7 @@ InputConfigDialog::InputConfigDialog(QWidget* parent) : QDialog(parent), ui(new const char* name = SDL_JoystickNameForIndex(i); ui->cbxJoystick->addItem(QString(name)); } - ui->cbxJoystick->setCurrentIndex(Input::JoystickID); + ui->cbxJoystick->setCurrentIndex(joystickID); } else { @@ -86,7 +96,7 @@ InputConfigDialog::InputConfigDialog(QWidget* parent) : QDialog(parent), ui(new setupKeypadPage(); - int inst = Platform::InstanceID(); + int inst = emuInstance->getInstanceID(); if (inst > 0) ui->lblInstanceNum->setText(QString("Configuring mappings for instance %1").arg(inst+1)); else @@ -174,38 +184,47 @@ void InputConfigDialog::populatePage(QWidget* page, void InputConfigDialog::on_InputConfigDialog_accepted() { + Config::Table& instcfg = emuInstance->getLocalConfig(); + Config::Table keycfg = instcfg.GetTable("Keyboard"); + Config::Table joycfg = instcfg.GetTable("Joystick"); + for (int i = 0; i < keypad_num; i++) { - Config::KeyMapping[dskeyorder[i]] = keypadKeyMap[i]; - Config::JoyMapping[dskeyorder[i]] = keypadJoyMap[i]; + const char* btn = EmuInstance::buttonNames[dskeyorder[i]]; + keycfg.SetInt(btn, keypadKeyMap[i]); + joycfg.SetInt(btn, keypadJoyMap[i]); } int i = 0; for (int hotkey : hk_addons) { - Config::HKKeyMapping[hotkey] = addonsKeyMap[i]; - Config::HKJoyMapping[hotkey] = addonsJoyMap[i]; + const char* btn = EmuInstance::hotkeyNames[hotkey]; + keycfg.SetInt(btn, addonsKeyMap[i]); + joycfg.SetInt(btn, addonsJoyMap[i]); i++; } i = 0; for (int hotkey : hk_general) { - Config::HKKeyMapping[hotkey] = hkGeneralKeyMap[i]; - Config::HKJoyMapping[hotkey] = hkGeneralJoyMap[i]; + const char* btn = EmuInstance::hotkeyNames[hotkey]; + keycfg.SetInt(btn, hkGeneralKeyMap[i]); + joycfg.SetInt(btn, hkGeneralJoyMap[i]); i++; } - Config::JoystickID = Input::JoystickID; + instcfg.SetInt("JoystickID", joystickID); Config::Save(); + emuInstance->inputLoadConfig(); + closeDlg(); } void InputConfigDialog::on_InputConfigDialog_rejected() { - Input::JoystickID = Config::JoystickID; - Input::OpenJoystick(); + Config::Table& instcfg = emuInstance->getLocalConfig(); + emuInstance->setJoystick(instcfg.GetInt("JoystickID")); closeDlg(); } @@ -225,6 +244,11 @@ void InputConfigDialog::on_cbxJoystick_currentIndexChanged(int id) // prevent a spurious change if (ui->cbxJoystick->count() < 2) return; - Input::JoystickID = id; - Input::OpenJoystick(); + joystickID = id; + emuInstance->setJoystick(id); +} + +SDL_Joystick* InputConfigDialog::getJoystick() +{ + return emuInstance->getJoystick(); } diff --git a/src/frontend/qt_sdl/InputConfig/InputConfigDialog.h b/src/frontend/qt_sdl/InputConfig/InputConfigDialog.h index 8d4f882aa4..50ad1802a1 100644 --- a/src/frontend/qt_sdl/InputConfig/InputConfigDialog.h +++ b/src/frontend/qt_sdl/InputConfig/InputConfigDialog.h @@ -24,6 +24,7 @@ #include #include "Config.h" +#include "EmuInstance.h" static constexpr int keypad_num = 12; @@ -89,6 +90,8 @@ class InputConfigDialog : public QDialog explicit InputConfigDialog(QWidget* parent); ~InputConfigDialog(); + SDL_Joystick* getJoystick(); + static InputConfigDialog* currentDlg; static InputConfigDialog* openDlg(QWidget* parent) { @@ -123,9 +126,12 @@ private slots: Ui::InputConfigDialog* ui; + EmuInstance* emuInstance; + int keypadKeyMap[12], keypadJoyMap[12]; int addonsKeyMap[hk_addons.size()], addonsJoyMap[hk_addons.size()]; int hkGeneralKeyMap[hk_general.size()], hkGeneralJoyMap[hk_general.size()]; + int joystickID; }; diff --git a/src/frontend/qt_sdl/InputConfig/MapButton.h b/src/frontend/qt_sdl/InputConfig/MapButton.h index 5d4fb3eb64..2162cb060f 100644 --- a/src/frontend/qt_sdl/InputConfig/MapButton.h +++ b/src/frontend/qt_sdl/InputConfig/MapButton.h @@ -23,8 +23,10 @@ #include -#include "Input.h" #include "Platform.h" +#include "EmuInstance.h" + +class InputConfigDialog; class KeyMapButton : public QPushButton { @@ -76,7 +78,7 @@ class KeyMapButton : public QPushButton if (!ismod) key |= mod; - else if (Input::IsRightModKey(event)) + else if (isRightModKey(event)) key |= (1<<31); *mapping = key; @@ -162,6 +164,9 @@ class JoyMapButton : public QPushButton this->mapping = mapping; this->isHotkey = hotkey; + // the parent will be set later when this button is added to a layout + parentDialog = nullptr; + setCheckable(true); setText(mappingText()); setFocusPolicy(Qt::StrongFocus); //Fixes binding keys in macOS @@ -176,6 +181,20 @@ class JoyMapButton : public QPushButton } protected: + void showEvent(QShowEvent* event) override + { + if (event->spontaneous()) return; + + QWidget* w = parentWidget(); + for (;;) + { + parentDialog = qobject_cast(w); + if (parentDialog) break; + w = w->parentWidget(); + if (!w) break; + } + } + void keyPressEvent(QKeyEvent* event) override { if (!isChecked()) return QPushButton::keyPressEvent(event); @@ -203,7 +222,7 @@ class JoyMapButton : public QPushButton void timerEvent(QTimerEvent* event) override { - SDL_Joystick* joy = Input::Joystick; + SDL_Joystick* joy = parentDialog->getJoystick(); if (!joy) { click(); return; } if (!SDL_JoystickGetAttached(joy)) { click(); return; } @@ -279,13 +298,15 @@ private slots: timerID = startTimer(50); memset(axesRest, 0, sizeof(axesRest)); - if (Input::Joystick && SDL_JoystickGetAttached(Input::Joystick)) + + SDL_Joystick* joy = parentDialog->getJoystick(); + if (joy && SDL_JoystickGetAttached(joy)) { - int naxes = SDL_JoystickNumAxes(Input::Joystick); + int naxes = SDL_JoystickNumAxes(joy); if (naxes > 16) naxes = 16; for (int a = 0; a < naxes; a++) { - axesRest[a] = SDL_JoystickGetAxis(Input::Joystick, a); + axesRest[a] = SDL_JoystickGetAxis(joy, a); } } } @@ -349,6 +370,8 @@ private slots: return str; } + InputConfigDialog* parentDialog; + int* mapping; bool isHotkey; diff --git a/src/frontend/qt_sdl/InterfaceSettingsDialog.cpp b/src/frontend/qt_sdl/InterfaceSettingsDialog.cpp index 851e7abf6f..ecc3231f9a 100644 --- a/src/frontend/qt_sdl/InterfaceSettingsDialog.cpp +++ b/src/frontend/qt_sdl/InterfaceSettingsDialog.cpp @@ -31,21 +31,26 @@ InterfaceSettingsDialog::InterfaceSettingsDialog(QWidget* parent) : QDialog(pare ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose); - ui->cbMouseHide->setChecked(Config::MouseHide != 0); - ui->spinMouseHideSeconds->setEnabled(Config::MouseHide != 0); - ui->spinMouseHideSeconds->setValue(Config::MouseHideSeconds); - ui->cbPauseLostFocus->setChecked(Config::PauseLostFocus != 0); - ui->spinMaxFPS->setValue(Config::MaxFPS); + emuInstance = ((MainWindow*)parent)->getEmuInstance(); + + auto& cfg = emuInstance->getGlobalConfig(); + + ui->cbMouseHide->setChecked(cfg.GetBool("MouseHide")); + ui->spinMouseHideSeconds->setEnabled(ui->cbMouseHide->isChecked()); + ui->spinMouseHideSeconds->setValue(cfg.GetInt("MouseHideSeconds")); + ui->cbPauseLostFocus->setChecked(cfg.GetBool("PauseLostFocus")); + ui->spinMaxFPS->setValue(cfg.GetInt("MaxFPS")); const QList themeKeys = QStyleFactory::keys(); const QString currentTheme = qApp->style()->objectName(); + QString cfgTheme = cfg.GetQString("UITheme"); ui->cbxUITheme->addItem("System default", ""); for (int i = 0; i < themeKeys.length(); i++) { ui->cbxUITheme->addItem(themeKeys[i], themeKeys[i]); - if (!Config::UITheme.empty() && themeKeys[i].compare(currentTheme, Qt::CaseInsensitive) == 0) + if (!cfgTheme.isEmpty() && themeKeys[i].compare(currentTheme, Qt::CaseInsensitive) == 0) ui->cbxUITheme->setCurrentIndex(i + 1); } } @@ -57,36 +62,31 @@ InterfaceSettingsDialog::~InterfaceSettingsDialog() void InterfaceSettingsDialog::on_cbMouseHide_clicked() { - if (ui->spinMouseHideSeconds->isEnabled()) - { - ui->spinMouseHideSeconds->setEnabled(false); - } - else - { - ui->spinMouseHideSeconds->setEnabled(true); - } + ui->spinMouseHideSeconds->setEnabled(ui->cbMouseHide->isChecked()); } void InterfaceSettingsDialog::done(int r) { if (r == QDialog::Accepted) { - Config::MouseHide = ui->cbMouseHide->isChecked() ? 1:0; - Config::MouseHideSeconds = ui->spinMouseHideSeconds->value(); - Config::PauseLostFocus = ui->cbPauseLostFocus->isChecked() ? 1:0; - Config::MaxFPS = ui->spinMaxFPS->value(); + auto& cfg = emuInstance->getGlobalConfig(); + + cfg.SetBool("MouseHide", ui->cbMouseHide->isChecked()); + cfg.SetInt("MouseHideSeconds", ui->spinMouseHideSeconds->value()); + cfg.SetBool("PauseLostFocus", ui->cbPauseLostFocus->isChecked()); + cfg.SetInt("MaxFPS", ui->spinMaxFPS->value()); QString themeName = ui->cbxUITheme->currentData().toString(); - Config::UITheme = themeName.toStdString(); + cfg.SetQString("UITheme", themeName); Config::Save(); - if (!Config::UITheme.empty()) + if (!themeName.isEmpty()) qApp->setStyle(themeName); else qApp->setStyle(*systemThemeName); - emit updateMouseTimer(); + emit updateInterfaceSettings(); } QDialog::done(r); diff --git a/src/frontend/qt_sdl/InterfaceSettingsDialog.h b/src/frontend/qt_sdl/InterfaceSettingsDialog.h index 5a23b6eafc..97d18f22f6 100644 --- a/src/frontend/qt_sdl/InterfaceSettingsDialog.h +++ b/src/frontend/qt_sdl/InterfaceSettingsDialog.h @@ -24,6 +24,8 @@ namespace Ui { class InterfaceSettingsDialog; } class InterfaceSettingsDialog; +class EmuInstance; + class InterfaceSettingsDialog : public QDialog { Q_OBJECT @@ -51,7 +53,7 @@ class InterfaceSettingsDialog : public QDialog } signals: - void updateMouseTimer(); + void updateInterfaceSettings(); private slots: void done(int r); @@ -60,6 +62,8 @@ private slots: private: Ui::InterfaceSettingsDialog* ui; + + EmuInstance* emuInstance; }; #endif // INTERFACESETTINGSDIALOG_H diff --git a/src/frontend/qt_sdl/LocalMP.cpp b/src/frontend/qt_sdl/LocalMP.cpp index 7ea986866c..3f4b21d334 100644 --- a/src/frontend/qt_sdl/LocalMP.cpp +++ b/src/frontend/qt_sdl/LocalMP.cpp @@ -16,25 +16,10 @@ with melonDS. If not, see http://www.gnu.org/licenses/. */ -#include -#include -#include - -#ifdef __WIN32__ - #include -#else - #include - #include - #include - #ifdef __APPLE__ - #include "sem_timedwait.h" - #endif -#endif - -#include -#include - -#include "Config.h" +#include +#include +#include + #include "LocalMP.h" #include "Platform.h" @@ -47,17 +32,12 @@ using Platform::LogLevel; namespace LocalMP { -u32 MPUniqueID; -u8 PacketBuffer[2048]; - -struct MPQueueHeader +struct MPStatusData { - 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 MPHostinst; // instance ID from which the last CMD frame was sent u16 MPReplyBitmask; // bitmask of which clients replied in time }; @@ -70,230 +50,73 @@ struct MPPacketHeader u64 Timestamp; }; -struct MPSync -{ - u32 Magic; - u32 SenderID; - u16 ClientMask; - u16 Type; - u64 Timestamp; -}; - -QSharedMemory* MPQueue; -int InstanceID; -u32 PacketReadOffset; -u32 ReplyReadOffset; +const u32 kPacketQueueSize = 0x10000; +const u32 kReplyQueueSize = 0x10000; +const u32 kMaxFrameSize = 0x948; -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; +QMutex MPQueueLock; +MPStatusData MPStatus; +u8 MPPacketQueue[kPacketQueueSize]; +u8 MPReplyQueue[kReplyQueueSize]; +u32 PacketReadOffset[16]; +u32 ReplyReadOffset[16]; 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]; +QSemaphore SemPool[32]; void SemPoolInit() { for (int i = 0; i < 32; i++) { - SemPool[i] = INVALID_HANDLE_VALUE; - SemInited[i] = false; + SemPool[i].acquire(SemPool[i].available()); } } -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 = CreateSemaphoreA(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; + SemPool[num].release(1); + return true; } 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 SemPool[num].tryAcquire(1); - return sem_timedwait(SemPool[num], &ts) == 0; + return SemPool[num].tryAcquire(1, timeout); } void SemReset(int num) { - while (sem_trywait(SemPool[num]) == 0); + SemPool[num].acquire(SemPool[num].available()); } -#endif - bool Init() { - MPQueue = new QSharedMemory("melonNIFI"); - - if (!MPQueue->attach()) - { - Log(LogLevel::Info, "MP sharedmem doesn't exist. creating\n"); - if (!MPQueue->create(kQueueSize)) - { - Log(LogLevel::Error, "MP sharedmem create failed :( (%d)\n", MPQueue->error()); - delete MPQueue; - MPQueue = nullptr; - return false; - } + MPQueueLock.lock(); - 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<InstanceBitmask |= (1<ConnectedBitmask |= (1 << i); - break; - } - } - header->NumInstances++; - - PacketReadOffset = header->PacketWriteOffset; - ReplyReadOffset = header->ReplyWriteOffset; + memset(MPPacketQueue, 0, kPacketQueueSize); + memset(MPReplyQueue, 0, kReplyQueueSize); + memset(&MPStatus, 0, sizeof(MPStatus)); + memset(PacketReadOffset, 0, sizeof(PacketReadOffset)); + memset(ReplyReadOffset, 0, sizeof(ReplyReadOffset)); - MPQueue->unlock(); + MPQueueLock.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; - Log(LogLevel::Info, "MP comm init OK, instance ID %d\n", InstanceID); + Log(LogLevel::Info, "MP comm init OK\n"); RecvTimeout = 25; @@ -302,25 +125,6 @@ bool Init() void DeInit() { - if (MPQueue) - { - MPQueue->lock(); - if (MPQueue->data() != nullptr) - { - MPQueueHeader *header = (MPQueueHeader *) MPQueue->data(); - header->ConnectedBitmask &= ~(1 << InstanceID); - header->InstanceBitmask &= ~(1 << InstanceID); - header->NumInstances--; - } - MPQueue->unlock(); - - SemPoolDeinit(); - - MPQueue->detach(); - } - - delete MPQueue; - MPQueue = nullptr; } void SetRecvTimeout(int timeout) @@ -328,54 +132,48 @@ void SetRecvTimeout(int timeout) RecvTimeout = timeout; } -void Begin() +void Begin(int inst) { - if (!MPQueue) return; - MPQueue->lock(); - MPQueueHeader* header = (MPQueueHeader*)MPQueue->data(); - PacketReadOffset = header->PacketWriteOffset; - ReplyReadOffset = header->ReplyWriteOffset; - SemReset(InstanceID); - SemReset(16+InstanceID); - header->ConnectedBitmask |= (1 << InstanceID); - MPQueue->unlock(); + MPQueueLock.lock(); + PacketReadOffset[inst] = MPStatus.PacketWriteOffset; + ReplyReadOffset[inst] = MPStatus.ReplyWriteOffset; + SemReset(inst); + SemReset(16+inst); + MPStatus.ConnectedBitmask |= (1 << inst); + MPQueueLock.unlock(); } -void End() +void End(int inst) { - if (!MPQueue) return; - MPQueue->lock(); - MPQueueHeader* header = (MPQueueHeader*)MPQueue->data(); - //SemReset(InstanceID); - //SemReset(16+InstanceID); - header->ConnectedBitmask &= ~(1 << InstanceID); - MPQueue->unlock(); + MPQueueLock.lock(); + MPStatus.ConnectedBitmask &= ~(1 << inst); + MPQueueLock.unlock(); } -void FIFORead(int fifo, void* buf, int len) +void FIFORead(int inst, int fifo, void* buf, int len) { - u8* data = (u8*)MPQueue->data(); + u8* data; - u32 offset, start, end; + u32 offset, datalen; if (fifo == 0) { - offset = PacketReadOffset; - start = kPacketStart; - end = kPacketEnd; + offset = PacketReadOffset[inst]; + data = MPPacketQueue; + datalen = kPacketQueueSize; } else { - offset = ReplyReadOffset; - start = kReplyStart; - end = kReplyEnd; + offset = ReplyReadOffset[inst]; + data = MPReplyQueue; + datalen = kReplyQueueSize; } - if ((offset + len) >= end) + if ((offset + len) >= datalen) { - u32 part1 = end - offset; + u32 part1 = datalen - offset; memcpy(buf, &data[offset], part1); - memcpy(&((u8*)buf)[part1], &data[start], len - part1); - offset = start + len - part1; + memcpy(&((u8*)buf)[part1], data, len - part1); + offset = len - part1; } else { @@ -383,35 +181,34 @@ void FIFORead(int fifo, void* buf, int len) offset += len; } - if (fifo == 0) PacketReadOffset = offset; - else ReplyReadOffset = offset; + if (fifo == 0) PacketReadOffset[inst] = offset; + else ReplyReadOffset[inst] = offset; } -void FIFOWrite(int fifo, void* buf, int len) +void FIFOWrite(int inst, int fifo, void* buf, int len) { - u8* data = (u8*)MPQueue->data(); - MPQueueHeader* header = (MPQueueHeader*)&data[0]; + u8* data; - u32 offset, start, end; + u32 offset, datalen; if (fifo == 0) { - offset = header->PacketWriteOffset; - start = kPacketStart; - end = kPacketEnd; + offset = MPStatus.PacketWriteOffset; + data = MPPacketQueue; + datalen = kPacketQueueSize; } else { - offset = header->ReplyWriteOffset; - start = kReplyStart; - end = kReplyEnd; + offset = MPStatus.ReplyWriteOffset; + data = MPReplyQueue; + datalen = kReplyQueueSize; } - if ((offset + len) >= end) + if ((offset + len) >= datalen) { - u32 part1 = end - offset; + u32 part1 = datalen - offset; memcpy(&data[offset], buf, part1); - memcpy(&data[start], &((u8*)buf)[part1], len - part1); - offset = start + len - part1; + memcpy(data, &((u8*)buf)[part1], len - part1); + offset = len - part1; } else { @@ -419,53 +216,56 @@ void FIFOWrite(int fifo, void* buf, int len) offset += len; } - if (fifo == 0) header->PacketWriteOffset = offset; - else header->ReplyWriteOffset = offset; + if (fifo == 0) MPStatus.PacketWriteOffset = offset; + else MPStatus.ReplyWriteOffset = offset; } -int SendPacketGeneric(u32 type, u8* packet, int len, u64 timestamp) +int SendPacketGeneric(int inst, u32 type, u8* packet, int len, u64 timestamp) { - if (!MPQueue) return 0; - MPQueue->lock(); - u8* data = (u8*)MPQueue->data(); - MPQueueHeader* header = (MPQueueHeader*)&data[0]; + if (len > kMaxFrameSize) + { + Log(LogLevel::Warn, "wifi: attempting to send frame too big (len=%d max=%d)\n", len, kMaxFrameSize); + return 0; + } - u16 mask = header->ConnectedBitmask; + MPQueueLock.lock(); + + u16 mask = MPStatus.ConnectedBitmask; // TODO: check if the FIFO is full! MPPacketHeader pktheader; pktheader.Magic = 0x4946494E; - pktheader.SenderID = InstanceID; + pktheader.SenderID = inst; pktheader.Type = type; pktheader.Length = len; pktheader.Timestamp = timestamp; type &= 0xFFFF; int nfifo = (type == 2) ? 1 : 0; - FIFOWrite(nfifo, &pktheader, sizeof(pktheader)); + FIFOWrite(inst, nfifo, &pktheader, sizeof(pktheader)); if (len) - FIFOWrite(nfifo, packet, len); + FIFOWrite(inst, 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); + MPStatus.MPHostinst = inst; + MPStatus.MPReplyBitmask = 0; + ReplyReadOffset[inst] = MPStatus.ReplyWriteOffset; + SemReset(16 + inst); } else if (type == 2) { - header->MPReplyBitmask |= (1 << InstanceID); + MPStatus.MPReplyBitmask |= (1 << inst); } - MPQueue->unlock(); + MPQueueLock.unlock(); if (type == 2) { - SemPost(16 + header->MPHostInstanceID); + SemPost(16 + MPStatus.MPHostinst); } else { @@ -479,119 +279,102 @@ int SendPacketGeneric(u32 type, u8* packet, int len, u64 timestamp) return len; } -int RecvPacketGeneric(u8* packet, bool block, u64* timestamp) +int RecvPacketGeneric(int inst, u8* packet, bool block, u64* timestamp) { - if (!MPQueue) return 0; for (;;) { - if (!SemWait(InstanceID, block ? RecvTimeout : 0)) + if (!SemWait(inst, block ? RecvTimeout : 0)) { return 0; } - MPQueue->lock(); - u8* data = (u8*)MPQueue->data(); - MPQueueHeader* header = (MPQueueHeader*)&data[0]; + MPQueueLock.lock(); MPPacketHeader pktheader; - FIFORead(0, &pktheader, sizeof(pktheader)); + FIFORead(inst, 0, &pktheader, sizeof(pktheader)); if (pktheader.Magic != 0x4946494E) { Log(LogLevel::Warn, "PACKET FIFO OVERFLOW\n"); - PacketReadOffset = header->PacketWriteOffset; - SemReset(InstanceID); - MPQueue->unlock(); + PacketReadOffset[inst] = MPStatus.PacketWriteOffset; + SemReset(inst); + MPQueueLock.unlock(); return 0; } - if (pktheader.SenderID == InstanceID) + if (pktheader.SenderID == inst) { // skip this packet - PacketReadOffset += pktheader.Length; - if (PacketReadOffset >= kPacketEnd) - PacketReadOffset += kPacketStart - kPacketEnd; + PacketReadOffset[inst] += pktheader.Length; + if (PacketReadOffset[inst] >= kPacketQueueSize) + PacketReadOffset[inst] -= kPacketQueueSize; - MPQueue->unlock(); + MPQueueLock.unlock(); continue; } if (pktheader.Length) { - FIFORead(0, packet, pktheader.Length); + FIFORead(inst, 0, packet, pktheader.Length); if (pktheader.Type == 1) LastHostID = pktheader.SenderID; } if (timestamp) *timestamp = pktheader.Timestamp; - MPQueue->unlock(); + MPQueueLock.unlock(); return pktheader.Length; } } -int SendPacket(u8* packet, int len, u64 timestamp) +int SendPacket(int inst, u8* packet, int len, u64 timestamp) { - return SendPacketGeneric(0, packet, len, timestamp); + return SendPacketGeneric(inst, 0, packet, len, timestamp); } -int RecvPacket(u8* packet, u64* timestamp) +int RecvPacket(int inst, u8* packet, u64* timestamp) { - return RecvPacketGeneric(packet, false, timestamp); + return RecvPacketGeneric(inst, packet, false, timestamp); } -int SendCmd(u8* packet, int len, u64 timestamp) +int SendCmd(int inst, u8* packet, int len, u64 timestamp) { - return SendPacketGeneric(1, packet, len, timestamp); + return SendPacketGeneric(inst, 1, packet, len, timestamp); } -int SendReply(u8* packet, int len, u64 timestamp, u16 aid) +int SendReply(int inst, u8* packet, int len, u64 timestamp, u16 aid) { - return SendPacketGeneric(2 | (aid<<16), packet, len, timestamp); + return SendPacketGeneric(inst, 2 | (aid<<16), packet, len, timestamp); } -int SendAck(u8* packet, int len, u64 timestamp) +int SendAck(int inst, u8* packet, int len, u64 timestamp) { - return SendPacketGeneric(3, packet, len, timestamp); + return SendPacketGeneric(inst, 3, packet, len, timestamp); } -int RecvHostPacket(u8* packet, u64* timestamp) +int RecvHostPacket(int inst, u8* packet, u64* timestamp) { - if (!MPQueue) return -1; - 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(); + u16 curinstmask = MPStatus.ConnectedBitmask; if (!(curinstmask & (1 << LastHostID))) return -1; } - return RecvPacketGeneric(packet, true, timestamp); + return RecvPacketGeneric(inst, packet, true, timestamp); } -u16 RecvReplies(u8* packets, u64 timestamp, u16 aidmask) +u16 RecvReplies(int inst, u8* packets, u64 timestamp, u16 aidmask) { - if (!MPQueue) return 0; - u16 ret = 0; - u16 myinstmask = (1 << InstanceID); + u16 myinstmask = (1 << inst); u16 curinstmask; - { - MPQueue->lock(); - u8* data = (u8*)MPQueue->data(); - MPQueueHeader* header = (MPQueueHeader*)&data[0]; - curinstmask = header->ConnectedBitmask; - MPQueue->unlock(); - } + curinstmask = MPStatus.ConnectedBitmask; // if all clients have left: return early if ((myinstmask & curinstmask) == curinstmask) @@ -599,44 +382,42 @@ u16 RecvReplies(u8* packets, u64 timestamp, u16 aidmask) for (;;) { - if (!SemWait(16+InstanceID, RecvTimeout)) + if (!SemWait(16+inst, RecvTimeout)) { // no more replies available return ret; } - MPQueue->lock(); - u8* data = (u8*)MPQueue->data(); - MPQueueHeader* header = (MPQueueHeader*)&data[0]; + MPQueueLock.lock(); MPPacketHeader pktheader; - FIFORead(1, &pktheader, sizeof(pktheader)); + FIFORead(inst, 1, &pktheader, sizeof(pktheader)); if (pktheader.Magic != 0x4946494E) { Log(LogLevel::Warn, "REPLY FIFO OVERFLOW\n"); - ReplyReadOffset = header->ReplyWriteOffset; - SemReset(16+InstanceID); - MPQueue->unlock(); + ReplyReadOffset[inst] = MPStatus.ReplyWriteOffset; + SemReset(16+inst); + MPQueueLock.unlock(); return 0; } - if ((pktheader.SenderID == InstanceID) || // packet we sent out (shouldn't happen, but hey) + if ((pktheader.SenderID == inst) || // 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; + ReplyReadOffset[inst] += pktheader.Length; + if (ReplyReadOffset[inst] >= kReplyQueueSize) + ReplyReadOffset[inst] -= kReplyQueueSize; - MPQueue->unlock(); + MPQueueLock.unlock(); continue; } if (pktheader.Length) { u32 aid = (pktheader.Type >> 16); - FIFORead(1, &packets[(aid-1)*1024], pktheader.Length); + FIFORead(inst, 1, &packets[(aid-1)*1024], pktheader.Length); ret |= (1 << aid); } @@ -646,11 +427,11 @@ u16 RecvReplies(u8* packets, u64 timestamp, u16 aidmask) { // all the clients have sent their reply - MPQueue->unlock(); + MPQueueLock.unlock(); return ret; } - MPQueue->unlock(); + MPQueueLock.unlock(); } } diff --git a/src/frontend/qt_sdl/LocalMP.h b/src/frontend/qt_sdl/LocalMP.h index e7b4188ac6..dcf69807ef 100644 --- a/src/frontend/qt_sdl/LocalMP.h +++ b/src/frontend/qt_sdl/LocalMP.h @@ -30,16 +30,16 @@ 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); +void Begin(int inst); +void End(int inst); + +int SendPacket(int inst, u8* data, int len, u64 timestamp); +int RecvPacket(int inst, u8* data, u64* timestamp); +int SendCmd(int inst, u8* data, int len, u64 timestamp); +int SendReply(int inst, u8* data, int len, u64 timestamp, u16 aid); +int SendAck(int inst, u8* data, int len, u64 timestamp); +int RecvHostPacket(int inst, u8* data, u64* timestamp); +u16 RecvReplies(int inst, u8* data, u64 timestamp, u16 aidmask); } diff --git a/src/frontend/qt_sdl/MPSettingsDialog.cpp b/src/frontend/qt_sdl/MPSettingsDialog.cpp index bd64dfa28b..11ad43cb31 100644 --- a/src/frontend/qt_sdl/MPSettingsDialog.cpp +++ b/src/frontend/qt_sdl/MPSettingsDialog.cpp @@ -22,9 +22,10 @@ #include "types.h" #include "Platform.h" #include "Config.h" +#include "main.h" -#include "LAN_Socket.h" -#include "LAN_PCap.h" +#include "Net_Slirp.h" +#include "Net_PCap.h" #include "Wifi.h" #include "MPSettingsDialog.h" @@ -33,21 +34,22 @@ MPSettingsDialog* MPSettingsDialog::currentDlg = nullptr; -extern bool RunningSomething; - MPSettingsDialog::MPSettingsDialog(QWidget* parent) : QDialog(parent), ui(new Ui::MPSettingsDialog) { ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose); + emuInstance = ((MainWindow*)parent)->getEmuInstance(); + + auto& cfg = emuInstance->getGlobalConfig(); 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); + grpAudioMode->button(cfg.GetInt("MP.AudioMode"))->setChecked(true); - ui->sbReceiveTimeout->setValue(Config::MPRecvTimeout); + ui->sbReceiveTimeout->setValue(cfg.GetInt("MP.RecvTimeout")); } MPSettingsDialog::~MPSettingsDialog() @@ -59,8 +61,9 @@ void MPSettingsDialog::done(int r) { if (r == QDialog::Accepted) { - Config::MPAudioMode = grpAudioMode->checkedId(); - Config::MPRecvTimeout = ui->sbReceiveTimeout->value(); + auto& cfg = emuInstance->getGlobalConfig(); + cfg.SetInt("MP.AudioMode", grpAudioMode->checkedId()); + cfg.SetInt("MP.RecvTimeout", ui->sbReceiveTimeout->value()); Config::Save(); } @@ -69,5 +72,3 @@ void MPSettingsDialog::done(int r) closeDlg(); } - -// diff --git a/src/frontend/qt_sdl/MPSettingsDialog.h b/src/frontend/qt_sdl/MPSettingsDialog.h index 837ac8dbbc..cdd05831ea 100644 --- a/src/frontend/qt_sdl/MPSettingsDialog.h +++ b/src/frontend/qt_sdl/MPSettingsDialog.h @@ -25,6 +25,8 @@ namespace Ui { class MPSettingsDialog; } class MPSettingsDialog; +class EmuInstance; + class MPSettingsDialog : public QDialog { Q_OBJECT @@ -58,6 +60,7 @@ private slots: private: Ui::MPSettingsDialog* ui; + EmuInstance* emuInstance; QButtonGroup* grpAudioMode; }; diff --git a/src/frontend/qt_sdl/Net.cpp b/src/frontend/qt_sdl/Net.cpp new file mode 100644 index 0000000000..fa201d4211 --- /dev/null +++ b/src/frontend/qt_sdl/Net.cpp @@ -0,0 +1,112 @@ +/* + Copyright 2016-2023 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 +#include +#include +#include "Net.h" +#include "PacketDispatcher.h" +#include "Platform.h" +#include "Config.h" + +using namespace melonDS; + +namespace Net +{ + +using Platform::Log; +using Platform::LogLevel; + +bool Inited = false; +bool DirectMode; + +PacketDispatcher Dispatcher; + + +bool Init() +{ + if (Inited) DeInit(); + + Dispatcher.clear(); + + Config::Table cfg = Config::GetGlobalTable(); + DirectMode = cfg.GetBool("LAN.DirectMode"); + + bool ret = false; + if (DirectMode) + ret = Net_PCap::Init(); + else + ret = Net_Slirp::Init(); + + Inited = ret; + return ret; +} + +void DeInit() +{ + if (!Inited) return; + + if (DirectMode) + Net_PCap::DeInit(); + else + Net_Slirp::DeInit(); + + Inited = false; +} + + +void RegisterInstance(int inst) +{ + Dispatcher.registerInstance(inst); +} + +void UnregisterInstance(int inst) +{ + Dispatcher.unregisterInstance(inst); +} + + +void RXEnqueue(const void* buf, int len) +{ + Dispatcher.sendPacket(nullptr, 0, buf, len, 16, 0xFFFF); +} + + +int SendPacket(u8* data, int len, int inst) +{ + if (DirectMode) + return Net_PCap::SendPacket(data, len); + else + return Net_Slirp::SendPacket(data, len); +} + +int RecvPacket(u8* data, int inst) +{ + if (DirectMode) + Net_PCap::RecvCheck(); + else + Net_Slirp::RecvCheck(); + + int ret = 0; + if (!Dispatcher.recvPacket(nullptr, nullptr, data, &ret, inst)) + return 0; + + return ret; +} + +} diff --git a/src/frontend/qt_sdl/AudioInOut.h b/src/frontend/qt_sdl/Net.h similarity index 67% rename from src/frontend/qt_sdl/AudioInOut.h rename to src/frontend/qt_sdl/Net.h index 0bf36540ea..97c65ef3b8 100644 --- a/src/frontend/qt_sdl/AudioInOut.h +++ b/src/frontend/qt_sdl/Net.h @@ -16,34 +16,28 @@ with melonDS. If not, see http://www.gnu.org/licenses/. */ -#ifndef AUDIO_INOUT_H -#define AUDIO_INOUT_H +#ifndef NET_H +#define NET_H #include "types.h" +#include "Net_PCap.h" +#include "Net_Slirp.h" -#include - -class EmuThread; -namespace melonDS -{ -class NDS; -} -namespace AudioInOut +namespace Net { +using namespace melonDS; -void Init(EmuThread* thread); +bool Init(); void DeInit(); -void MicProcess(melonDS::NDS& nds); -void AudioMute(QMainWindow* mainWindow); - -void AudioSync(melonDS::NDS& nds); +void RegisterInstance(int inst); +void UnregisterInstance(int inst); -void UpdateSettings(melonDS::NDS& nds); +void RXEnqueue(const void* buf, int len); -void Enable(); -void Disable(); +int SendPacket(u8* data, int len, int inst); +int RecvPacket(u8* data, int inst); } -#endif +#endif // NET_H diff --git a/src/frontend/qt_sdl/LAN_PCap.cpp b/src/frontend/qt_sdl/Net_PCap.cpp similarity index 80% rename from src/frontend/qt_sdl/LAN_PCap.cpp rename to src/frontend/qt_sdl/Net_PCap.cpp index 4a1ebc3578..967ca70030 100644 --- a/src/frontend/qt_sdl/LAN_PCap.cpp +++ b/src/frontend/qt_sdl/Net_PCap.cpp @@ -16,16 +16,12 @@ with melonDS. If not, see http://www.gnu.org/licenses/. */ -// direct LAN interface. Currently powered by libpcap, may change. - -#include -#include #include #include -#include "Wifi.h" -#include "LAN_PCap.h" +#include "Net.h" #include "Config.h" #include "Platform.h" +#include "main.h" #ifdef __WIN32__ #include @@ -53,7 +49,7 @@ using Platform::LogLevel; #define DECL_PCAP_FUNC(ret, name, args, args2) \ typedef ret (*type_##name) args; \ - type_##name ptr_##name = NULL; \ + type_##name ptr_##name = nullptr; \ ret name args { return ptr_##name args2; } DECL_PCAP_FUNC(int, pcap_findalldevs, (pcap_if_t** alldevs, char* errbuf), (alldevs,errbuf)) @@ -66,7 +62,7 @@ DECL_PCAP_FUNC(int, pcap_dispatch, (pcap_t* dev, int num, pcap_handler callback, DECL_PCAP_FUNC(const u_char*, pcap_next, (pcap_t* dev, struct pcap_pkthdr* hdr), (dev,hdr)) -namespace LAN_PCap +namespace Net_PCap { const char* PCapLibNames[] = @@ -82,20 +78,16 @@ const char* PCapLibNames[] = "libpcap.so.1", "libpcap.so", #endif - NULL + nullptr }; -AdapterData* Adapters = NULL; +AdapterData* Adapters = nullptr; int NumAdapters = 0; -Platform::DynamicLibrary* PCapLib = NULL; -pcap_t* PCapAdapter = NULL; +Platform::DynamicLibrary* PCapLib = nullptr; +pcap_t* PCapAdapter = nullptr; AdapterData* PCapAdapterData; -u8 PacketBuffer[2048]; -int PacketLen; -volatile int RXNum; - #define LOAD_PCAP_FUNC(sym) \ ptr_##sym = (type_##sym)DynamicLibrary_LoadFunction(lib, #sym); \ @@ -115,18 +107,15 @@ bool TryLoadPCap(Platform::DynamicLibrary *lib) return true; } -bool Init(bool open_adapter) +bool InitAdapterList() { - PCapAdapter = NULL; - PacketLen = 0; - RXNum = 0; - NumAdapters = 0; // TODO: how to deal with cases where an adapter is unplugged or changes config?? if (!PCapLib) { - PCapLib = NULL; + PCapLib = nullptr; + PCapAdapter = nullptr; for (int i = 0; PCapLibNames[i]; i++) { @@ -144,7 +133,7 @@ bool Init(bool open_adapter) break; } - if (PCapLib == NULL) + if (PCapLib == nullptr) { Log(LogLevel::Error, "PCap: init failed\n"); return false; @@ -156,7 +145,7 @@ bool Init(bool open_adapter) pcap_if_t* alldevs; ret = pcap_findalldevs(&alldevs, errbuf); - if (ret < 0 || alldevs == NULL) + if (ret < 0 || alldevs == nullptr) { Log(LogLevel::Warn, "PCap: no devices available\n"); return false; @@ -172,18 +161,10 @@ bool Init(bool open_adapter) dev = alldevs; while (dev) { - adata->Internal = dev; - -#ifdef __WIN32__ - // hax - int len = strlen(dev->name); - len -= 12; if (len > 127) len = 127; - strncpy(adata->DeviceName, &dev->name[12], len); - adata->DeviceName[len] = '\0'; -#else strncpy(adata->DeviceName, dev->name, 127); adata->DeviceName[127] = '\0'; +#ifndef __WIN32__ strncpy(adata->FriendlyName, adata->DeviceName, 127); adata->FriendlyName[127] = '\0'; #endif // __WIN32__ @@ -196,12 +177,12 @@ bool Init(bool open_adapter) ULONG bufsize = 16384; IP_ADAPTER_ADDRESSES* buf = (IP_ADAPTER_ADDRESSES*)HeapAlloc(GetProcessHeap(), 0, bufsize); - ULONG uret = GetAdaptersAddresses(AF_INET, 0, NULL, buf, &bufsize); + ULONG uret = GetAdaptersAddresses(AF_INET, 0, nullptr, buf, &bufsize); if (uret == ERROR_BUFFER_OVERFLOW) { HeapFree(GetProcessHeap(), 0, buf); buf = (IP_ADAPTER_ADDRESSES*)HeapAlloc(GetProcessHeap(), 0, bufsize); - uret = GetAdaptersAddresses(AF_INET, 0, NULL, buf, &bufsize); + uret = GetAdaptersAddresses(AF_INET, 0, nullptr, buf, &bufsize); } if (uret != ERROR_SUCCESS) { @@ -215,16 +196,16 @@ bool Init(bool open_adapter) IP_ADAPTER_ADDRESSES* addr = buf; while (addr) { - if (strcmp(addr->AdapterName, adata->DeviceName)) + if (strcmp(addr->AdapterName, &adata->DeviceName[12])) { addr = addr->Next; continue; } - WideCharToMultiByte(CP_UTF8, 0, addr->FriendlyName, 127, adata->FriendlyName, 127, NULL, NULL); + WideCharToMultiByte(CP_UTF8, 0, addr->FriendlyName, 127, adata->FriendlyName, 127, nullptr, nullptr); adata->FriendlyName[127] = '\0'; - WideCharToMultiByte(CP_UTF8, 0, addr->Description, 127, adata->Description, 127, NULL, NULL); + WideCharToMultiByte(CP_UTF8, 0, addr->Description, 127, adata->Description, 127, nullptr, nullptr); adata->Description[127] = '\0'; if (addr->PhysicalAddressLength != 6) @@ -287,7 +268,7 @@ bool Init(bool open_adapter) struct sockaddr_in* sa = (sockaddr_in*)curaddr->ifa_addr; memcpy(adata->IP_v4, &sa->sin_addr, 4); } - #ifdef __linux__ +#ifdef __linux__ else if (af == AF_PACKET) { struct sockaddr_ll* sa = (sockaddr_ll*)curaddr->ifa_addr; @@ -296,7 +277,7 @@ bool Init(bool open_adapter) else memcpy(adata->MAC, sa->sll_addr, 6); } - #else +#else else if (af == AF_LINK) { struct sockaddr_dl* sa = (sockaddr_dl*)curaddr->ifa_addr; @@ -305,7 +286,7 @@ bool Init(bool open_adapter) else memcpy(adata->MAC, LLADDR(sa), 6); } - #endif +#endif curaddr = curaddr->ifa_next; } } @@ -314,31 +295,39 @@ bool Init(bool open_adapter) #endif // __WIN32__ - if (!open_adapter) return true; + pcap_freealldevs(alldevs); + return true; +} + +bool Init() +{ + if (!PCapLib) PCapAdapter = nullptr; if (PCapAdapter) pcap_close(PCapAdapter); + InitAdapterList(); + // open pcap device + Config::Table cfg = Config::GetGlobalTable(); + std::string devicename = cfg.GetString("LAN.Device"); PCapAdapterData = &Adapters[0]; for (int i = 0; i < NumAdapters; i++) { - if (!strncmp(Adapters[i].DeviceName, Config::LANDevice.c_str(), 128)) + if (!strncmp(Adapters[i].DeviceName, devicename.c_str(), 128)) PCapAdapterData = &Adapters[i]; } - dev = (pcap_if_t*)PCapAdapterData->Internal; - PCapAdapter = pcap_open_live(dev->name, 2048, PCAP_OPENFLAG_PROMISCUOUS, 1, errbuf); + char errbuf[PCAP_ERRBUF_SIZE]; + PCapAdapter = pcap_open_live(PCapAdapterData->DeviceName, 2048, PCAP_OPENFLAG_PROMISCUOUS, 1, errbuf); if (!PCapAdapter) { Log(LogLevel::Error, "PCap: failed to open adapter %s\n", errbuf); return false; } - pcap_freealldevs(alldevs); - if (pcap_setnonblock(PCapAdapter, 1, errbuf) < 0) { Log(LogLevel::Error, "PCap: failed to set nonblocking mode\n"); - pcap_close(PCapAdapter); PCapAdapter = NULL; + pcap_close(PCapAdapter); PCapAdapter = nullptr; return false; } @@ -352,34 +341,28 @@ void DeInit() if (PCapAdapter) { pcap_close(PCapAdapter); - PCapAdapter = NULL; + PCapAdapter = nullptr; } Platform::DynamicLibrary_Unload(PCapLib); - PCapLib = NULL; + PCapLib = nullptr; } } -void RXCallback(u_char* blarg, const struct pcap_pkthdr* header, const u_char* data) +void RXCallback(u_char* userdata, const struct pcap_pkthdr* header, const u_char* data) { - while (RXNum > 0); - - if (header->len > 2048-64) return; - - PacketLen = header->len; - memcpy(PacketBuffer, data, PacketLen); - RXNum = 1; + Net::RXEnqueue(data, header->len); } int SendPacket(u8* data, int len) { - if (PCapAdapter == NULL) + if (PCapAdapter == nullptr) return 0; if (len > 2048) { - Log(LogLevel::Error, "LAN_SendPacket: error: packet too long (%d)\n", len); + Log(LogLevel::Error, "Net_SendPacket: error: packet too long (%d)\n", len); return 0; } @@ -388,21 +371,12 @@ int SendPacket(u8* data, int len) return len; } -int RecvPacket(u8* data) +void RecvCheck() { - if (PCapAdapter == NULL) - return 0; - - int ret = 0; - if (RXNum > 0) - { - memcpy(data, PacketBuffer, PacketLen); - ret = PacketLen; - RXNum = 0; - } + if (PCapAdapter == nullptr) + return; - pcap_dispatch(PCapAdapter, 1, RXCallback, NULL); - return ret; + pcap_dispatch(PCapAdapter, 1, RXCallback, nullptr); } } diff --git a/src/frontend/qt_sdl/LAN_PCap.h b/src/frontend/qt_sdl/Net_PCap.h similarity index 86% rename from src/frontend/qt_sdl/LAN_PCap.h rename to src/frontend/qt_sdl/Net_PCap.h index 2e03d66a70..f35d4dc671 100644 --- a/src/frontend/qt_sdl/LAN_PCap.h +++ b/src/frontend/qt_sdl/Net_PCap.h @@ -16,12 +16,12 @@ with melonDS. If not, see http://www.gnu.org/licenses/. */ -#ifndef LAN_PCAP_H -#define LAN_PCAP_H +#ifndef NET_PCAP_H +#define NET_PCAP_H #include "types.h" -namespace LAN_PCap +namespace Net_PCap { using namespace melonDS; @@ -33,8 +33,6 @@ struct AdapterData u8 MAC[6]; u8 IP_v4[4]; - - void* Internal; }; @@ -42,12 +40,13 @@ extern AdapterData* Adapters; extern int NumAdapters; -bool Init(bool open_adapter); +bool InitAdapterList(); +bool Init(); void DeInit(); int SendPacket(u8* data, int len); -int RecvPacket(u8* data); +void RecvCheck(); } -#endif // LAN_PCAP_H +#endif // NET_PCAP_H diff --git a/src/frontend/qt_sdl/LAN_Socket.cpp b/src/frontend/qt_sdl/Net_Slirp.cpp similarity index 86% rename from src/frontend/qt_sdl/LAN_Socket.cpp rename to src/frontend/qt_sdl/Net_Slirp.cpp index df0d754b12..ab8725d15e 100644 --- a/src/frontend/qt_sdl/LAN_Socket.cpp +++ b/src/frontend/qt_sdl/Net_Slirp.cpp @@ -16,13 +16,9 @@ with melonDS. If not, see http://www.gnu.org/licenses/. */ -// indirect LAN interface, powered by BSD sockets. - #include -#include #include -#include "Wifi.h" -#include "LAN_Socket.h" +#include "Net.h" #include "FIFO.h" #include "Platform.h" @@ -39,7 +35,7 @@ using namespace melonDS; -namespace LAN_Socket +namespace Net_Slirp { using Platform::Log; @@ -58,10 +54,6 @@ u32 IPv4ID; Slirp* Ctx = nullptr; -/*const int FDListMax = 64; -struct pollfd FDList[FDListMax]; -int FDListSize;*/ - #ifdef __WIN32__ @@ -85,23 +77,6 @@ int clock_gettime(int, struct timespec *spec) #endif // __WIN32__ -void RXEnqueue(const void* buf, int len) -{ - int alignedlen = (len + 3) & ~3; - int totallen = alignedlen + 4; - - if (!RXBuffer.CanFit(totallen >> 2)) - { - Log(LogLevel::Warn, "slirp: !! NOT ENOUGH SPACE IN RX BUFFER\n"); - return; - } - - u32 header = (alignedlen & 0xFFFF) | (len << 16); - RXBuffer.Write(header); - for (int i = 0; i < alignedlen; i += 4) - RXBuffer.Write(((u32*)buf)[i>>2]); -} - ssize_t SlirpCbSendPacket(const void* buf, size_t len, void* opaque) { if (len > 2048) @@ -112,7 +87,7 @@ ssize_t SlirpCbSendPacket(const void* buf, size_t len, void* opaque) Log(LogLevel::Debug, "slirp: response packet of %zu bytes, type %04X\n", len, ntohs(((u16*)buf)[6])); - RXEnqueue(buf, len); + Net::RXEnqueue(buf, len); return len; } @@ -145,40 +120,11 @@ void SlirpCbTimerMod(void* timer, int64_t expire_time, void* opaque) void SlirpCbRegisterPollFD(int fd, void* opaque) { Log(LogLevel::Debug, "Slirp: register poll FD %d\n", fd); - - /*if (FDListSize >= FDListMax) - { - printf("!! SLIRP FD LIST FULL\n"); - return; - } - - for (int i = 0; i < FDListSize; i++) - { - if (FDList[i].fd == fd) return; - } - - FDList[FDListSize].fd = fd; - FDListSize++;*/ } void SlirpCbUnregisterPollFD(int fd, void* opaque) { Log(LogLevel::Debug, "Slirp: unregister poll FD %d\n", fd); - - /*if (FDListSize < 1) - { - printf("!! SLIRP FD LIST EMPTY\n"); - return; - } - - for (int i = 0; i < FDListSize; i++) - { - if (FDList[i].fd == fd) - { - FDListSize--; - FDList[i] = FDList[FDListSize]; - } - }*/ } void SlirpCbNotify(void* opaque) @@ -203,9 +149,6 @@ bool Init() { IPv4ID = 0; - //FDListSize = 0; - //memset(FDList, 0, sizeof(FDList)); - SlirpConfig cfg; memset(&cfg, 0, sizeof(cfg)); cfg.version = 1; @@ -425,7 +368,7 @@ void HandleDNSFrame(u8* data, int len) if (framelen & 1) { *out++ = 0; framelen++; } FinishUDPFrame(resp, framelen); - RXEnqueue(resp, framelen); + Net::RXEnqueue(resp, framelen); } int SendPacket(u8* data, int len) @@ -434,7 +377,7 @@ int SendPacket(u8* data, int len) if (len > 2048) { - Log(LogLevel::Error, "LAN_SendPacket: error: packet too long (%d)\n", len); + Log(LogLevel::Error, "Net_SendPacket: error: packet too long (%d)\n", len); return 0; } @@ -472,8 +415,6 @@ int SlirpCbAddPoll(int fd, int events, void* opaque) int idx = PollListSize++; - //printf("Slirp: add poll: fd=%d, idx=%d, events=%08X\n", fd, idx, events); - u16 evt = 0; if (events & SLIRP_POLL_IN) evt |= POLLIN; @@ -497,8 +438,6 @@ int SlirpCbGetREvents(int idx, void* opaque) if (idx < 0 || idx >= PollListSize) return 0; - //printf("Slirp: get revents, idx=%d, res=%04X\n", idx, FDList[idx].revents); - u16 evt = PollList[idx].revents; int ret = 0; @@ -511,11 +450,9 @@ int SlirpCbGetREvents(int idx, void* opaque) return ret; } -int RecvPacket(u8* data) +void RecvCheck() { - if (!Ctx) return 0; - - int ret = 0; + if (!Ctx) return; //if (PollListSize > 0) { @@ -525,19 +462,6 @@ int RecvPacket(u8* data) int res = poll(PollList, PollListSize, timeout); slirp_pollfds_poll(Ctx, res<0, SlirpCbGetREvents, nullptr); } - - if (!RXBuffer.IsEmpty()) - { - u32 header = RXBuffer.Read(); - u32 len = header & 0xFFFF; - - for (int i = 0; i < len; i += 4) - ((u32*)data)[i>>2] = RXBuffer.Read(); - - ret = header >> 16; - } - - return ret; } } diff --git a/src/frontend/qt_sdl/LAN_Socket.h b/src/frontend/qt_sdl/Net_Slirp.h similarity index 87% rename from src/frontend/qt_sdl/LAN_Socket.h rename to src/frontend/qt_sdl/Net_Slirp.h index 043e13302f..03b5e3448d 100644 --- a/src/frontend/qt_sdl/LAN_Socket.h +++ b/src/frontend/qt_sdl/Net_Slirp.h @@ -16,24 +16,21 @@ with melonDS. If not, see http://www.gnu.org/licenses/. */ -#ifndef LAN_SOCKET_H -#define LAN_SOCKET_H +#ifndef NET_SLIRP_H +#define NET_SLIRP_H #include "types.h" -namespace LAN_Socket +namespace Net_Slirp { using namespace melonDS; -// - - bool Init(); void DeInit(); int SendPacket(u8* data, int len); -int RecvPacket(u8* data); +void RecvCheck(); } -#endif // LAN_SOCKET_H +#endif // NET_SLIRP_H diff --git a/src/frontend/qt_sdl/PacketDispatcher.cpp b/src/frontend/qt_sdl/PacketDispatcher.cpp new file mode 100644 index 0000000000..28b62d7181 --- /dev/null +++ b/src/frontend/qt_sdl/PacketDispatcher.cpp @@ -0,0 +1,162 @@ +/* + Copyright 2016-2023 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 "PacketDispatcher.h" + +using namespace melonDS; + +struct PacketHeader +{ + u32 magic; + u32 senderID; + u32 headerLength; + u32 dataLength; +}; + +const u32 kPacketMagic = 0x4B504C4D; + + +PacketDispatcher::PacketDispatcher() +{ + instanceMask = 0; + memset(packetQueues, 0, sizeof(packetQueues)); +} + +PacketDispatcher::~PacketDispatcher() +{ + // +} + + +void PacketDispatcher::registerInstance(int inst) +{ + mutex.lock(); + + instanceMask |= (1 << inst); + packetQueues[inst] = new PacketQueue(); + + mutex.unlock(); +} + +void PacketDispatcher::unregisterInstance(int inst) +{ + mutex.lock(); + + instanceMask &= ~(1 << inst); + delete packetQueues[inst]; + + mutex.unlock(); +} + + +void PacketDispatcher::clear() +{ + mutex.lock(); + for (int i = 0; i < 16; i++) + { + if (!(instanceMask & (1 << i))) + continue; + + PacketQueue* queue = packetQueues[i]; + queue->Clear(); + } + mutex.unlock(); +} + + +void PacketDispatcher::sendPacket(const void* header, int headerlen, const void* data, int datalen, int sender, u16 recv_mask) +{ + if (!header) headerlen = 0; + if (!data) datalen = 0; + if ((!headerlen) && (!datalen)) return; + if ((sizeof(PacketHeader) + headerlen + datalen) >= 0x8000) return; + if (sender < 0 || sender > 16) return; + + recv_mask &= instanceMask; + if (sender < 16) recv_mask &= ~(1 << sender); + if (!recv_mask) return; + + PacketHeader phdr; + phdr.magic = kPacketMagic; + phdr.senderID = sender; + phdr.headerLength = headerlen; + phdr.dataLength = datalen; + + int totallen = sizeof(phdr) + headerlen + datalen; + + mutex.lock(); + for (int i = 0; i < 16; i++) + { + if (!(recv_mask & (1 << i))) + continue; + + PacketQueue* queue = packetQueues[i]; + + // if we run out of space: discard old packets + while (!queue->CanFit(totallen)) + { + PacketHeader tmp; + queue->Read(&tmp, sizeof(tmp)); + queue->Skip(tmp.headerLength + tmp.dataLength); + } + + queue->Write(&phdr, sizeof(phdr)); + if (headerlen) queue->Write(header, headerlen); + if (datalen) queue->Write(data, datalen); + } + mutex.unlock(); +} + +bool PacketDispatcher::recvPacket(void *header, int *headerlen, void *data, int *datalen, int receiver) +{ + if ((!header) && (!data)) return false; + if (receiver < 0 || receiver > 15) return false; + + mutex.lock(); + PacketQueue* queue = packetQueues[receiver]; + + PacketHeader phdr; + if (!queue->Read(&phdr, sizeof(phdr))) + { + mutex.unlock(); + return false; + } + + if (phdr.magic != kPacketMagic) + { + mutex.unlock(); + return false; + } + + if (phdr.headerLength) + { + if (headerlen) *headerlen = phdr.headerLength; + if (header) queue->Read(header, phdr.headerLength); + else queue->Skip(phdr.headerLength); + } + + if (phdr.dataLength) + { + if (datalen) *datalen = phdr.dataLength; + if (data) queue->Read(data, phdr.dataLength); + else queue->Skip(phdr.dataLength); + } + + mutex.unlock(); + return true; +} diff --git a/src/frontend/qt_sdl/Input.h b/src/frontend/qt_sdl/PacketDispatcher.h similarity index 53% rename from src/frontend/qt_sdl/Input.h rename to src/frontend/qt_sdl/PacketDispatcher.h index 5a38756b4a..428fc8053f 100644 --- a/src/frontend/qt_sdl/Input.h +++ b/src/frontend/qt_sdl/PacketDispatcher.h @@ -16,40 +16,33 @@ with melonDS. If not, see http://www.gnu.org/licenses/. */ -#ifndef INPUT_H -#define INPUT_H - -#include +#ifndef PACKETDISPATCHER_H +#define PACKETDISPATCHER_H +#include #include "types.h" +#include "FIFO.h" -namespace Input -{ - -using namespace melonDS; -extern int JoystickID; -extern SDL_Joystick* Joystick; - -extern u32 InputMask; +using PacketQueue = melonDS::RingBuffer<0x8000>; -void Init(); - -// set joystickID before calling openJoystick() -void OpenJoystick(); -void CloseJoystick(); - -void KeyPress(QKeyEvent* event); -void KeyRelease(QKeyEvent* event); -void KeyReleaseAll(); +class PacketDispatcher +{ +public: + PacketDispatcher(); + ~PacketDispatcher(); -void Process(); + void registerInstance(int inst); + void unregisterInstance(int inst); -bool HotkeyDown(int id); -bool HotkeyPressed(int id); -bool HotkeyReleased(int id); + void clear(); -bool IsRightModKey(QKeyEvent* event); + void sendPacket(const void* header, int headerlen, const void* data, int datalen, int sender, melonDS::u16 recv_mask); + bool recvPacket(void* header, int* headerlen, void* data, int* datalen, int receiver); -} +private: + QMutex mutex; + melonDS::u16 instanceMask; + PacketQueue* packetQueues[16]; +}; -#endif // INPUT_H +#endif // PACKETDISPATCHER_H diff --git a/src/frontend/qt_sdl/PathSettingsDialog.cpp b/src/frontend/qt_sdl/PathSettingsDialog.cpp index 71342087aa..0e9abb095d 100644 --- a/src/frontend/qt_sdl/PathSettingsDialog.cpp +++ b/src/frontend/qt_sdl/PathSettingsDialog.cpp @@ -24,6 +24,7 @@ #include "types.h" #include "Config.h" #include "Platform.h" +#include "main.h" #include "PathSettingsDialog.h" #include "ui_PathSettingsDialog.h" @@ -33,9 +34,6 @@ namespace Platform = melonDS::Platform; PathSettingsDialog* PathSettingsDialog::currentDlg = nullptr; -extern std::string EmuDirectory; -extern bool RunningSomething; - bool PathSettingsDialog::needsReset = false; constexpr char errordialog[] = "melonDS cannot write to that directory."; @@ -45,15 +43,26 @@ PathSettingsDialog::PathSettingsDialog(QWidget* parent) : QDialog(parent), ui(ne ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose); - ui->txtSaveFilePath->setText(QString::fromStdString(Config::SaveFilePath)); - ui->txtSavestatePath->setText(QString::fromStdString(Config::SavestatePath)); - ui->txtCheatFilePath->setText(QString::fromStdString(Config::CheatFilePath)); + emuInstance = ((MainWindow*)parent)->getEmuInstance(); + + auto& cfg = emuInstance->getGlobalConfig(); + ui->txtSaveFilePath->setText(cfg.GetQString("SaveFilePath")); + ui->txtSavestatePath->setText(cfg.GetQString("SavestatePath")); + ui->txtCheatFilePath->setText(cfg.GetQString("CheatFilePath")); - int inst = Platform::InstanceID(); + int inst = emuInstance->getInstanceID(); if (inst > 0) ui->lblInstanceNum->setText(QString("Configuring paths for instance %1").arg(inst+1)); else ui->lblInstanceNum->hide(); + +#define SET_ORIGVAL(type, val) \ + for (type* w : findChildren(nullptr)) \ + w->setProperty("user_originalValue", w->val()); + + SET_ORIGVAL(QLineEdit, text); + +#undef SET_ORIGVAL } PathSettingsDialog::~PathSettingsDialog() @@ -67,23 +76,35 @@ void PathSettingsDialog::done(int r) if (r == QDialog::Accepted) { - std::string saveFilePath = ui->txtSaveFilePath->text().toStdString(); - std::string savestatePath = ui->txtSavestatePath->text().toStdString(); - std::string cheatFilePath = ui->txtCheatFilePath->text().toStdString(); + bool modified = false; + +#define CHECK_ORIGVAL(type, val) \ + if (!modified) for (type* w : findChildren(nullptr)) \ + { \ + QVariant v = w->val(); \ + if (v != w->property("user_originalValue")) \ + { \ + modified = true; \ + break; \ + }\ + } + + CHECK_ORIGVAL(QLineEdit, text); + +#undef CHECK_ORIGVAL - if ( saveFilePath != Config::SaveFilePath - || savestatePath != Config::SavestatePath - || cheatFilePath != Config::CheatFilePath) + if (modified) { - if (RunningSomething + if (emuInstance->emuIsActive() && QMessageBox::warning(this, "Reset necessary to apply changes", "The emulation will be reset for the changes to take place.", QMessageBox::Ok, QMessageBox::Cancel) != QMessageBox::Ok) return; - Config::SaveFilePath = saveFilePath; - Config::SavestatePath = savestatePath; - Config::CheatFilePath = cheatFilePath; + auto& cfg = emuInstance->getGlobalConfig(); + cfg.SetQString("SaveFilePath", ui->txtSaveFilePath->text()); + cfg.SetQString("SavestatePath", ui->txtSavestatePath->text()); + cfg.SetQString("CheatFilePath", ui->txtCheatFilePath->text()); Config::Save(); @@ -100,7 +121,7 @@ void PathSettingsDialog::on_btnSaveFileBrowse_clicked() { QString dir = QFileDialog::getExistingDirectory(this, "Select save files path...", - QString::fromStdString(EmuDirectory)); + emuDirectory); if (dir.isEmpty()) return; @@ -117,7 +138,7 @@ void PathSettingsDialog::on_btnSavestateBrowse_clicked() { QString dir = QFileDialog::getExistingDirectory(this, "Select savestates path...", - QString::fromStdString(EmuDirectory)); + emuDirectory); if (dir.isEmpty()) return; @@ -134,7 +155,7 @@ void PathSettingsDialog::on_btnCheatFileBrowse_clicked() { QString dir = QFileDialog::getExistingDirectory(this, "Select cheat files path...", - QString::fromStdString(EmuDirectory)); + emuDirectory); if (dir.isEmpty()) return; diff --git a/src/frontend/qt_sdl/PathSettingsDialog.h b/src/frontend/qt_sdl/PathSettingsDialog.h index dd64d46cf5..4a1187b6dd 100644 --- a/src/frontend/qt_sdl/PathSettingsDialog.h +++ b/src/frontend/qt_sdl/PathSettingsDialog.h @@ -25,6 +25,8 @@ namespace Ui { class PathSettingsDialog; } class PathSettingsDialog; +class EmuInstance; + class PathSettingsDialog : public QDialog { Q_OBJECT @@ -62,6 +64,7 @@ private slots: private: Ui::PathSettingsDialog* ui; + EmuInstance* emuInstance; }; #endif // PATHSETTINGSDIALOG_H diff --git a/src/frontend/qt_sdl/Platform.cpp b/src/frontend/qt_sdl/Platform.cpp index b3230a4129..5450a4f0e3 100644 --- a/src/frontend/qt_sdl/Platform.cpp +++ b/src/frontend/qt_sdl/Platform.cpp @@ -29,17 +29,15 @@ #include #include #include -#include #include #include #include #include "Platform.h" #include "Config.h" -#include "ROMManager.h" +#include "main.h" #include "CameraManager.h" -#include "LAN_Socket.h" -#include "LAN_PCap.h" +#include "Net.h" #include "LocalMP.h" #include "SPI_Firmware.h" @@ -48,173 +46,18 @@ #define ftell _ftelli64 #endif // __WIN32__ -std::string EmuDirectory; - extern CameraManager* camManager[2]; -void emuStop(); - -// TEMP -//#include "main.h" -//extern MainWindow* mainWindow; - namespace melonDS::Platform { -void PathInit(int argc, char** argv) +void SignalStop(StopReason reason, void* userdata) { - // First, check for the portable directory next to the executable. - QString appdirpath = QCoreApplication::applicationDirPath(); - QString portablepath = appdirpath + QDir::separator() + "portable"; - -#if defined(__APPLE__) - // On Apple platforms we may need to navigate outside an app bundle. - // The executable directory would be "melonDS.app/Contents/MacOS", so we need to go a total of three steps up. - QDir bundledir(appdirpath); - if (bundledir.cd("..") && bundledir.cd("..") && bundledir.dirName().endsWith(".app") && bundledir.cd("..")) - { - portablepath = bundledir.absolutePath() + QDir::separator() + "portable"; - } -#endif - - QDir portabledir(portablepath); - if (portabledir.exists()) - { - EmuDirectory = portabledir.absolutePath().toStdString(); - } - else - { - // If no overrides are specified, use the default path. -#if defined(__WIN32__) && defined(WIN32_PORTABLE) - EmuDirectory = appdirpath.toStdString(); -#else - QString confdir; - QDir config(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation)); - config.mkdir("melonDS"); - confdir = config.absolutePath() + QDir::separator() + "melonDS"; - EmuDirectory = confdir.toStdString(); -#endif - } + EmuInstance* inst = (EmuInstance*)userdata; + inst->emuStop(reason); } -QSharedMemory* IPCBuffer = nullptr; -int IPCInstanceID; - -void IPCInit() -{ - IPCInstanceID = 0; - - IPCBuffer = new QSharedMemory("melonIPC"); - -#if !defined(Q_OS_WINDOWS) - // QSharedMemory instances can be left over from crashed processes on UNIX platforms. - // To prevent melonDS thinking there's another instance, we attach and then immediately detach from the - // shared memory. If no other process was actually using it, it'll be destroyed and we'll have a clean - // shared memory buffer after creating it again below. - if (IPCBuffer->attach()) - { - IPCBuffer->detach(); - delete IPCBuffer; - IPCBuffer = new QSharedMemory("melonIPC"); - } -#endif - - if (!IPCBuffer->attach()) - { - Log(LogLevel::Info, "IPC sharedmem doesn't exist. creating\n"); - if (!IPCBuffer->create(1024)) - { - Log(LogLevel::Error, "IPC sharedmem create failed: %s\n", IPCBuffer->errorString().toStdString().c_str()); - 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<unlock(); - - Log(LogLevel::Info, "IPC: instance ID %d\n", IPCInstanceID); -} - -void IPCDeInit() -{ - if (IPCBuffer) - { - IPCBuffer->lock(); - u8* data = (u8*)IPCBuffer->data(); - *(u16*)&data[0] &= ~(1<unlock(); - - IPCBuffer->detach(); - delete IPCBuffer; - } - IPCBuffer = nullptr; -} - - -void Init(int argc, char** argv) -{ - PathInit(argc, argv); - IPCInit(); -} - -void DeInit() -{ - IPCDeInit(); -} - -void SignalStop(StopReason reason) -{ - emuStop(); - switch (reason) - { - case StopReason::GBAModeNotSupported: - Log(LogLevel::Error, "!! GBA MODE NOT SUPPORTED\n"); - //mainWindow->osdAddMessage(0xFFA0A0, "GBA mode not supported."); - break; - case StopReason::BadExceptionRegion: - //mainWindow->osdAddMessage(0xFFA0A0, "Internal error."); - break; - case StopReason::PowerOff: - case StopReason::External: - //mainWindow->osdAddMessage(0xFFC040, "Shutdown"); - default: - break; - } -} - - -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; -} static QIODevice::OpenMode GetQMode(FileMode mode) { @@ -308,9 +151,9 @@ FileHandle* OpenFile(const std::string& path, FileMode mode) } } -FileHandle* OpenLocalFile(const std::string& path, FileMode mode) +std::string GetLocalFilePath(const std::string& filename) { - QString qpath = QString::fromStdString(path); + QString qpath = QString::fromStdString(filename); QDir dir(qpath); QString fullpath; @@ -321,10 +164,15 @@ FileHandle* OpenLocalFile(const std::string& path, FileMode mode) } else { - fullpath = QString::fromStdString(EmuDirectory) + QDir::separator() + qpath; + fullpath = emuDirectory + QDir::separator() + qpath; } - return OpenFile(fullpath.toStdString(), mode); + return fullpath.toStdString(); +} + +FileHandle* OpenLocalFile(const std::string& path, FileMode mode) +{ + return OpenFile(GetLocalFilePath(path), mode); } bool CloseFile(FileHandle* file) @@ -539,27 +387,30 @@ void Sleep(u64 usecs) } -void WriteNDSSave(const u8* savedata, u32 savelen, u32 writeoffset, u32 writelen) +void WriteNDSSave(const u8* savedata, u32 savelen, u32 writeoffset, u32 writelen, void* userdata) { - if (ROMManager::NDSSave) - ROMManager::NDSSave->RequestFlush(savedata, savelen, writeoffset, writelen); + EmuInstance* inst = (EmuInstance*)userdata; + if (inst->ndsSave) + inst->ndsSave->RequestFlush(savedata, savelen, writeoffset, writelen); } -void WriteGBASave(const u8* savedata, u32 savelen, u32 writeoffset, u32 writelen) +void WriteGBASave(const u8* savedata, u32 savelen, u32 writeoffset, u32 writelen, void* userdata) { - if (ROMManager::GBASave) - ROMManager::GBASave->RequestFlush(savedata, savelen, writeoffset, writelen); + EmuInstance* inst = (EmuInstance*)userdata; + if (inst->gbaSave) + inst->gbaSave->RequestFlush(savedata, savelen, writeoffset, writelen); } -void WriteFirmware(const Firmware& firmware, u32 writeoffset, u32 writelen) +void WriteFirmware(const Firmware& firmware, u32 writeoffset, u32 writelen, void* userdata) { - if (!ROMManager::FirmwareSave) + EmuInstance* inst = (EmuInstance*)userdata; + if (!inst->firmwareSave) return; if (firmware.GetHeader().Identifier != GENERATED_FIRMWARE_IDENTIFIER) { // If this is not the default built-in firmware... // ...then write the whole thing back. - ROMManager::FirmwareSave->RequestFlush(firmware.Buffer(), firmware.Length(), writeoffset, writelen); + inst->firmwareSave->RequestFlush(firmware.Buffer(), firmware.Length(), writeoffset, writelen); } else { @@ -576,131 +427,104 @@ void WriteFirmware(const Firmware& firmware, u32 writeoffset, u32 writelen) { // If we're writing to the access points... const u8* buffer = firmware.GetExtendedAccessPointPosition(); u32 length = sizeof(firmware.GetExtendedAccessPoints()) + sizeof(firmware.GetAccessPoints()); - ROMManager::FirmwareSave->RequestFlush(buffer, length, writeoffset - eapstart, writelen); + inst->firmwareSave->RequestFlush(buffer, length, writeoffset - eapstart, writelen); } } } -void WriteDateTime(int year, int month, int day, int hour, int minute, int second) +void WriteDateTime(int year, int month, int day, int hour, int minute, int second, void* userdata) { + EmuInstance* inst = (EmuInstance*)userdata; QDateTime hosttime = QDateTime::currentDateTime(); QDateTime time = QDateTime(QDate(year, month, day), QTime(hour, minute, second)); + auto& cfg = inst->getLocalConfig(); - Config::RTCOffset = hosttime.secsTo(time); + cfg.SetInt64("RTC.Offset", hosttime.secsTo(time)); Config::Save(); } -bool MP_Init() -{ - return LocalMP::Init(); -} - -void MP_DeInit() -{ - return LocalMP::DeInit(); -} - -void MP_Begin() -{ - return LocalMP::Begin(); -} -void MP_End() +void MP_Begin(void* userdata) { - return LocalMP::End(); + int inst = ((EmuInstance*)userdata)->getInstanceID(); + LocalMP::Begin(inst); } -int MP_SendPacket(u8* data, int len, u64 timestamp) +void MP_End(void* userdata) { - return LocalMP::SendPacket(data, len, timestamp); + int inst = ((EmuInstance*)userdata)->getInstanceID(); + LocalMP::End(inst); } -int MP_RecvPacket(u8* data, u64* timestamp) +int MP_SendPacket(u8* data, int len, u64 timestamp, void* userdata) { - return LocalMP::RecvPacket(data, timestamp); + int inst = ((EmuInstance*)userdata)->getInstanceID(); + return LocalMP::SendPacket(inst, data, len, timestamp); } -int MP_SendCmd(u8* data, int len, u64 timestamp) +int MP_RecvPacket(u8* data, u64* timestamp, void* userdata) { - return LocalMP::SendCmd(data, len, timestamp); + int inst = ((EmuInstance*)userdata)->getInstanceID(); + return LocalMP::RecvPacket(inst, data, timestamp); } -int MP_SendReply(u8* data, int len, u64 timestamp, u16 aid) +int MP_SendCmd(u8* data, int len, u64 timestamp, void* userdata) { - return LocalMP::SendReply(data, len, timestamp, aid); + int inst = ((EmuInstance*)userdata)->getInstanceID(); + return LocalMP::SendCmd(inst, data, len, timestamp); } -int MP_SendAck(u8* data, int len, u64 timestamp) +int MP_SendReply(u8* data, int len, u64 timestamp, u16 aid, void* userdata) { - return LocalMP::SendAck(data, len, timestamp); + int inst = ((EmuInstance*)userdata)->getInstanceID(); + return LocalMP::SendReply(inst, data, len, timestamp, aid); } -int MP_RecvHostPacket(u8* data, u64* timestamp) +int MP_SendAck(u8* data, int len, u64 timestamp, void* userdata) { - return LocalMP::RecvHostPacket(data, timestamp); + int inst = ((EmuInstance*)userdata)->getInstanceID(); + return LocalMP::SendAck(inst, data, len, timestamp); } -u16 MP_RecvReplies(u8* data, u64 timestamp, u16 aidmask) +int MP_RecvHostPacket(u8* data, u64* timestamp, void* userdata) { - return LocalMP::RecvReplies(data, timestamp, aidmask); + int inst = ((EmuInstance*)userdata)->getInstanceID(); + return LocalMP::RecvHostPacket(inst, data, timestamp); } -bool LAN_Init() +u16 MP_RecvReplies(u8* data, u64 timestamp, u16 aidmask, void* userdata) { - if (Config::DirectLAN) - { - if (!LAN_PCap::Init(true)) - return false; - } - else - { - if (!LAN_Socket::Init()) - return false; - } - - return true; + int inst = ((EmuInstance*)userdata)->getInstanceID(); + return LocalMP::RecvReplies(inst, data, timestamp, aidmask); } -void LAN_DeInit() -{ - // checkme. blarg - //if (Config::DirectLAN) - // LAN_PCap::DeInit(); - //else - // LAN_Socket::DeInit(); - LAN_PCap::DeInit(); - LAN_Socket::DeInit(); -} -int LAN_SendPacket(u8* data, int len) +int Net_SendPacket(u8* data, int len, void* userdata) { - if (Config::DirectLAN) - return LAN_PCap::SendPacket(data, len); - else - return LAN_Socket::SendPacket(data, len); + int inst = ((EmuInstance*)userdata)->getInstanceID(); + Net::SendPacket(data, len, inst); + return 0; } -int LAN_RecvPacket(u8* data) +int Net_RecvPacket(u8* data, void* userdata) { - if (Config::DirectLAN) - return LAN_PCap::RecvPacket(data); - else - return LAN_Socket::RecvPacket(data); + int inst = ((EmuInstance*)userdata)->getInstanceID(); + return Net::RecvPacket(data, inst); } -void Camera_Start(int num) +void Camera_Start(int num, void* userdata) { return camManager[num]->start(); } -void Camera_Stop(int num) +void Camera_Stop(int num, void* userdata) { return camManager[num]->stop(); } -void Camera_CaptureFrame(int num, u32* frame, int width, int height, bool yuv) +void Camera_CaptureFrame(int num, u32* frame, int width, int height, bool yuv, void* userdata) { return camManager[num]->captureFrame(frame, width, height, yuv); } diff --git a/src/frontend/qt_sdl/PowerManagement/PowerManagementDialog.cpp b/src/frontend/qt_sdl/PowerManagement/PowerManagementDialog.cpp index 3d47c45a47..e28bd9ec26 100644 --- a/src/frontend/qt_sdl/PowerManagement/PowerManagementDialog.cpp +++ b/src/frontend/qt_sdl/PowerManagement/PowerManagementDialog.cpp @@ -30,43 +30,47 @@ #include #include "main.h" +#include "EmuInstance.h" using namespace melonDS; PowerManagementDialog* PowerManagementDialog::currentDlg = nullptr; -PowerManagementDialog::PowerManagementDialog(QWidget* parent, EmuThread* emuThread) : QDialog(parent), emuThread(emuThread), ui(new Ui::PowerManagementDialog) +PowerManagementDialog::PowerManagementDialog(QWidget* parent) : QDialog(parent), ui(new Ui::PowerManagementDialog) { inited = false; ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose); - if (emuThread->NDS->ConsoleType == 1) + emuInstance = ((MainWindow*)parent)->getEmuInstance(); + auto nds = emuInstance->getNDS(); + + if (nds->ConsoleType == 1) { ui->grpDSBattery->setEnabled(false); - auto& dsi = static_cast(*emuThread->NDS); - oldDSiBatteryLevel = dsi.I2C.GetBPTWL()->GetBatteryLevel(); - oldDSiBatteryCharging = dsi.I2C.GetBPTWL()->GetBatteryCharging(); + auto dsi = static_cast(nds); + oldDSiBatteryLevel = dsi->I2C.GetBPTWL()->GetBatteryLevel(); + oldDSiBatteryCharging = dsi->I2C.GetBPTWL()->GetBatteryCharging(); } else { ui->grpDSiBattery->setEnabled(false); - oldDSBatteryLevel = emuThread->NDS->SPI.GetPowerMan()->GetBatteryLevelOkay(); + oldDSBatteryLevel = nds->SPI.GetPowerMan()->GetBatteryLevelOkay(); } updateDSBatteryLevelControls(); - bool defaultDSiBatteryCharging = (emuThread->NDS->ConsoleType == 1) ? Config::DSiBatteryCharging : false; + //bool defaultDSiBatteryCharging = (nds->ConsoleType == 1) ? Config::DSiBatteryCharging : false; - if (emuThread->NDS->ConsoleType == 1) + if (nds->ConsoleType == 1) { - auto& dsi = static_cast(*emuThread->NDS); - ui->cbDSiBatteryCharging->setChecked(dsi.I2C.GetBPTWL()->GetBatteryCharging()); + auto dsi = static_cast(nds); + ui->cbDSiBatteryCharging->setChecked(dsi->I2C.GetBPTWL()->GetBatteryCharging()); int dsiBatterySliderPos = 4; - switch (dsi.I2C.GetBPTWL()->GetBatteryLevel()) + switch (dsi->I2C.GetBPTWL()->GetBatteryLevel()) { case DSi_BPTWL::batteryLevel_AlmostEmpty: dsiBatterySliderPos = 0; break; case DSi_BPTWL::batteryLevel_Low: dsiBatterySliderPos = 1; break; @@ -78,12 +82,14 @@ PowerManagementDialog::PowerManagementDialog(QWidget* parent, EmuThread* emuThre } else { - ui->cbDSiBatteryCharging->setChecked(Config::DSiBatteryCharging); - ui->sliderDSiBatteryLevel->setValue(Config::DSiBatteryLevel); + auto& cfg = emuInstance->getLocalConfig(); + + ui->cbDSiBatteryCharging->setChecked(cfg.GetBool("DSi.Battery.Charging")); + ui->sliderDSiBatteryLevel->setValue(cfg.GetInt("DSi.Battery.Level")); } - int inst = Platform::InstanceID(); + int inst = emuInstance->getInstanceID(); if (inst > 0) ui->lblInstanceNum->setText(QString("Setting battery levels for instance %1").arg(inst+1)); else @@ -99,30 +105,34 @@ PowerManagementDialog::~PowerManagementDialog() void PowerManagementDialog::done(int r) { + auto nds = emuInstance->getNDS(); + if (r == QDialog::Accepted) { - if (emuThread->NDS->ConsoleType == 1) + auto& cfg = emuInstance->getLocalConfig(); + + if (nds->ConsoleType == 1) { - auto& dsi = static_cast(*emuThread->NDS); - Config::DSiBatteryLevel = dsi.I2C.GetBPTWL()->GetBatteryLevel(); - Config::DSiBatteryCharging = dsi.I2C.GetBPTWL()->GetBatteryCharging(); + auto dsi = static_cast(nds); + cfg.SetInt("DSi.Battery.Level", dsi->I2C.GetBPTWL()->GetBatteryLevel()); + cfg.SetBool("DSi.Battery.Charging", dsi->I2C.GetBPTWL()->GetBatteryCharging()); } else { - Config::DSBatteryLevelOkay = emuThread->NDS->SPI.GetPowerMan()->GetBatteryLevelOkay(); + cfg.SetBool("DS.Battery.LevelOkay", nds->SPI.GetPowerMan()->GetBatteryLevelOkay()); } } else { - if (emuThread->NDS->ConsoleType == 1) + if (nds->ConsoleType == 1) { - auto& dsi = static_cast(*emuThread->NDS); - dsi.I2C.GetBPTWL()->SetBatteryLevel(oldDSiBatteryLevel); - dsi.I2C.GetBPTWL()->SetBatteryCharging(oldDSiBatteryCharging); + auto dsi = static_cast(nds); + dsi->I2C.GetBPTWL()->SetBatteryLevel(oldDSiBatteryLevel); + dsi->I2C.GetBPTWL()->SetBatteryCharging(oldDSiBatteryCharging); } else { - emuThread->NDS->SPI.GetPowerMan()->SetBatteryLevelOkay(oldDSBatteryLevel); + nds->SPI.GetPowerMan()->SetBatteryLevelOkay(oldDSBatteryLevel); } } @@ -133,17 +143,17 @@ void PowerManagementDialog::done(int r) void PowerManagementDialog::on_rbDSBatteryLow_clicked() { - emuThread->NDS->SPI.GetPowerMan()->SetBatteryLevelOkay(false); + emuInstance->getNDS()->SPI.GetPowerMan()->SetBatteryLevelOkay(false); } void PowerManagementDialog::on_rbDSBatteryOkay_clicked() { - emuThread->NDS->SPI.GetPowerMan()->SetBatteryLevelOkay(true); + emuInstance->getNDS()->SPI.GetPowerMan()->SetBatteryLevelOkay(true); } void PowerManagementDialog::updateDSBatteryLevelControls() { - if (emuThread->NDS->SPI.GetPowerMan()->GetBatteryLevelOkay()) + if (emuInstance->getNDS()->SPI.GetPowerMan()->GetBatteryLevelOkay()) ui->rbDSBatteryOkay->setChecked(true); else ui->rbDSBatteryLow->setChecked(true); @@ -151,10 +161,12 @@ void PowerManagementDialog::updateDSBatteryLevelControls() void PowerManagementDialog::on_cbDSiBatteryCharging_toggled() { - if (emuThread->NDS->ConsoleType == 1) + auto nds = emuInstance->getNDS(); + + if (nds->ConsoleType == 1) { - auto& dsi = static_cast(*emuThread->NDS); - dsi.I2C.GetBPTWL()->SetBatteryCharging(ui->cbDSiBatteryCharging->isChecked()); + auto dsi = static_cast(nds); + dsi->I2C.GetBPTWL()->SetBatteryCharging(ui->cbDSiBatteryCharging->isChecked()); } } @@ -162,9 +174,11 @@ void PowerManagementDialog::on_sliderDSiBatteryLevel_valueChanged(int value) { if (!inited) return; - if (emuThread->NDS->ConsoleType == 1) + auto nds = emuInstance->getNDS(); + + if (nds->ConsoleType == 1) { - auto& dsi = static_cast(*emuThread->NDS); + auto dsi = static_cast(nds); u8 newBatteryLevel = DSi_BPTWL::batteryLevel_Full; switch (value) { @@ -174,7 +188,7 @@ void PowerManagementDialog::on_sliderDSiBatteryLevel_valueChanged(int value) case 3: newBatteryLevel = DSi_BPTWL::batteryLevel_ThreeQuarters; break; case 4: newBatteryLevel = DSi_BPTWL::batteryLevel_Full; break; } - dsi.I2C.GetBPTWL()->SetBatteryLevel(newBatteryLevel); + dsi->I2C.GetBPTWL()->SetBatteryLevel(newBatteryLevel); } updateDSBatteryLevelControls(); diff --git a/src/frontend/qt_sdl/PowerManagement/PowerManagementDialog.h b/src/frontend/qt_sdl/PowerManagement/PowerManagementDialog.h index bc2abc3d0d..3bbb76085c 100644 --- a/src/frontend/qt_sdl/PowerManagement/PowerManagementDialog.h +++ b/src/frontend/qt_sdl/PowerManagement/PowerManagementDialog.h @@ -25,19 +25,19 @@ #include "types.h" namespace Ui { class PowerManagementDialog; } -class EmuThread; class PowerManagementDialog; +class EmuInstance; class PowerManagementDialog : public QDialog { Q_OBJECT public: - explicit PowerManagementDialog(QWidget* parent, EmuThread* emu_thread); + explicit PowerManagementDialog(QWidget* parent); ~PowerManagementDialog(); static PowerManagementDialog* currentDlg; - static PowerManagementDialog* openDlg(QWidget* parent, EmuThread* emu_thread) + static PowerManagementDialog* openDlg(QWidget* parent) { if (currentDlg) { @@ -45,7 +45,7 @@ class PowerManagementDialog : public QDialog return currentDlg; } - currentDlg = new PowerManagementDialog(parent, emu_thread); + currentDlg = new PowerManagementDialog(parent); currentDlg->open(); return currentDlg; } @@ -65,7 +65,7 @@ private slots: private: Ui::PowerManagementDialog* ui; - EmuThread* emuThread; + EmuInstance* emuInstance; bool inited; bool oldDSBatteryLevel; diff --git a/src/frontend/qt_sdl/RAMInfoDialog.cpp b/src/frontend/qt_sdl/RAMInfoDialog.cpp index 5bff99adf0..20057adbc1 100644 --- a/src/frontend/qt_sdl/RAMInfoDialog.cpp +++ b/src/frontend/qt_sdl/RAMInfoDialog.cpp @@ -22,7 +22,6 @@ #include "main.h" using namespace melonDS; -extern EmuThread* emuThread; s32 GetMainRAMValue(NDS& nds, const u32& addr, const ramInfo_ByteType& byteType) { @@ -41,11 +40,13 @@ s32 GetMainRAMValue(NDS& nds, const u32& addr, const ramInfo_ByteType& byteType) RAMInfoDialog* RAMInfoDialog::currentDlg = nullptr; -RAMInfoDialog::RAMInfoDialog(QWidget* parent, EmuThread* emuThread) : QDialog(parent), emuThread(emuThread), ui(new Ui::RAMInfoDialog) +RAMInfoDialog::RAMInfoDialog(QWidget* parent) : QDialog(parent), ui(new Ui::RAMInfoDialog) { ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose); + emuInstance = ((MainWindow*)parent)->getEmuInstance(); + qRegisterMetaType>("QVector"); qRegisterMetaType("u32"); qRegisterMetaType("s32"); @@ -91,7 +92,7 @@ void RAMInfoDialog::ShowRowsInTable() for (u32 row = scrollValue; row < std::min(scrollValue+25, RowDataVector->size()); row++) { ramInfo_RowData& rowData = RowDataVector->at(row); - rowData.Update(*emuThread->NDS, SearchThread->GetSearchByteType()); + rowData.Update(*emuInstance->getNDS(), SearchThread->GetSearchByteType()); if (ui->ramTable->item(row, ramInfo_Address) == nullptr) { @@ -186,7 +187,7 @@ void RAMInfoDialog::on_ramTable_itemChanged(QTableWidgetItem *item) s32 itemValue = item->text().toInt(); if (rowData.Value != itemValue) - rowData.SetValue(*emuThread->NDS, itemValue); + rowData.SetValue(*emuInstance->getNDS(), itemValue); } /** @@ -235,7 +236,7 @@ void RAMSearchThread::run() u32 progress = 0; // Pause game running - emuThread->emuPause(); + Dialog->emuInstance->getEmuThread()->emuPause(); // For following search modes below, RowDataVector must be filled. if (SearchMode == ramInfoSTh_SearchAll || RowDataVector->size() == 0) @@ -243,7 +244,7 @@ void RAMSearchThread::run() // First search mode for (u32 addr = 0x02000000; SearchRunning && addr < 0x02000000+MainRAMMaxSize; addr += SearchByteType) { - const s32& value = GetMainRAMValue(*emuThread->NDS, addr, SearchByteType); + const s32& value = GetMainRAMValue(*Dialog->emuInstance->getNDS(), addr, SearchByteType); RowDataVector->push_back({ addr, value, value }); @@ -264,7 +265,7 @@ void RAMSearchThread::run() for (u32 row = 0; SearchRunning && row < RowDataVector->size(); row++) { const u32& addr = RowDataVector->at(row).Address; - const s32& value = GetMainRAMValue(*emuThread->NDS, addr, SearchByteType); + const s32& value = GetMainRAMValue(*Dialog->emuInstance->getNDS(), addr, SearchByteType); if (SearchValue == value) newRowDataVector->push_back({ addr, value, value }); @@ -282,7 +283,7 @@ void RAMSearchThread::run() } // Unpause game running - emuThread->emuUnpause(); + Dialog->emuInstance->getEmuThread()->emuUnpause(); SearchRunning = false; } diff --git a/src/frontend/qt_sdl/RAMInfoDialog.h b/src/frontend/qt_sdl/RAMInfoDialog.h index 2a5b16204e..4368ade113 100644 --- a/src/frontend/qt_sdl/RAMInfoDialog.h +++ b/src/frontend/qt_sdl/RAMInfoDialog.h @@ -32,7 +32,7 @@ namespace Ui { class RAMInfoDialog; } class RAMInfoDialog; class RAMSearchThread; class RAMUpdateThread; -class EmuThread; +class EmuInstance; enum ramInfo_ByteType { @@ -79,11 +79,11 @@ class RAMInfoDialog : public QDialog Q_OBJECT public: - explicit RAMInfoDialog(QWidget* parent, EmuThread* emuThread); + explicit RAMInfoDialog(QWidget* parent); ~RAMInfoDialog(); static RAMInfoDialog* currentDlg; - static RAMInfoDialog* openDlg(QWidget* parent, EmuThread* emuThread) + static RAMInfoDialog* openDlg(QWidget* parent) { if (currentDlg) { @@ -91,7 +91,7 @@ class RAMInfoDialog : public QDialog return currentDlg; } - currentDlg = new RAMInfoDialog(parent, emuThread); + currentDlg = new RAMInfoDialog(parent); currentDlg->show(); return currentDlg; } @@ -119,11 +119,13 @@ private slots: void SetProgressbarValue(const melonDS::u32& value); private: - EmuThread* emuThread; Ui::RAMInfoDialog* ui; + EmuInstance* emuInstance; RAMSearchThread* SearchThread; QTimer* TableUpdater; + + friend class RAMSearchThread; }; class RAMSearchThread : public QThread diff --git a/src/frontend/qt_sdl/ROMInfoDialog.cpp b/src/frontend/qt_sdl/ROMInfoDialog.cpp index 0f10b2f589..acc2e76809 100644 --- a/src/frontend/qt_sdl/ROMInfoDialog.cpp +++ b/src/frontend/qt_sdl/ROMInfoDialog.cpp @@ -27,6 +27,7 @@ #include "NDSCart.h" #include "Platform.h" #include "Config.h" +#include "main.h" using namespace melonDS; @@ -42,15 +43,18 @@ QString QStringBytes(u64 num) ROMInfoDialog* ROMInfoDialog::currentDlg = nullptr; -ROMInfoDialog::ROMInfoDialog(QWidget* parent, const melonDS::NDSCart::CartCommon& rom) : QDialog(parent), ui(new Ui::ROMInfoDialog) +ROMInfoDialog::ROMInfoDialog(QWidget* parent) : QDialog(parent), ui(new Ui::ROMInfoDialog) { ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose); - const NDSBanner* banner = rom.Banner(); - const NDSHeader& header = rom.GetHeader(); + emuInstance = ((MainWindow*)parent)->getEmuInstance(); + + auto rom = emuInstance->getNDS()->NDSCartSlot.GetCart(); + const NDSBanner* banner = rom->Banner(); + const NDSHeader& header = rom->GetHeader(); u32 iconData[32 * 32]; - ROMManager::ROMIcon(banner->Icon, banner->Palette, iconData); + emuInstance->romIcon(banner->Icon, banner->Palette, iconData); iconImage = QImage(reinterpret_cast(iconData), 32, 32, QImage::Format_RGBA8888).copy(); ui->iconImage->setPixmap(QPixmap::fromImage(iconImage)); @@ -58,7 +62,7 @@ ROMInfoDialog::ROMInfoDialog(QWidget* parent, const melonDS::NDSCart::CartCommon { ui->saveAnimatedIconButton->setEnabled(true); - ROMManager::AnimatedROMIcon(banner->DSiIcon, banner->DSiPalette, banner->DSiSequence, animatedIconData, animatedSequence); + emuInstance->animatedROMIcon(banner->DSiIcon, banner->DSiPalette, banner->DSiSequence, animatedIconData, animatedSequence); for (u32* image: animatedIconData) { @@ -135,7 +139,7 @@ void ROMInfoDialog::on_saveIconButton_clicked() { QString filename = QFileDialog::getSaveFileName(this, "Save Icon", - QString::fromStdString(Config::LastROMFolder), + emuInstance->getGlobalConfig().GetQString("LastROMFolder"), "PNG Images (*.png)"); if (filename.isEmpty()) return; @@ -147,7 +151,7 @@ void ROMInfoDialog::on_saveAnimatedIconButton_clicked() { QString filename = QFileDialog::getSaveFileName(this, "Save Animated Icon", - QString::fromStdString(Config::LastROMFolder), + emuInstance->getGlobalConfig().GetQString("LastROMFolder"), "GIF Images (*.gif)"); if (filename.isEmpty()) return; diff --git a/src/frontend/qt_sdl/ROMInfoDialog.h b/src/frontend/qt_sdl/ROMInfoDialog.h index f7e3b5f5d8..482cd4f827 100644 --- a/src/frontend/qt_sdl/ROMInfoDialog.h +++ b/src/frontend/qt_sdl/ROMInfoDialog.h @@ -25,21 +25,22 @@ #include #include "types.h" -#include "ROMManager.h" namespace Ui { class ROMInfoDialog; } class ROMInfoDialog; +class EmuInstance; namespace melonDS::NDSCart { class CartCommon; } + class ROMInfoDialog : public QDialog { Q_OBJECT public: - explicit ROMInfoDialog(QWidget* parent, const melonDS::NDSCart::CartCommon& rom); + explicit ROMInfoDialog(QWidget* parent); ~ROMInfoDialog(); static ROMInfoDialog* currentDlg; - static ROMInfoDialog* openDlg(QWidget* parent, const melonDS::NDSCart::CartCommon& rom) + static ROMInfoDialog* openDlg(QWidget* parent) { if (currentDlg) { @@ -47,7 +48,7 @@ class ROMInfoDialog : public QDialog return currentDlg; } - currentDlg = new ROMInfoDialog(parent, rom); + currentDlg = new ROMInfoDialog(parent); currentDlg->open(); return currentDlg; } @@ -66,6 +67,7 @@ private slots: private: Ui::ROMInfoDialog* ui; + EmuInstance* emuInstance; QImage iconImage; QTimeLine* iconTimeline; diff --git a/src/frontend/qt_sdl/ROMManager.h b/src/frontend/qt_sdl/ROMManager.h deleted file mode 100644 index ae854617f6..0000000000 --- a/src/frontend/qt_sdl/ROMManager.h +++ /dev/null @@ -1,104 +0,0 @@ -/* - Copyright 2016-2023 melonDS team - - This file is part of melonDS. - - melonDS is free software: you can redistribute it and/or modify it under - the terms of the GNU General Public License as published by the Free - Software Foundation, either version 3 of the License, or (at your option) - any later version. - - melonDS is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with melonDS. If not, see http://www.gnu.org/licenses/. -*/ - -#ifndef ROMMANAGER_H -#define ROMMANAGER_H - -#include "types.h" -#include "SaveManager.h" -#include "AREngine.h" -#include "DSi_NAND.h" -#include - -#include "MemConstants.h" - -#include -#include -#include -#include -#include - -namespace melonDS -{ -class NDS; -class DSi; -class FATStorage; -class FATStorageArgs; -} -class EmuThread; -namespace ROMManager -{ - -using namespace melonDS; -extern std::unique_ptr NDSSave; -extern std::unique_ptr GBASave; -extern std::unique_ptr FirmwareSave; - -QString VerifySetup(); -void Reset(EmuThread* thread); - -/// Boots the emulated console into its system menu without starting a game. -bool BootToMenu(EmuThread* thread); -void ClearBackupState(); - -/// Returns the configured ARM9 BIOS loaded from disk, -/// the FreeBIOS if external BIOS is disabled and we're in NDS mode, -/// or nullptr if loading failed. -std::unique_ptr LoadARM9BIOS() noexcept; -std::unique_ptr LoadARM7BIOS() noexcept; -std::unique_ptr LoadDSiARM9BIOS() noexcept; -std::unique_ptr LoadDSiARM7BIOS() noexcept; -std::optional GetDSiSDCardArgs() noexcept; -std::optional LoadDSiSDCard() noexcept; -std::optional GetDLDISDCardArgs() noexcept; -std::optional LoadDLDISDCard() noexcept; -void CustomizeFirmware(Firmware& firmware, bool overridesettings) noexcept; -Firmware GenerateFirmware(int type) noexcept; -/// Loads and customizes a firmware image based on the values in Config -std::optional LoadFirmware(int type) noexcept; -/// Loads and customizes a NAND image based on the values in Config -std::optional LoadNAND(const std::array& arm7ibios) noexcept; - -/// Inserts a ROM into the emulated console. -bool LoadROM(QMainWindow* mainWindow, EmuThread*, QStringList filepath, bool reset); -void EjectCart(NDS& nds); -bool CartInserted(); -QString CartLabel(); - -bool LoadGBAROM(QMainWindow* mainWindow, NDS& nds, QStringList filepath); -void LoadGBAAddon(NDS& nds, int type); -void EjectGBACart(NDS& nds); -bool GBACartInserted(); -QString GBACartLabel(); - -std::string GetSavestateName(int slot); -bool SavestateExists(int slot); -bool LoadState(NDS& nds, const std::string& filename); -bool SaveState(NDS& nds, const std::string& filename); -void UndoStateLoad(NDS& nds); - -void EnableCheats(NDS& nds, bool enable); -ARCodeFile* GetCheatFile(); - -void ROMIcon(const u8 (&data)[512], const u16 (&palette)[16], u32 (&iconRef)[32*32]); -void AnimatedROMIcon(const u8 (&data)[8][512], const u16 (&palette)[8][16], - const u16 (&sequence)[64], u32 (&animatedIconRef)[64][32*32], - std::vector &animatedSequenceRef); -} - -#endif // ROMMANAGER_H diff --git a/src/frontend/qt_sdl/Screen.cpp b/src/frontend/qt_sdl/Screen.cpp index 9174d3dd6d..539f198e98 100644 --- a/src/frontend/qt_sdl/Screen.cpp +++ b/src/frontend/qt_sdl/Screen.cpp @@ -16,20 +16,13 @@ with melonDS. If not, see http://www.gnu.org/licenses/. */ -#include -#include -#include #include #include -#include -#include -#include #include #include #include -#include #ifndef _WIN32 #ifndef APPLE #include @@ -41,6 +34,7 @@ #include "duckstation/gl/context.h" #include "main.h" +#include "EmuInstance.h" #include "NDS.h" #include "GPU.h" @@ -56,15 +50,6 @@ using namespace melonDS; -// TEMP -extern MainWindow* mainWindow; -extern EmuThread* emuThread; -extern bool RunningSomething; -extern int autoScreenSizing; - -extern int videoRenderer; -extern bool videoSettingsDirty; - const u32 kOSDMargin = 6; @@ -72,11 +57,29 @@ ScreenPanel::ScreenPanel(QWidget* parent) : QWidget(parent) { setMouseTracking(true); setAttribute(Qt::WA_AcceptTouchEvents); + + QWidget* w = parent; + for (;;) + { + mainWindow = qobject_cast(w); + if (mainWindow) break; + w = w->parentWidget(); + if (!w) break; + } + + emuInstance = mainWindow->getEmuInstance(); + + mouseHide = false; + mouseHideDelay = 0; + QTimer* mouseTimer = setupMouseTimer(); - connect(mouseTimer, &QTimer::timeout, [=] { if (Config::MouseHide) setCursor(Qt::BlankCursor);}); + connect(mouseTimer, &QTimer::timeout, [=] { if (mouseHide) setCursor(Qt::BlankCursor);}); osdEnabled = false; osdID = 1; + + loadConfig(); + setFilter(mainWindow->getWindowConfig().GetBool("ScreenFilter")); } ScreenPanel::~ScreenPanel() @@ -85,21 +88,48 @@ ScreenPanel::~ScreenPanel() delete mouseTimer; } +void ScreenPanel::loadConfig() +{ + auto& cfg = mainWindow->getWindowConfig(); + + screenRotation = cfg.GetInt("ScreenRotation"); + screenGap = cfg.GetInt("ScreenGap"); + screenLayout = cfg.GetInt("ScreenLayout"); + screenSwap = cfg.GetBool("ScreenSwap"); + screenSizing = cfg.GetInt("ScreenSizing"); + integerScaling = cfg.GetBool("IntegerScaling"); + screenAspectTop = cfg.GetInt("ScreenAspectTop"); + screenAspectBot = cfg.GetInt("ScreenAspectBot"); +} + +void ScreenPanel::setFilter(bool filter) +{ + this->filter = filter; +} + +void ScreenPanel::setMouseHide(bool enable, int delay) +{ + mouseHide = enable; + mouseHideDelay = delay; + + mouseTimer->setInterval(mouseHideDelay); +} + void ScreenPanel::setupScreenLayout() { int w = width(); int h = height(); - int sizing = Config::ScreenSizing; - if (sizing == 3) sizing = autoScreenSizing; + int sizing = screenSizing; + if (sizing == screenSizing_Auto) sizing = autoScreenSizing; float aspectTop, aspectBot; for (auto ratio : aspectRatios) { - if (ratio.id == Config::ScreenAspectTop) + if (ratio.id == screenAspectTop) aspectTop = ratio.ratio; - if (ratio.id == Config::ScreenAspectBot) + if (ratio.id == screenAspectBot) aspectBot = ratio.ratio; } @@ -109,49 +139,49 @@ void ScreenPanel::setupScreenLayout() if (aspectBot == 0) aspectBot = ((float) w / h) / (4.f / 3.f); - Frontend::SetupScreenLayout(w, h, - static_cast(Config::ScreenLayout), - static_cast(Config::ScreenRotation), - static_cast(sizing), - Config::ScreenGap, - Config::IntegerScaling != 0, - Config::ScreenSwap != 0, - aspectTop, - aspectBot); - - numScreens = Frontend::GetScreenTransforms(screenMatrix[0], screenKind); + layout.Setup(w, h, + static_cast(screenLayout), + static_cast(screenRotation), + static_cast(sizing), + screenGap, + integerScaling != 0, + screenSwap != 0, + aspectTop, + aspectBot); + + numScreens = layout.GetScreenTransforms(screenMatrix[0], screenKind); } QSize ScreenPanel::screenGetMinSize(int factor = 1) { - bool isHori = (Config::ScreenRotation == Frontend::screenRot_90Deg - || Config::ScreenRotation == Frontend::screenRot_270Deg); - int gap = Config::ScreenGap * factor; + bool isHori = (screenRotation == screenRot_90Deg + || screenRotation == screenRot_270Deg); + int gap = screenGap * factor; int w = 256 * factor; int h = 192 * factor; - if (Config::ScreenSizing == Frontend::screenSizing_TopOnly - || Config::ScreenSizing == Frontend::screenSizing_BotOnly) + if (screenSizing == screenSizing_TopOnly + || screenSizing == screenSizing_BotOnly) { return QSize(w, h); } - if (Config::ScreenLayout == Frontend::screenLayout_Natural) + if (screenLayout == screenLayout_Natural) { if (isHori) return QSize(h+gap+h, w); else return QSize(w, h+gap+h); } - else if (Config::ScreenLayout == Frontend::screenLayout_Vertical) + else if (screenLayout == screenLayout_Vertical) { if (isHori) return QSize(h, w+gap+w); else return QSize(w, h+gap+h); } - else if (Config::ScreenLayout == Frontend::screenLayout_Horizontal) + else if (screenLayout == screenLayout_Horizontal) { if (isHori) return QSize(h+gap+h, w); @@ -169,10 +199,20 @@ QSize ScreenPanel::screenGetMinSize(int factor = 1) void ScreenPanel::onScreenLayoutChanged() { + loadConfig(); + setMinimumSize(screenGetMinSize()); setupScreenLayout(); } +void ScreenPanel::onAutoScreenSizingChanged(int sizing) +{ + autoScreenSizing = sizing; + if (screenSizing != screenSizing_Auto) return; + + setupScreenLayout(); +} + void ScreenPanel::resizeEvent(QResizeEvent* event) { setupScreenLayout(); @@ -187,11 +227,11 @@ void ScreenPanel::mousePressEvent(QMouseEvent* event) int x = event->pos().x(); int y = event->pos().y(); - if (Frontend::GetTouchCoords(x, y, false)) + if (layout.GetTouchCoords(x, y, false)) { touching = true; - assert(emuThread->NDS != nullptr); - emuThread->NDS->TouchScreen(x, y); + assert(emuInstance->getNDS() != nullptr); + emuInstance->getNDS()->TouchScreen(x, y); } } @@ -203,8 +243,8 @@ void ScreenPanel::mouseReleaseEvent(QMouseEvent* event) if (touching) { touching = false; - assert(emuThread->NDS != nullptr); - emuThread->NDS->ReleaseScreen(); + assert(emuInstance->getNDS() != nullptr); + emuInstance->getNDS()->ReleaseScreen(); } } @@ -220,10 +260,10 @@ void ScreenPanel::mouseMoveEvent(QMouseEvent* event) int x = event->pos().x(); int y = event->pos().y(); - if (Frontend::GetTouchCoords(x, y, true)) + if (layout.GetTouchCoords(x, y, true)) { - assert(emuThread->NDS != nullptr); - emuThread->NDS->TouchScreen(x, y); + assert(emuInstance->getNDS() != nullptr); + emuInstance->getNDS()->TouchScreen(x, y); } } @@ -239,19 +279,19 @@ void ScreenPanel::tabletEvent(QTabletEvent* event) int x = event->x(); int y = event->y(); - if (Frontend::GetTouchCoords(x, y, event->type()==QEvent::TabletMove)) + if (layout.GetTouchCoords(x, y, event->type()==QEvent::TabletMove)) { touching = true; - assert(emuThread->NDS != nullptr); - emuThread->NDS->TouchScreen(x, y); + assert(emuInstance->getNDS() != nullptr); + emuInstance->getNDS()->TouchScreen(x, y); } } break; case QEvent::TabletRelease: if (touching) { - assert(emuThread->NDS != nullptr); - emuThread->NDS->ReleaseScreen(); + assert(emuInstance->getNDS() != nullptr); + emuInstance->getNDS()->ReleaseScreen(); touching = false; } break; @@ -274,19 +314,19 @@ void ScreenPanel::touchEvent(QTouchEvent* event) int x = (int)lastPosition.x(); int y = (int)lastPosition.y(); - if (Frontend::GetTouchCoords(x, y, event->type()==QEvent::TouchUpdate)) + if (layout.GetTouchCoords(x, y, event->type()==QEvent::TouchUpdate)) { touching = true; - assert(emuThread->NDS != nullptr); - emuThread->NDS->TouchScreen(x, y); + assert(emuInstance->getNDS() != nullptr); + emuInstance->getNDS()->TouchScreen(x, y); } } break; case QEvent::TouchEnd: if (touching) { - assert(emuThread->NDS != nullptr); - emuThread->NDS->ReleaseScreen(); + assert(emuInstance->getNDS() != nullptr); + emuInstance->getNDS()->ReleaseScreen(); touching = false; } break; @@ -318,7 +358,7 @@ QTimer* ScreenPanel::setupMouseTimer() { mouseTimer = new QTimer(); mouseTimer->setSingleShot(true); - mouseTimer->setInterval(Config::MouseHideSeconds*1000); + mouseTimer->setInterval(mouseHideDelay); mouseTimer->start(); return mouseTimer; @@ -618,19 +658,23 @@ void ScreenPanelNative::paintEvent(QPaintEvent* event) // fill background painter.fillRect(event->rect(), QColor::fromRgb(0, 0, 0)); + auto emuThread = emuInstance->getEmuThread(); + if (emuThread->emuIsActive()) { - assert(emuThread->NDS != nullptr); + auto nds = emuInstance->getNDS(); + + assert(nds != nullptr); emuThread->FrontBufferLock.lock(); int frontbuf = emuThread->FrontBuffer; - if (!emuThread->NDS->GPU.Framebuffer[frontbuf][0] || !emuThread->NDS->GPU.Framebuffer[frontbuf][1]) + if (!nds->GPU.Framebuffer[frontbuf][0] || !nds->GPU.Framebuffer[frontbuf][1]) { emuThread->FrontBufferLock.unlock(); return; } - memcpy(screen[0].scanLine(0), emuThread->NDS->GPU.Framebuffer[frontbuf][0].get(), 256 * 192 * 4); - memcpy(screen[1].scanLine(0), emuThread->NDS->GPU.Framebuffer[frontbuf][1].get(), 256 * 192 * 4); + memcpy(screen[0].scanLine(0), nds->GPU.Framebuffer[frontbuf][0].get(), 256 * 192 * 4); + memcpy(screen[1].scanLine(0), nds->GPU.Framebuffer[frontbuf][1].get(), 256 * 192 * 4); emuThread->FrontBufferLock.unlock(); QRect screenrc(0, 0, 256, 192); @@ -683,14 +727,29 @@ ScreenPanelGL::~ScreenPanelGL() bool ScreenPanelGL::createContext() { - std::optional windowInfo = getWindowInfo(); - std::array versionsToTry = { - GL::Context::Version{GL::Context::Profile::Core, 4, 3}, - GL::Context::Version{GL::Context::Profile::Core, 3, 2}}; - if (windowInfo.has_value()) + std::optional windowinfo = getWindowInfo(); + + // if our parent window is parented to another window, we will + // share our OpenGL context with that window + MainWindow* parentwin = (MainWindow*)parentWidget()->parentWidget(); + if (parentwin) { - glContext = GL::Context::Create(*getWindowInfo(), versionsToTry); - glContext->DoneCurrent(); + if (windowinfo.has_value()) + { + glContext = parentwin->getOGLContext()->CreateSharedContext(*windowinfo); + glContext->DoneCurrent(); + } + } + else + { + std::array versionsToTry = { + GL::Context::Version{GL::Context::Profile::Core, 4, 3}, + GL::Context::Version{GL::Context::Profile::Core, 3, 2}}; + if (windowinfo.has_value()) + { + glContext = GL::Context::Create(*windowinfo, versionsToTry); + glContext->DoneCurrent(); + } } return glContext != nullptr; @@ -710,10 +769,10 @@ void ScreenPanelGL::initOpenGL() glContext->MakeCurrent(); OpenGL::CompileVertexFragmentProgram(screenShaderProgram, - kScreenVS, kScreenFS, - "ScreenShader", - {{"vPosition", 0}, {"vTexcoord", 1}}, - {{"oColor", 0}}); + kScreenVS, kScreenFS, + "ScreenShader", + {{"vPosition", 0}, {"vTexcoord", 1}}, + {{"oColor", 0}}); glUseProgram(screenShaderProgram); glUniform1i(glGetUniformLocation(screenShaderProgram, "ScreenTex"), 0); @@ -767,11 +826,12 @@ void ScreenPanelGL::initOpenGL() memset(zeroData, 0, sizeof(zeroData)); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 192, 256, 2, GL_RGBA, GL_UNSIGNED_BYTE, zeroData); + OpenGL::CompileVertexFragmentProgram(osdShader, - kScreenVS_OSD, kScreenFS_OSD, - "OSDShader", - {{"vPosition", 0}}, - {{"oColor", 0}}); + kScreenVS_OSD, kScreenFS_OSD, + "OSDShader", + {{"vPosition", 0}}, + {{"oColor", 0}}); glUseProgram(osdShader); glUniform1i(glGetUniformLocation(osdShader, "OSDTex"), 0); @@ -800,8 +860,6 @@ void ScreenPanelGL::initOpenGL() glEnableVertexAttribArray(0); // position glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, (void*)(0)); - - glContext->SetSwapInterval(Config::ScreenVSync ? Config::ScreenVSyncInterval : 0); transferLayout(); } @@ -816,6 +874,7 @@ void ScreenPanelGL::deinitOpenGL() glDeleteProgram(screenShaderProgram); + for (const auto& [key, tex] : osdTextures) { glDeleteTextures(1, &tex); @@ -827,11 +886,19 @@ void ScreenPanelGL::deinitOpenGL() glDeleteProgram(osdShader); + glContext->DoneCurrent(); lastScreenWidth = lastScreenHeight = -1; } +void ScreenPanelGL::makeCurrentGL() +{ + if (!glContext) return; + + glContext->MakeCurrent(); +} + void ScreenPanelGL::osdRenderItem(OSDItem* item) { ScreenPanel::osdRenderItem(item); @@ -863,7 +930,13 @@ void ScreenPanelGL::osdDeleteItem(OSDItem* item) void ScreenPanelGL::drawScreenGL() { if (!glContext) return; - if (!emuThread->NDS) return; + + auto nds = emuInstance->getNDS(); + if (!nds) return; + + auto emuThread = emuInstance->getEmuThread(); + + glContext->MakeCurrent(); int w = windowInfo.surface_width; int h = windowInfo.surface_height; @@ -886,10 +959,10 @@ void ScreenPanelGL::drawScreenGL() glActiveTexture(GL_TEXTURE0); #ifdef OGLRENDERER_ENABLED - if (emuThread->NDS->GPU.GetRenderer3D().Accelerated) + if (nds->GPU.GetRenderer3D().Accelerated) { // hardware-accelerated render - emuThread->NDS->GPU.GetRenderer3D().BindOutputTexture(frontbuf); + nds->GPU.GetRenderer3D().BindOutputTexture(frontbuf); } else #endif @@ -897,12 +970,12 @@ void ScreenPanelGL::drawScreenGL() // regular render glBindTexture(GL_TEXTURE_2D, screenTexture); - if (emuThread->NDS->GPU.Framebuffer[frontbuf][0] && emuThread->NDS->GPU.Framebuffer[frontbuf][1]) + if (nds->GPU.Framebuffer[frontbuf][0] && nds->GPU.Framebuffer[frontbuf][1]) { glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 256, 192, GL_RGBA, - GL_UNSIGNED_BYTE, emuThread->NDS->GPU.Framebuffer[frontbuf][0].get()); + GL_UNSIGNED_BYTE, nds->GPU.Framebuffer[frontbuf][0].get()); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 192+2, 256, 192, GL_RGBA, - GL_UNSIGNED_BYTE, emuThread->NDS->GPU.Framebuffer[frontbuf][1].get()); + GL_UNSIGNED_BYTE, nds->GPU.Framebuffer[frontbuf][1].get()); } } @@ -1019,7 +1092,8 @@ std::optional ScreenPanelGL::getWindowInfo() } else { - qCritical() << "Unknown PNI platform " << platform_name; + //qCritical() << "Unknown PNI platform " << platform_name; + Platform::Log(LogLevel::Error, "Unknown PNI platform %s\n", platform_name.toStdString().c_str()); return std::nullopt; } #endif @@ -1058,7 +1132,6 @@ void ScreenPanelGL::transferLayout() lastScreenHeight = windowInfo->surface_height; } - this->filter = Config::ScreenFilter; this->windowInfo = *windowInfo; screenSettingsLock.unlock(); diff --git a/src/frontend/qt_sdl/Screen.h b/src/frontend/qt_sdl/Screen.h index 4ef4feca5b..8311784971 100644 --- a/src/frontend/qt_sdl/Screen.h +++ b/src/frontend/qt_sdl/Screen.h @@ -31,11 +31,12 @@ #include #include "glad/glad.h" -#include "FrontendUtil.h" +#include "ScreenLayout.h" #include "duckstation/gl/context.h" -class EmuThread; +class MainWindow; +class EmuInstance; const struct { int id; float ratio; const char* label; } aspectRatios[] = @@ -57,6 +58,10 @@ class ScreenPanel : public QWidget explicit ScreenPanel(QWidget* parent); virtual ~ScreenPanel(); + void setFilter(bool filter); + + void setMouseHide(bool enable, int delay); + QTimer* setupMouseTimer(); void updateMouseTimer(); QTimer* mouseTimer; @@ -67,8 +72,34 @@ class ScreenPanel : public QWidget private slots: void onScreenLayoutChanged(); + void onAutoScreenSizingChanged(int sizing); protected: + MainWindow* mainWindow; + EmuInstance* emuInstance; + + bool filter; + + int screenRotation; + int screenGap; + int screenLayout; + bool screenSwap; + int screenSizing; + bool integerScaling; + int screenAspectTop, screenAspectBot; + + int autoScreenSizing; + + ScreenLayout layout; + float screenMatrix[kMaxScreenTransforms][6]; + int screenKind[kMaxScreenTransforms]; + int numScreens; + + bool touching = false; + + bool mouseHide; + int mouseHideDelay; + struct OSDItem { unsigned int id; @@ -86,6 +117,8 @@ private slots: unsigned int osdID; std::deque osdItems; + void loadConfig(); + virtual void setupScreenLayout(); void resizeEvent(QResizeEvent* event) override; @@ -98,12 +131,6 @@ private slots: void touchEvent(QTouchEvent* event); bool event(QEvent* event) override; - float screenMatrix[Frontend::MaxScreenTransforms][6]; - int screenKind[Frontend::MaxScreenTransforms]; - int numScreens; - - bool touching = false; - void showCursor(); int osdFindBreakPoint(const char* text, int i); @@ -132,7 +159,7 @@ class ScreenPanelNative : public ScreenPanel void setupScreenLayout() override; QImage screen[2]; - QTransform screenTrans[Frontend::MaxScreenTransforms]; + QTransform screenTrans[kMaxScreenTransforms]; }; @@ -152,6 +179,7 @@ class ScreenPanelGL : public ScreenPanel void initOpenGL(); void deinitOpenGL(); + void makeCurrentGL(); void drawScreenGL(); GL::Context* getContext() { return glContext.get(); } @@ -177,7 +205,6 @@ class ScreenPanelGL : public ScreenPanel QMutex screenSettingsLock; WindowInfo windowInfo; - bool filter; int lastScreenWidth = -1, lastScreenHeight = -1; diff --git a/src/frontend/qt_sdl/TitleManagerDialog.cpp b/src/frontend/qt_sdl/TitleManagerDialog.cpp index 21ca844e2a..70c4ecd32e 100644 --- a/src/frontend/qt_sdl/TitleManagerDialog.cpp +++ b/src/frontend/qt_sdl/TitleManagerDialog.cpp @@ -23,7 +23,7 @@ #include "types.h" #include "Platform.h" #include "Config.h" -#include "ROMManager.h" +#include "main.h" #include "DSi_NAND.h" #include "TitleManagerDialog.h" @@ -36,14 +36,14 @@ using namespace melonDS::Platform; std::unique_ptr TitleManagerDialog::nand = nullptr; TitleManagerDialog* TitleManagerDialog::currentDlg = nullptr; -extern std::string EmuDirectory; - TitleManagerDialog::TitleManagerDialog(QWidget* parent, DSi_NAND::NANDImage& image) : QDialog(parent), ui(new Ui::TitleManagerDialog), nandmount(image) { ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose); + emuInstance = ((MainWindow*)parent)->getEmuInstance(); + ui->lstTitleList->setIconSize(QSize(32, 32)); const u32 category = 0x00030004; @@ -113,7 +113,7 @@ void TitleManagerDialog::createTitleItem(u32 category, u32 titleid) nandmount.GetTitleInfo(category, titleid, version, &header, &banner); u32 icondata[32*32]; - ROMManager::ROMIcon(banner.Icon, banner.Palette, icondata); + emuInstance->romIcon(banner.Icon, banner.Palette, icondata); QImage iconimg((const uchar*)icondata, 32, 32, QImage::Format_RGBA8888); QIcon icon(QPixmap::fromImage(iconimg.copy())); @@ -140,7 +140,9 @@ bool TitleManagerDialog::openNAND() { nand = nullptr; - FileHandle* bios7i = Platform::OpenLocalFile(Config::DSiBIOS7Path, FileMode::Read); + Config::Table cfg = Config::GetGlobalTable(); + + FileHandle* bios7i = Platform::OpenLocalFile(cfg.GetString("DSi.BIOS7Path"), FileMode::Read); if (!bios7i) return false; @@ -149,7 +151,7 @@ bool TitleManagerDialog::openNAND() FileRead(es_keyY, 16, 1, bios7i); CloseFile(bios7i); - FileHandle* nandfile = Platform::OpenLocalFile(Config::DSiNANDPath, FileMode::ReadWriteExisting); + FileHandle* nandfile = Platform::OpenLocalFile(cfg.GetString("DSi.NANDPath"), FileMode::ReadWriteExisting); if (!nandfile) return false; @@ -296,7 +298,7 @@ void TitleManagerDialog::onImportTitleData() QString file = QFileDialog::getOpenFileName(this, "Select file to import...", - QString::fromStdString(EmuDirectory), + emuDirectory, "Title data files (" + extensions + ");;Any file (*.*)"); if (file.isEmpty()) return; @@ -370,7 +372,7 @@ void TitleManagerDialog::onExportTitleData() QString file = QFileDialog::getSaveFileName(this, "Select path to export to...", - QString::fromStdString(EmuDirectory) + exportname, + emuDirectory + exportname, "Title data files (" + extensions + ");;Any file (*.*)"); if (file.isEmpty()) return; @@ -543,7 +545,7 @@ void TitleImportDialog::on_btnAppBrowse_clicked() { QString file = QFileDialog::getOpenFileName(this, "Select title executable...", - QString::fromStdString(EmuDirectory), + emuDirectory, "DSiWare executables (*.app *.nds *.dsi *.srl);;Any file (*.*)"); if (file.isEmpty()) return; @@ -555,7 +557,7 @@ void TitleImportDialog::on_btnTmdBrowse_clicked() { QString file = QFileDialog::getOpenFileName(this, "Select title metadata...", - QString::fromStdString(EmuDirectory), + emuDirectory, "DSiWare metadata (*.tmd);;Any file (*.*)"); if (file.isEmpty()) return; diff --git a/src/frontend/qt_sdl/TitleManagerDialog.h b/src/frontend/qt_sdl/TitleManagerDialog.h index 2e392ebf7f..8360ffa485 100644 --- a/src/frontend/qt_sdl/TitleManagerDialog.h +++ b/src/frontend/qt_sdl/TitleManagerDialog.h @@ -41,6 +41,8 @@ namespace Ui class TitleManagerDialog; class TitleImportDialog; +class EmuInstance; + class TitleManagerDialog : public QDialog { Q_OBJECT @@ -94,6 +96,8 @@ private slots: void onExportTitleData(); private: + EmuInstance* emuInstance; + melonDS::DSi_NAND::NANDMount nandmount; Ui::TitleManagerDialog* ui; diff --git a/src/frontend/qt_sdl/VideoSettingsDialog.cpp b/src/frontend/qt_sdl/VideoSettingsDialog.cpp index 714707b8a2..f0f4672ba5 100644 --- a/src/frontend/qt_sdl/VideoSettingsDialog.cpp +++ b/src/frontend/qt_sdl/VideoSettingsDialog.cpp @@ -16,7 +16,6 @@ with melonDS. If not, see http://www.gnu.org/licenses/. */ -#include #include #include @@ -24,26 +23,31 @@ #include "Platform.h" #include "Config.h" #include "GPU.h" +#include "main.h" #include "VideoSettingsDialog.h" #include "ui_VideoSettingsDialog.h" -inline bool UsesGL() +inline bool VideoSettingsDialog::UsesGL() { - return (Config::ScreenUseGL != 0) || (Config::_3DRenderer != renderer3D_Software); + auto& cfg = emuInstance->getGlobalConfig(); + return cfg.GetBool("Screen.UseGL") || (cfg.GetInt("3D.Renderer") != renderer3D_Software); } VideoSettingsDialog* VideoSettingsDialog::currentDlg = nullptr; void VideoSettingsDialog::setEnabled() { - bool softwareRenderer = Config::_3DRenderer == renderer3D_Software; + auto& cfg = emuInstance->getGlobalConfig(); + int renderer = cfg.GetInt("3D.Renderer"); + + bool softwareRenderer = renderer == renderer3D_Software; ui->cbGLDisplay->setEnabled(softwareRenderer); ui->cbSoftwareThreaded->setEnabled(softwareRenderer); ui->cbxGLResolution->setEnabled(!softwareRenderer); - ui->cbBetterPolygons->setEnabled(Config::_3DRenderer == renderer3D_OpenGL); - ui->cbxComputeHiResCoords->setEnabled(Config::_3DRenderer == renderer3D_OpenGLCompute); + ui->cbBetterPolygons->setEnabled(renderer == renderer3D_OpenGL); + ui->cbxComputeHiResCoords->setEnabled(renderer == renderer3D_OpenGLCompute); } VideoSettingsDialog::VideoSettingsDialog(QWidget* parent) : QDialog(parent), ui(new Ui::VideoSettingsDialog) @@ -51,14 +55,17 @@ VideoSettingsDialog::VideoSettingsDialog(QWidget* parent) : QDialog(parent), ui( ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose); - oldRenderer = Config::_3DRenderer; - oldGLDisplay = Config::ScreenUseGL; - oldVSync = Config::ScreenVSync; - oldVSyncInterval = Config::ScreenVSyncInterval; - oldSoftThreaded = Config::Threaded3D; - oldGLScale = Config::GL_ScaleFactor; - oldGLBetterPolygons = Config::GL_BetterPolygons; - oldHiresCoordinates = Config::GL_HiresCoordinates; + emuInstance = ((MainWindow*)parent)->getEmuInstance(); + + auto& cfg = emuInstance->getGlobalConfig(); + oldRenderer = cfg.GetInt("3D.Renderer"); + oldGLDisplay = cfg.GetBool("Screen.UseGL"); + oldVSync = cfg.GetBool("Screen.VSync"); + oldVSyncInterval = cfg.GetInt("Screen.VSyncInterval"); + oldSoftThreaded = cfg.GetBool("3D.Soft.Threaded"); + oldGLScale = cfg.GetInt("3D.GL.ScaleFactor"); + oldGLBetterPolygons = cfg.GetBool("3D.GL.BetterPolygons"); + oldHiresCoordinates = cfg.GetBool("3D.GL.HiresCoordinates"); grp3DRenderer = new QButtonGroup(this); grp3DRenderer->addButton(ui->rb3DSoftware, renderer3D_Software); @@ -69,7 +76,7 @@ VideoSettingsDialog::VideoSettingsDialog(QWidget* parent) : QDialog(parent), ui( #else connect(grp3DRenderer, SIGNAL(idClicked(int)), this, SLOT(onChange3DRenderer(int))); #endif - grp3DRenderer->button(Config::_3DRenderer)->setChecked(true); + grp3DRenderer->button(oldRenderer)->setChecked(true); #ifndef OGLRENDERER_ENABLED ui->rb3DOpenGL->setEnabled(false); @@ -79,21 +86,21 @@ VideoSettingsDialog::VideoSettingsDialog(QWidget* parent) : QDialog(parent), ui( ui->rb3DCompute->setEnabled(false); #endif - ui->cbGLDisplay->setChecked(Config::ScreenUseGL != 0); + ui->cbGLDisplay->setChecked(oldGLDisplay != 0); - ui->cbVSync->setChecked(Config::ScreenVSync != 0); - ui->sbVSyncInterval->setValue(Config::ScreenVSyncInterval); + ui->cbVSync->setChecked(oldVSync != 0); + ui->sbVSyncInterval->setValue(oldVSyncInterval); - ui->cbSoftwareThreaded->setChecked(Config::Threaded3D != 0); + ui->cbSoftwareThreaded->setChecked(oldSoftThreaded); for (int i = 1; i <= 16; i++) ui->cbxGLResolution->addItem(QString("%1x native (%2x%3)").arg(i).arg(256*i).arg(192*i)); - ui->cbxGLResolution->setCurrentIndex(Config::GL_ScaleFactor-1); + ui->cbxGLResolution->setCurrentIndex(oldGLScale-1); - ui->cbBetterPolygons->setChecked(Config::GL_BetterPolygons != 0); - ui->cbxComputeHiResCoords->setChecked(Config::GL_HiresCoordinates != 0); + ui->cbBetterPolygons->setChecked(oldGLBetterPolygons != 0); + ui->cbxComputeHiResCoords->setChecked(oldHiresCoordinates != 0); - if (!Config::ScreenVSync) + if (!oldVSync) ui->sbVSyncInterval->setEnabled(false); setVsyncControlEnable(UsesGL()); @@ -116,14 +123,15 @@ void VideoSettingsDialog::on_VideoSettingsDialog_rejected() { bool old_gl = UsesGL(); - Config::_3DRenderer = oldRenderer; - Config::ScreenUseGL = oldGLDisplay; - Config::ScreenVSync = oldVSync; - Config::ScreenVSyncInterval = oldVSyncInterval; - Config::Threaded3D = oldSoftThreaded; - Config::GL_ScaleFactor = oldGLScale; - Config::GL_BetterPolygons = oldGLBetterPolygons; - Config::GL_HiresCoordinates = oldHiresCoordinates; + auto& cfg = emuInstance->getGlobalConfig(); + cfg.SetInt("3D.Renderer", oldRenderer); + cfg.SetBool("Screen.UseGL", oldGLDisplay); + cfg.SetBool("Screen.VSync", oldVSync); + cfg.SetInt("Screen.VSyncInterval", oldVSyncInterval); + cfg.SetBool("3D.Soft.Threaded", oldSoftThreaded); + cfg.SetInt("3D.GL.ScaleFactor", oldGLScale); + cfg.SetBool("3D.GL.BetterPolygons", oldGLBetterPolygons); + cfg.SetBool("3D.GL.HiresCoordinates", oldHiresCoordinates); emit updateVideoSettings(old_gl != UsesGL()); @@ -140,7 +148,8 @@ void VideoSettingsDialog::onChange3DRenderer(int renderer) { bool old_gl = UsesGL(); - Config::_3DRenderer = renderer; + auto& cfg = emuInstance->getGlobalConfig(); + cfg.SetInt("3D.Renderer", renderer); setEnabled(); @@ -151,7 +160,8 @@ void VideoSettingsDialog::on_cbGLDisplay_stateChanged(int state) { bool old_gl = UsesGL(); - Config::ScreenUseGL = (state != 0); + auto& cfg = emuInstance->getGlobalConfig(); + cfg.SetBool("Screen.UseGL", (state != 0)); setVsyncControlEnable(UsesGL()); @@ -162,19 +172,25 @@ void VideoSettingsDialog::on_cbVSync_stateChanged(int state) { bool vsync = (state != 0); ui->sbVSyncInterval->setEnabled(vsync); - Config::ScreenVSync = vsync; + + auto& cfg = emuInstance->getGlobalConfig(); + cfg.SetBool("Screen.VSync", vsync); + emit updateVideoSettings(false); } void VideoSettingsDialog::on_sbVSyncInterval_valueChanged(int val) { - Config::ScreenVSyncInterval = val; + auto& cfg = emuInstance->getGlobalConfig(); + cfg.SetInt("Screen.VSyncInterval", val); + emit updateVideoSettings(false); } void VideoSettingsDialog::on_cbSoftwareThreaded_stateChanged(int state) { - Config::Threaded3D = (state != 0); + auto& cfg = emuInstance->getGlobalConfig(); + cfg.SetBool("3D.Soft.Threaded", (state != 0)); emit updateVideoSettings(false); } @@ -184,7 +200,8 @@ void VideoSettingsDialog::on_cbxGLResolution_currentIndexChanged(int idx) // prevent a spurious change if (ui->cbxGLResolution->count() < 16) return; - Config::GL_ScaleFactor = idx+1; + auto& cfg = emuInstance->getGlobalConfig(); + cfg.SetInt("3D.GL.ScaleFactor", idx+1); setVsyncControlEnable(UsesGL()); @@ -193,14 +210,16 @@ void VideoSettingsDialog::on_cbxGLResolution_currentIndexChanged(int idx) void VideoSettingsDialog::on_cbBetterPolygons_stateChanged(int state) { - Config::GL_BetterPolygons = (state != 0); + auto& cfg = emuInstance->getGlobalConfig(); + cfg.SetBool("3D.GL.BetterPolygons", (state != 0)); emit updateVideoSettings(false); } void VideoSettingsDialog::on_cbxComputeHiResCoords_stateChanged(int state) { - Config::GL_HiresCoordinates = (state != 0); + auto& cfg = emuInstance->getGlobalConfig(); + cfg.SetBool("3D.GL.HiresCoordinates", (state != 0)); emit updateVideoSettings(false); } diff --git a/src/frontend/qt_sdl/VideoSettingsDialog.h b/src/frontend/qt_sdl/VideoSettingsDialog.h index 97e0dbd0d9..1ff5c0ae3c 100644 --- a/src/frontend/qt_sdl/VideoSettingsDialog.h +++ b/src/frontend/qt_sdl/VideoSettingsDialog.h @@ -24,6 +24,7 @@ namespace Ui { class VideoSettingsDialog; } class VideoSettingsDialog; +class EmuInstance; class VideoSettingsDialog : public QDialog { @@ -33,6 +34,8 @@ class VideoSettingsDialog : public QDialog explicit VideoSettingsDialog(QWidget* parent); ~VideoSettingsDialog(); + bool UsesGL(); + static VideoSettingsDialog* currentDlg; static VideoSettingsDialog* openDlg(QWidget* parent) { @@ -73,6 +76,7 @@ private slots: void setEnabled(); Ui::VideoSettingsDialog* ui; + EmuInstance* emuInstance; QButtonGroup* grp3DRenderer; diff --git a/src/frontend/qt_sdl/WifiSettingsDialog.cpp b/src/frontend/qt_sdl/WifiSettingsDialog.cpp index d71657a339..ad082f4232 100644 --- a/src/frontend/qt_sdl/WifiSettingsDialog.cpp +++ b/src/frontend/qt_sdl/WifiSettingsDialog.cpp @@ -22,10 +22,9 @@ #include "types.h" #include "Platform.h" #include "Config.h" +#include "main.h" -#include "LAN_Socket.h" -#include "LAN_PCap.h" -#include "Wifi.h" +#include "Net.h" #include "WifiSettingsDialog.h" #include "ui_WifiSettingsDialog.h" @@ -42,15 +41,17 @@ WifiSettingsDialog* WifiSettingsDialog::currentDlg = nullptr; bool WifiSettingsDialog::needsReset = false; -extern bool RunningSomething; - WifiSettingsDialog::WifiSettingsDialog(QWidget* parent) : QDialog(parent), ui(new Ui::WifiSettingsDialog) { ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose); - haspcap = LAN_PCap::Init(false); + emuInstance = ((MainWindow*)parent)->getEmuInstance(); + auto& cfg = emuInstance->getGlobalConfig(); + + Net::DeInit(); + haspcap = Net_PCap::InitAdapterList(); ui->rbDirectMode->setText("Direct mode (requires " PCAP_NAME " and ethernet connection)"); @@ -58,20 +59,21 @@ WifiSettingsDialog::WifiSettingsDialog(QWidget* parent) : QDialog(parent), ui(ne ui->lblAdapterIP->setText("(none)"); int sel = 0; - for (int i = 0; i < LAN_PCap::NumAdapters; i++) + for (int i = 0; i < Net_PCap::NumAdapters; i++) { - LAN_PCap::AdapterData* adapter = &LAN_PCap::Adapters[i]; + Net_PCap::AdapterData* adapter = &Net_PCap::Adapters[i]; ui->cbxDirectAdapter->addItem(QString(adapter->FriendlyName)); - if (!strncmp(adapter->DeviceName, Config::LANDevice.c_str(), 128)) + if (!strncmp(adapter->DeviceName, cfg.GetString("LAN.Device").c_str(), 128)) sel = i; } ui->cbxDirectAdapter->setCurrentIndex(sel); // errrr??? - ui->rbDirectMode->setChecked(Config::DirectLAN); - ui->rbIndirectMode->setChecked(!Config::DirectLAN); + bool direct = cfg.GetBool("LAN.DirectMode"); + ui->rbDirectMode->setChecked(direct); + ui->rbIndirectMode->setChecked(!direct); if (!haspcap) ui->rbDirectMode->setEnabled(false); updateAdapterControls(); @@ -88,22 +90,27 @@ void WifiSettingsDialog::done(int r) if (r == QDialog::Accepted) { - Config::DirectLAN = ui->rbDirectMode->isChecked(); + auto& cfg = emuInstance->getGlobalConfig(); + + cfg.SetBool("LAN.DirectMode", ui->rbDirectMode->isChecked()); int sel = ui->cbxDirectAdapter->currentIndex(); - if (sel < 0 || sel >= LAN_PCap::NumAdapters) sel = 0; - if (LAN_PCap::NumAdapters < 1) + if (sel < 0 || sel >= Net_PCap::NumAdapters) sel = 0; + if (Net_PCap::NumAdapters < 1) { - Config::LANDevice = ""; + cfg.SetString("LAN.Device", ""); } else { - Config::LANDevice = LAN_PCap::Adapters[sel].DeviceName; + cfg.SetString("LAN.Device", Net_PCap::Adapters[sel].DeviceName); } Config::Save(); } + Net_PCap::DeInit(); + Net::Init(); + QDialog::done(r); closeDlg(); @@ -123,10 +130,10 @@ void WifiSettingsDialog::on_cbxDirectAdapter_currentIndexChanged(int sel) { if (!haspcap) return; - if (sel < 0 || sel >= LAN_PCap::NumAdapters) return; - if (LAN_PCap::NumAdapters < 1) return; + if (sel < 0 || sel >= Net_PCap::NumAdapters) return; + if (Net_PCap::NumAdapters < 1) return; - LAN_PCap::AdapterData* adapter = &LAN_PCap::Adapters[sel]; + Net_PCap::AdapterData* adapter = &Net_PCap::Adapters[sel]; char tmp[64]; sprintf(tmp, "%02X:%02X:%02X:%02X:%02X:%02X", diff --git a/src/frontend/qt_sdl/WifiSettingsDialog.h b/src/frontend/qt_sdl/WifiSettingsDialog.h index 82e1bd4951..68d518d10e 100644 --- a/src/frontend/qt_sdl/WifiSettingsDialog.h +++ b/src/frontend/qt_sdl/WifiSettingsDialog.h @@ -24,6 +24,8 @@ namespace Ui { class WifiSettingsDialog; } class WifiSettingsDialog; +class EmuInstance; + class WifiSettingsDialog : public QDialog { Q_OBJECT @@ -61,6 +63,7 @@ private slots: private: Ui::WifiSettingsDialog* ui; + EmuInstance* emuInstance; bool haspcap; diff --git a/src/frontend/qt_sdl/Window.cpp b/src/frontend/qt_sdl/Window.cpp index 536e02195b..cf0c8a559c 100644 --- a/src/frontend/qt_sdl/Window.cpp +++ b/src/frontend/qt_sdl/Window.cpp @@ -51,7 +51,6 @@ #endif #include "main.h" -#include "Input.h" #include "CheatsDialog.h" #include "DateTimeDialog.h" #include "EmuSettingsDialog.h" @@ -68,7 +67,6 @@ #include "RAMInfoDialog.h" #include "TitleManagerDialog.h" #include "PowerManagement/PowerManagementDialog.h" -#include "AudioInOut.h" #include "Platform.h" #include "Config.h" @@ -78,40 +76,67 @@ //#include "main_shaders.h" -#include "ROMManager.h" +#include "EmuInstance.h" #include "ArchiveUtil.h" #include "CameraManager.h" using namespace melonDS; -// TEMP -extern MainWindow* mainWindow; -extern EmuThread* emuThread; -extern bool RunningSomething; -extern QString NdsRomMimeType; -extern QStringList NdsRomExtensions; -extern QString GbaRomMimeType; -extern QStringList GbaRomExtensions; -extern QStringList ArchiveMimeTypes; -extern QStringList ArchiveExtensions; -/*static bool FileExtensionInList(const QString& filename, const QStringList& extensions, Qt::CaseSensitivity cs); -static bool MimeTypeInList(const QMimeType& mimetype, const QStringList& superTypeNames); -static bool NdsRomByExtension(const QString& filename); -static bool GbaRomByExtension(const QString& filename); -static bool SupportedArchiveByExtension(const QString& filename); -static bool NdsRomByMimetype(const QMimeType& mimetype); -static bool GbaRomByMimetype(const QMimeType& mimetype); -static bool SupportedArchiveByMimetype(const QMimeType& mimetype); -static bool ZstdNdsRomByExtension(const QString& filename); -static bool ZstdGbaRomByExtension(const QString& filename); -static bool FileIsSupportedFiletype(const QString& filename, bool insideArchive);*/ + + extern CameraManager* camManager[2]; extern bool camStarted[2]; -extern int videoRenderer; -extern bool videoSettingsDirty; +QString NdsRomMimeType = "application/x-nintendo-ds-rom"; +QStringList NdsRomExtensions { ".nds", ".srl", ".dsi", ".ids" }; + +QString GbaRomMimeType = "application/x-gba-rom"; +QStringList GbaRomExtensions { ".gba", ".agb" }; + + +// This list of supported archive formats is based on libarchive(3) version 3.6.2 (2022-12-09). +QStringList ArchiveMimeTypes +{ +#ifdef ARCHIVE_SUPPORT_ENABLED + "application/zip", + "application/x-7z-compressed", + "application/vnd.rar", // *.rar + "application/x-tar", + + "application/x-compressed-tar", // *.tar.gz + "application/x-xz-compressed-tar", + "application/x-bzip-compressed-tar", + "application/x-lz4-compressed-tar", + "application/x-zstd-compressed-tar", + + "application/x-tarz", // *.tar.Z + "application/x-lzip-compressed-tar", + "application/x-lzma-compressed-tar", + "application/x-lrzip-compressed-tar", + "application/x-tzo", // *.tar.lzo +#endif +}; + +QStringList ArchiveExtensions +{ +#ifdef ARCHIVE_SUPPORT_ENABLED + ".zip", ".7z", ".rar", ".tar", + + ".tar.gz", ".tgz", + ".tar.xz", ".txz", + ".tar.bz2", ".tbz2", + ".tar.lz4", ".tlz4", + ".tar.zst", ".tzst", + + ".tar.Z", ".taz", + ".tar.lz", + ".tar.lzma", ".tlz", + ".tar.lrz", ".tlrz", + ".tar.lzo", ".tzo" +#endif +}; // AAAAAAA static bool FileExtensionInList(const QString& filename, const QStringList& extensions, Qt::CaseSensitivity cs = Qt::CaseInsensitive) @@ -197,37 +222,44 @@ static void signalHandler(int) } #endif -MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) + +MainWindow::MainWindow(int id, EmuInstance* inst, QWidget* parent) : + QMainWindow(parent), + windowID(id), + emuInstance(inst), + globalCfg(inst->globalCfg), + localCfg(inst->localCfg), + windowCfg(localCfg.GetTable("Window"+std::to_string(id), "Window0")), + emuThread(inst->getEmuThread()) { #ifndef _WIN32 - if (socketpair(AF_UNIX, SOCK_STREAM, 0, signalFd)) + if (!parent) { - qFatal("Couldn't create socketpair"); - } + if (socketpair(AF_UNIX, SOCK_STREAM, 0, signalFd)) + { + qFatal("Couldn't create socketpair"); + } - signalSn = new QSocketNotifier(signalFd[1], QSocketNotifier::Read, this); - connect(signalSn, SIGNAL(activated(int)), this, SLOT(onQuit())); + signalSn = new QSocketNotifier(signalFd[1], QSocketNotifier::Read, this); + connect(signalSn, SIGNAL(activated(int)), this, SLOT(onQuit())); - struct sigaction sa; + struct sigaction sa; - sa.sa_handler = signalHandler; - sigemptyset(&sa.sa_mask); - sa.sa_flags = 0; - sa.sa_flags |= SA_RESTART; - sigaction(SIGINT, &sa, 0); + sa.sa_handler = signalHandler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sa.sa_flags |= SA_RESTART; + sigaction(SIGINT, &sa, 0); + } #endif - oldW = Config::WindowWidth; - oldH = Config::WindowHeight; - oldMax = Config::WindowMaximized; + showOSD = windowCfg.GetBool("ShowOSD"); setWindowTitle("melonDS " MELONDS_VERSION); setAttribute(Qt::WA_DeleteOnClose); setAcceptDrops(true); setFocusPolicy(Qt::ClickFocus); - int inst = Platform::InstanceID(); - QMenuBar* menubar = new QMenuBar(); { QMenu* menu = menubar->addMenu("File"); @@ -241,9 +273,11 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) actOpenROMArchive->setShortcut(QKeySequence(Qt::Key_O | Qt::CTRL | Qt::SHIFT));*/ recentMenu = menu->addMenu("Open recent"); - for (int i = 0; i < 10; ++i) + Config::Array recentROMs = globalCfg.GetArray("RecentROM"); + int numrecent = std::min(kMaxRecentROMs, (int)recentROMs.Size()); + for (int i = 0; i < numrecent; ++i) { - std::string item = Config::RecentROMList[i]; + std::string item = recentROMs.GetString(i); if (!item.empty()) recentFileList.push_back(QString::fromStdString(item)); } @@ -255,7 +289,7 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) menu->addSeparator(); - actCurrentCart = menu->addAction("DS slot: " + ROMManager::CartLabel()); + actCurrentCart = menu->addAction("DS slot: " + emuInstance->cartLabel()); actCurrentCart->setEnabled(false); actInsertCart = menu->addAction("Insert cart..."); @@ -266,7 +300,7 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) menu->addSeparator(); - actCurrentGBACart = menu->addAction("GBA slot: " + ROMManager::GBACartLabel()); + actCurrentGBACart = menu->addAction("GBA slot: " + emuInstance->gbaCartLabel()); actCurrentGBACart->setEnabled(false); actInsertGBACart = menu->addAction("Insert ROM cart..."); @@ -452,7 +486,7 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) QMenu* submenu = menu->addMenu("Screen rotation"); grpScreenRotation = new QActionGroup(submenu); - for (int i = 0; i < Frontend::screenRot_MAX; i++) + for (int i = 0; i < screenRot_MAX; i++) { int data = i*90; actScreenRotation[i] = submenu->addAction(QString("%1°").arg(data)); @@ -486,7 +520,7 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) const char* screenlayout[] = {"Natural", "Vertical", "Horizontal", "Hybrid"}; - for (int i = 0; i < Frontend::screenLayout_MAX; i++) + for (int i = 0; i < screenLayout_MAX; i++) { actScreenLayout[i] = submenu->addAction(QString(screenlayout[i])); actScreenLayout[i]->setActionGroup(grpScreenLayout); @@ -508,7 +542,7 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) const char* screensizing[] = {"Even", "Emphasize top", "Emphasize bottom", "Auto", "Top only", "Bottom only"}; - for (int i = 0; i < Frontend::screenSizing_MAX; i++) + for (int i = 0; i < screenSizing_MAX; i++) { actScreenSizing[i] = submenu->addAction(QString(screensizing[i])); actScreenSizing[i]->setActionGroup(grpScreenSizing); @@ -557,6 +591,13 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) } } + if (parentWidget() != nullptr) // TEST + { + QMenu* menu = menubar->addMenu("Test"); + + menu->addAction("Test"); + } + actScreenFiltering = menu->addAction("Screen filtering"); actScreenFiltering->setCheckable(true); connect(actScreenFiltering, &QAction::triggered, this, &MainWindow::onChangeScreenFiltering); @@ -577,9 +618,7 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) } setMenuBar(menubar); - resize(Config::WindowWidth, Config::WindowHeight); - - if (Config::FirmwareUsername == "Arisotura") + if (localCfg.GetString("Firmware.Username") == "Arisotura") actMPNewInstance->setText("Fart"); #ifdef Q_OS_MAC @@ -589,17 +628,22 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) move(frameGeo.topLeft()); #endif - if (oldMax) - showMaximized(); - else - show(); + std::string geom = windowCfg.GetString("Geometry"); + if (!geom.empty()) + { + QByteArray raw = QByteArray::fromStdString(geom); + QByteArray dec = QByteArray::fromBase64(raw, QByteArray::Base64Encoding | QByteArray::AbortOnBase64DecodingErrors); + if (!dec.isEmpty()) + restoreGeometry(dec); + } + show(); createScreenPanel(); actEjectCart->setEnabled(false); actEjectGBACart->setEnabled(false); - if (Config::ConsoleType == 1) + if (globalCfg.GetInt("Emu.ConsoleType") == 1) { actInsertGBACart->setEnabled(false); for (int i = 0; i < 1; i++) @@ -623,47 +667,50 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) actPowerManagement->setEnabled(false); actSetupCheats->setEnabled(false); - actTitleManager->setEnabled(!Config::DSiNANDPath.empty()); + actTitleManager->setEnabled(!globalCfg.GetString("DSi.NANDPath").empty()); - actEnableCheats->setChecked(Config::EnableCheats); + actEnableCheats->setChecked(localCfg.GetBool("EnableCheats")); actROMInfo->setEnabled(false); actRAMInfo->setEnabled(false); - actSavestateSRAMReloc->setChecked(Config::SavestateRelocSRAM); + actSavestateSRAMReloc->setChecked(globalCfg.GetBool("Savestate.RelocSRAM")); - actScreenRotation[Config::ScreenRotation]->setChecked(true); + actScreenRotation[windowCfg.GetInt("ScreenRotation")]->setChecked(true); + int screenGap = windowCfg.GetInt("ScreenGap"); for (int i = 0; i < 6; i++) { - if (actScreenGap[i]->data().toInt() == Config::ScreenGap) + if (actScreenGap[i]->data().toInt() == screenGap) { actScreenGap[i]->setChecked(true); break; } } - actScreenLayout[Config::ScreenLayout]->setChecked(true); - actScreenSizing[Config::ScreenSizing]->setChecked(true); - actIntegerScaling->setChecked(Config::IntegerScaling); + actScreenLayout[windowCfg.GetInt("ScreenLayout")]->setChecked(true); + actScreenSizing[windowCfg.GetInt("ScreenSizing")]->setChecked(true); + actIntegerScaling->setChecked(windowCfg.GetBool("IntegerScaling")); - actScreenSwap->setChecked(Config::ScreenSwap); + actScreenSwap->setChecked(windowCfg.GetBool("ScreenSwap")); + int aspectTop = windowCfg.GetInt("ScreenAspectTop"); + int aspectBot = windowCfg.GetInt("ScreenAspectBot"); for (int i = 0; i < AspectRatiosNum; i++) { - if (Config::ScreenAspectTop == aspectRatios[i].id) + if (aspectTop == aspectRatios[i].id) actScreenAspectTop[i]->setChecked(true); - if (Config::ScreenAspectBot == aspectRatios[i].id) + if (aspectBot == aspectRatios[i].id) actScreenAspectBot[i]->setChecked(true); } - actScreenFiltering->setChecked(Config::ScreenFilter); - actShowOSD->setChecked(Config::ShowOSD); + actScreenFiltering->setChecked(windowCfg.GetBool("ScreenFilter")); + actShowOSD->setChecked(showOSD); - actLimitFramerate->setChecked(Config::LimitFPS); - actAudioSync->setChecked(Config::AudioSync); + actLimitFramerate->setChecked(emuInstance->doLimitFPS); + actAudioSync->setChecked(emuInstance->doAudioSync); - if (inst > 0) + if (emuInstance->instanceID > 0) { actEmuSettings->setEnabled(false); actVideoSettings->setEnabled(false); @@ -675,6 +722,9 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) actPreferences->setEnabled(false); #endif // __APPLE__ } + + QObject::connect(qApp, &QApplication::applicationStateChanged, this, &MainWindow::onAppStateChanged); + onUpdateInterfaceSettings(); } MainWindow::~MainWindow() @@ -683,35 +733,42 @@ MainWindow::~MainWindow() delete[] actScreenAspectBot; } -void MainWindow::osdAddMessage(unsigned int color, const char* fmt, ...) +void MainWindow::osdAddMessage(unsigned int color, const char* msg) { - if (fmt == nullptr) - return; - - char msg[256]; - va_list args; - va_start(args, fmt); - vsnprintf(msg, 256, fmt, args); - va_end(args); - + if (!showOSD) return; panel->osdAddMessage(color, msg); } void MainWindow::closeEvent(QCloseEvent* event) { - if (hasOGL) + QByteArray geom = saveGeometry(); + QByteArray enc = geom.toBase64(QByteArray::Base64Encoding); + windowCfg.SetString("Geometry", enc.toStdString()); + + Config::Save(); + + if (hasOGL && (windowID == 0)) { // we intentionally don't unpause here emuThread->emuPause(); emuThread->deinitContext(); } + emuThread->detachWindow(this); + + if (windowID == 0) + { + int inst = emuInstance->instanceID; + deleteEmuInstance(inst); + } + QMainWindow::closeEvent(event); } void MainWindow::createScreenPanel() { - hasOGL = (Config::ScreenUseGL != 0) || (Config::_3DRenderer != 0); + hasOGL = globalCfg.GetBool("Screen.UseGL") || + (globalCfg.GetInt("3D.Renderer") != renderer3D_Software); if (hasOGL) { @@ -732,7 +789,7 @@ void MainWindow::createScreenPanel() setCentralWidget(panel); actScreenFiltering->setEnabled(hasOGL); - panel->osdSetEnabled(Config::ShowOSD); + panel->osdSetEnabled(showOSD); connect(this, SIGNAL(screenLayoutChange()), panel, SLOT(onScreenLayoutChanged())); emit screenLayoutChange(); @@ -746,7 +803,7 @@ GL::Context* MainWindow::getOGLContext() return glpanel->getContext(); } -/*void MainWindow::initOpenGL() +void MainWindow::initOpenGL() { if (!hasOGL) return; @@ -762,43 +819,28 @@ void MainWindow::deinitOpenGL() return glpanel->deinitOpenGL(); } -void MainWindow::drawScreenGL() +void MainWindow::setGLSwapInterval(int intv) { if (!hasOGL) return; ScreenPanelGL* glpanel = static_cast(panel); - return glpanel->drawScreenGL(); -}*/ + return glpanel->setSwapInterval(intv); +} -void MainWindow::resizeEvent(QResizeEvent* event) +void MainWindow::makeCurrentGL() { - int w = event->size().width(); - int h = event->size().height(); - - if (!isFullScreen()) - { - // this is ugly - // thing is, when maximizing the window, we first receive the resizeEvent - // with a new size matching the screen, then the changeEvent telling us that - // the maximized flag was updated - oldW = Config::WindowWidth; - oldH = Config::WindowHeight; - oldMax = isMaximized(); + if (!hasOGL) return; - Config::WindowWidth = w; - Config::WindowHeight = h; - } + ScreenPanelGL* glpanel = static_cast(panel); + return glpanel->makeCurrentGL(); } -void MainWindow::changeEvent(QEvent* event) +void MainWindow::drawScreenGL() { - if (isMaximized() && !oldMax) - { - Config::WindowWidth = oldW; - Config::WindowHeight = oldH; - } + if (!hasOGL) return; - Config::WindowMaximized = isMaximized() ? 1:0; + ScreenPanelGL* glpanel = static_cast(panel); + return glpanel->drawScreenGL(); } void MainWindow::keyPressEvent(QKeyEvent* event) @@ -808,14 +850,14 @@ void MainWindow::keyPressEvent(QKeyEvent* event) // TODO!! REMOVE ME IN RELEASE BUILDS!! //if (event->key() == Qt::Key_F11) emuThread->NDS->debug(0); - Input::KeyPress(event); + emuInstance->onKeyPress(event); } void MainWindow::keyReleaseEvent(QKeyEvent* event) { if (event->isAutoRepeat()) return; - Input::KeyRelease(event); + emuInstance->onKeyRelease(event); } @@ -866,7 +908,7 @@ void MainWindow::dropEvent(QDropEvent* event) if (isNdsRom) { - if (!ROMManager::LoadROM(mainWindow, emuThread, file, true)) + if (!emuInstance->loadROM(file, true)) { emuThread->emuUnpause(); return; @@ -877,15 +919,15 @@ void MainWindow::dropEvent(QDropEvent* event) recentFileList.prepend(barredFilename); updateRecentFilesMenu(); - assert(emuThread->NDS != nullptr); - emuThread->NDS->Start(); + assert(emuInstance->nds != nullptr); + emuInstance->nds->Start(); emuThread->emuRun(); updateCartInserted(false); } else if (isGbaRom) { - if (!ROMManager::LoadGBAROM(mainWindow, *emuThread->NDS, file)) + if (!emuInstance->loadGBAROM(file)) { emuThread->emuUnpause(); return; @@ -905,32 +947,32 @@ void MainWindow::dropEvent(QDropEvent* event) void MainWindow::focusInEvent(QFocusEvent* event) { - AudioInOut::AudioMute(mainWindow); + emuInstance->audioMute(); } void MainWindow::focusOutEvent(QFocusEvent* event) { - AudioInOut::AudioMute(mainWindow); + emuInstance->audioMute(); } void MainWindow::onAppStateChanged(Qt::ApplicationState state) { if (state == Qt::ApplicationInactive) { - Input::KeyReleaseAll(); - if (Config::PauseLostFocus && emuThread->emuIsRunning()) + emuInstance->keyReleaseAll(); + if (pauseOnLostFocus && emuThread->emuIsRunning()) emuThread->emuPause(); } else if (state == Qt::ApplicationActive) { - if (Config::PauseLostFocus && !pausedManually) + if (pauseOnLostFocus && !pausedManually) emuThread->emuUnpause(); } } bool MainWindow::verifySetup() { - QString res = ROMManager::VerifySetup(); + QString res = emuInstance->verifySetup(); if (!res.isEmpty()) { QMessageBox::critical(this, "melonDS", res); @@ -950,7 +992,7 @@ bool MainWindow::preloadROMs(QStringList file, QStringList gbafile, bool boot) bool gbaloaded = false; if (!gbafile.isEmpty()) { - if (!ROMManager::LoadGBAROM(mainWindow, *emuThread->NDS, gbafile)) return false; + if (!emuInstance->loadGBAROM(gbafile)) return false; gbaloaded = true; } @@ -958,7 +1000,7 @@ bool MainWindow::preloadROMs(QStringList file, QStringList gbafile, bool boot) bool ndsloaded = false; if (!file.isEmpty()) { - if (!ROMManager::LoadROM(mainWindow, emuThread, file, true)) return false; + if (!emuInstance->loadROM(file, true)) return false; recentFileList.removeAll(file.join("|")); recentFileList.prepend(file.join("|")); @@ -970,7 +1012,7 @@ bool MainWindow::preloadROMs(QStringList file, QStringList gbafile, bool boot) { if (ndsloaded) { - emuThread->NDS->Start(); + emuInstance->nds->Start(); emuThread->emuRun(); } else @@ -1115,13 +1157,13 @@ QStringList MainWindow::pickROM(bool gba) const QString filename = QFileDialog::getOpenFileName( this, "Open " + console + " ROM", - QString::fromStdString(Config::LastROMFolder), + globalCfg.GetQString("LastROMFolder"), "All supported files (*" + allROMs + ")" + extraFilters ); if (filename.isEmpty()) return {}; - Config::LastROMFolder = QFileInfo(filename).dir().path().toStdString(); + globalCfg.SetQString("LastROMFolder", QFileInfo(filename).dir().path()); return splitArchivePath(filename, false); } @@ -1130,14 +1172,14 @@ void MainWindow::updateCartInserted(bool gba) bool inserted; if (gba) { - inserted = ROMManager::GBACartInserted() && (Config::ConsoleType == 0); - actCurrentGBACart->setText("GBA slot: " + ROMManager::GBACartLabel()); + inserted = emuInstance->gbaCartInserted() && (globalCfg.GetInt("Emu.ConsoleType") == 0); + actCurrentGBACart->setText("GBA slot: " + emuInstance->gbaCartLabel()); actEjectGBACart->setEnabled(inserted); } else { - inserted = ROMManager::CartInserted(); - actCurrentCart->setText("DS slot: " + ROMManager::CartLabel()); + inserted = emuInstance->cartInserted(); + actCurrentCart->setText("DS slot: " + emuInstance->cartLabel()); actEjectCart->setEnabled(inserted); actImportSavefile->setEnabled(inserted); actSetupCheats->setEnabled(inserted); @@ -1163,7 +1205,7 @@ void MainWindow::onOpenFile() return; } - if (!ROMManager::LoadROM(mainWindow, emuThread, file, true)) + if (!emuInstance->loadROM(file, true)) { emuThread->emuUnpause(); return; @@ -1174,8 +1216,8 @@ void MainWindow::onOpenFile() recentFileList.prepend(filename); updateRecentFilesMenu(); - assert(emuThread->NDS != nullptr); - emuThread->NDS->Start(); + assert(emuInstance->nds != nullptr); + emuInstance->nds->Start(); emuThread->emuRun(); updateCartInserted(false); @@ -1184,8 +1226,7 @@ void MainWindow::onOpenFile() void MainWindow::onClearRecentFiles() { recentFileList.clear(); - for (int i = 0; i < 10; i++) - Config::RecentROMList[i] = ""; + globalCfg.GetArray("RecentROM").Clear(); updateRecentFilesMenu(); } @@ -1193,9 +1234,12 @@ void MainWindow::updateRecentFilesMenu() { recentMenu->clear(); + Config::Array recentroms = globalCfg.GetArray("RecentROM"); + recentroms.Clear(); + for (int i = 0; i < recentFileList.size(); ++i) { - if (i >= 10) break; + if (i >= kMaxRecentROMs) break; QString item_full = recentFileList.at(i); QString item_display = item_full; @@ -1223,7 +1267,7 @@ void MainWindow::updateRecentFilesMenu() actRecentFile_i->setData(item_full); connect(actRecentFile_i, &QAction::triggered, this, &MainWindow::onClickRecentFile); - Config::RecentROMList[i] = recentFileList.at(i).toStdString(); + recentroms.SetQString(i, recentFileList.at(i)); } while (recentFileList.size() > 10) @@ -1260,7 +1304,7 @@ void MainWindow::onClickRecentFile() return; } - if (!ROMManager::LoadROM(mainWindow, emuThread, file, true)) + if (!emuInstance->loadROM(file, true)) { emuThread->emuUnpause(); return; @@ -1270,8 +1314,8 @@ void MainWindow::onClickRecentFile() recentFileList.prepend(filename); updateRecentFilesMenu(); - assert(emuThread->NDS != nullptr); - emuThread->NDS->Start(); + assert(emuInstance->nds != nullptr); + emuInstance->nds->Start(); emuThread->emuRun(); updateCartInserted(false); @@ -1287,7 +1331,7 @@ void MainWindow::onBootFirmware() return; } - if (!ROMManager::BootToMenu(emuThread)) + if (!emuInstance->bootToMenu()) { // TODO: better error reporting? QMessageBox::critical(this, "melonDS", "This firmware is not bootable."); @@ -1295,8 +1339,8 @@ void MainWindow::onBootFirmware() return; } - assert(emuThread->NDS != nullptr); - emuThread->NDS->Start(); + assert(emuInstance->nds != nullptr); + emuInstance->nds->Start(); emuThread->emuRun(); } @@ -1311,7 +1355,7 @@ void MainWindow::onInsertCart() return; } - if (!ROMManager::LoadROM(mainWindow, emuThread, file, false)) + if (!emuInstance->loadROM(file, false)) { emuThread->emuUnpause(); return; @@ -1326,7 +1370,7 @@ void MainWindow::onEjectCart() { emuThread->emuPause(); - ROMManager::EjectCart(*emuThread->NDS); + emuInstance->ejectCart(); emuThread->emuUnpause(); @@ -1344,7 +1388,7 @@ void MainWindow::onInsertGBACart() return; } - if (!ROMManager::LoadGBAROM(mainWindow, *emuThread->NDS, file)) + if (!emuInstance->loadGBAROM(file)) { emuThread->emuUnpause(); return; @@ -1362,7 +1406,7 @@ void MainWindow::onInsertGBAAddon() emuThread->emuPause(); - ROMManager::LoadGBAAddon(*emuThread->NDS, type); + emuInstance->loadGBAAddon(type); emuThread->emuUnpause(); @@ -1373,7 +1417,7 @@ void MainWindow::onEjectGBACart() { emuThread->emuPause(); - ROMManager::EjectGBACart(*emuThread->NDS); + emuInstance->ejectGBACart(); emuThread->emuUnpause(); @@ -1389,14 +1433,14 @@ void MainWindow::onSaveState() std::string filename; if (slot > 0) { - filename = ROMManager::GetSavestateName(slot); + filename = emuInstance->getSavestateName(slot); } else { // TODO: specific 'last directory' for savestate files? QString qfilename = QFileDialog::getSaveFileName(this, "Save state", - QString::fromStdString(Config::LastROMFolder), + globalCfg.GetQString("LastROMFolder"), "melonDS savestates (*.mln);;Any file (*.*)"); if (qfilename.isEmpty()) { @@ -1407,16 +1451,16 @@ void MainWindow::onSaveState() filename = qfilename.toStdString(); } - if (ROMManager::SaveState(*emuThread->NDS, filename)) + if (emuInstance->saveState(filename)) { - if (slot > 0) osdAddMessage(0, "State saved to slot %d", slot); - else osdAddMessage(0, "State saved to file"); + if (slot > 0) emuInstance->osdAddMessage(0, "State saved to slot %d", slot); + else emuInstance->osdAddMessage(0, "State saved to file"); actLoadState[slot]->setEnabled(true); } else { - osdAddMessage(0xFFA0A0, "State save failed"); + emuInstance->osdAddMessage(0xFFA0A0, "State save failed"); } emuThread->emuUnpause(); @@ -1431,14 +1475,14 @@ void MainWindow::onLoadState() std::string filename; if (slot > 0) { - filename = ROMManager::GetSavestateName(slot); + filename = emuInstance->getSavestateName(slot); } else { // TODO: specific 'last directory' for savestate files? QString qfilename = QFileDialog::getOpenFileName(this, "Load state", - QString::fromStdString(Config::LastROMFolder), + globalCfg.GetQString("LastROMFolder"), "melonDS savestates (*.ml*);;Any file (*.*)"); if (qfilename.isEmpty()) { @@ -1451,23 +1495,23 @@ void MainWindow::onLoadState() if (!Platform::FileExists(filename)) { - if (slot > 0) osdAddMessage(0xFFA0A0, "State slot %d is empty", slot); - else osdAddMessage(0xFFA0A0, "State file does not exist"); + if (slot > 0) emuInstance->osdAddMessage(0xFFA0A0, "State slot %d is empty", slot); + else emuInstance->osdAddMessage(0xFFA0A0, "State file does not exist"); emuThread->emuUnpause(); return; } - if (ROMManager::LoadState(*emuThread->NDS, filename)) + if (emuInstance->loadState(filename)) { - if (slot > 0) osdAddMessage(0, "State loaded from slot %d", slot); - else osdAddMessage(0, "State loaded from file"); + if (slot > 0) emuInstance->osdAddMessage(0, "State loaded from slot %d", slot); + else emuInstance->osdAddMessage(0, "State loaded from file"); actUndoStateLoad->setEnabled(true); } else { - osdAddMessage(0xFFA0A0, "State load failed"); + emuInstance->osdAddMessage(0xFFA0A0, "State load failed"); } emuThread->emuUnpause(); @@ -1476,10 +1520,10 @@ void MainWindow::onLoadState() void MainWindow::onUndoStateLoad() { emuThread->emuPause(); - ROMManager::UndoStateLoad(*emuThread->NDS); + emuInstance->undoStateLoad(); emuThread->emuUnpause(); - osdAddMessage(0, "State load undone"); + emuInstance->osdAddMessage(0, "State load undone"); } void MainWindow::onImportSavefile() @@ -1487,7 +1531,7 @@ void MainWindow::onImportSavefile() emuThread->emuPause(); QString path = QFileDialog::getOpenFileName(this, "Select savefile", - QString::fromStdString(Config::LastROMFolder), + globalCfg.GetQString("LastROMFolder"), "Savefiles (*.sav *.bin *.dsv);;Any file (*.*)"); if (path.isEmpty()) @@ -1504,7 +1548,7 @@ void MainWindow::onImportSavefile() return; } - if (RunningSomething) + if (emuThread->emuIsActive()) { if (QMessageBox::warning(this, "melonDS", @@ -1515,7 +1559,7 @@ void MainWindow::onImportSavefile() return; } - ROMManager::Reset(emuThread); + emuInstance->reset(); } u32 len = FileLength(f); @@ -1524,8 +1568,8 @@ void MainWindow::onImportSavefile() Platform::FileRewind(f); Platform::FileRead(data.get(), len, 1, f); - assert(emuThread->NDS != nullptr); - emuThread->NDS->SetNDSSave(data.get(), len); + assert(emuInstance->nds != nullptr); + emuInstance->nds->SetNDSSave(data.get(), len); CloseFile(f); emuThread->emuUnpause(); @@ -1534,55 +1578,46 @@ void MainWindow::onImportSavefile() void MainWindow::onQuit() { #ifndef _WIN32 - signalSn->setEnabled(false); + if (!parentWidget()) + signalSn->setEnabled(false); #endif - QApplication::quit(); + close(); } void MainWindow::onPause(bool checked) { - if (!RunningSomething) return; + if (!emuThread->emuIsActive()) return; if (checked) { emuThread->emuPause(); - osdAddMessage(0, "Paused"); pausedManually = true; } else { emuThread->emuUnpause(); - osdAddMessage(0, "Resumed"); pausedManually = false; } } void MainWindow::onReset() { - if (!RunningSomething) return; + if (!emuThread->emuIsActive()) return; - emuThread->emuPause(); - - actUndoStateLoad->setEnabled(false); - - ROMManager::Reset(emuThread); - - osdAddMessage(0, "Reset"); - emuThread->emuRun(); + emuThread->emuReset(); } void MainWindow::onStop() { - if (!RunningSomething) return; + if (!emuThread->emuIsActive()) return; - emuThread->emuPause(); - emuThread->NDS->Stop(); + emuThread->emuStop(true); } void MainWindow::onFrameStep() { - if (!RunningSomething) return; + if (!emuThread->emuIsActive()) return; emuThread->emuFrameStep(); } @@ -1594,13 +1629,13 @@ void MainWindow::onOpenDateTime() void MainWindow::onOpenPowerManagement() { - PowerManagementDialog* dlg = PowerManagementDialog::openDlg(this, emuThread); + PowerManagementDialog* dlg = PowerManagementDialog::openDlg(this); } void MainWindow::onEnableCheats(bool checked) { - Config::EnableCheats = checked?1:0; - ROMManager::EnableCheats(*emuThread->NDS, Config::EnableCheats != 0); + localCfg.SetBool("EnableCheats", checked); + emuInstance->enableCheats(checked); } void MainWindow::onSetupCheats() @@ -1618,14 +1653,14 @@ void MainWindow::onCheatsDialogFinished(int res) void MainWindow::onROMInfo() { - auto cart = emuThread->NDS->NDSCartSlot.GetCart(); + auto cart = emuInstance->nds->NDSCartSlot.GetCart(); if (cart) - ROMInfoDialog* dlg = ROMInfoDialog::openDlg(this, *cart); + ROMInfoDialog* dlg = ROMInfoDialog::openDlg(this); } void MainWindow::onRAMInfo() { - RAMInfoDialog* dlg = RAMInfoDialog::openDlg(this, emuThread); + RAMInfoDialog* dlg = RAMInfoDialog::openDlg(this); } void MainWindow::onOpenTitleManager() @@ -1635,19 +1670,7 @@ void MainWindow::onOpenTitleManager() 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(); + createEmuInstance(); } void MainWindow::onOpenEmuSettings() @@ -1660,9 +1683,7 @@ void MainWindow::onOpenEmuSettings() void MainWindow::onEmuSettingsDialogFinished(int res) { - emuThread->emuUnpause(); - - if (Config::ConsoleType == 1) + if (globalCfg.GetInt("Emu.ConsoleType") == 1) { actInsertGBACart->setEnabled(false); for (int i = 0; i < 1; i++) @@ -1674,16 +1695,18 @@ void MainWindow::onEmuSettingsDialogFinished(int res) actInsertGBACart->setEnabled(true); for (int i = 0; i < 1; i++) actInsertGBAAddon[i]->setEnabled(true); - actEjectGBACart->setEnabled(ROMManager::GBACartInserted()); + actEjectGBACart->setEnabled(emuInstance->gbaCartInserted()); } if (EmuSettingsDialog::needsReset) onReset(); - actCurrentGBACart->setText("GBA slot: " + ROMManager::GBACartLabel()); + actCurrentGBACart->setText("GBA slot: " + emuInstance->gbaCartLabel()); - if (!RunningSomething) - actTitleManager->setEnabled(!Config::DSiNANDPath.empty()); + if (!emuThread->emuIsActive()) + actTitleManager->setEnabled(!globalCfg.GetString("DSi.NANDPath").empty()); + + emuThread->emuUnpause(); } void MainWindow::onOpenInputConfig() @@ -1728,9 +1751,10 @@ void MainWindow::onCameraSettingsFinished(int res) void MainWindow::onOpenAudioSettings() { - AudioSettingsDialog* dlg = AudioSettingsDialog::openDlg(this, emuThread->emuIsActive(), emuThread); + AudioSettingsDialog* dlg = AudioSettingsDialog::openDlg(this); connect(emuThread, &EmuThread::syncVolumeLevel, dlg, &AudioSettingsDialog::onSyncVolumeLevel); connect(emuThread, &EmuThread::windowEmuStart, dlg, &AudioSettingsDialog::onConsoleReset); + connect(dlg, &AudioSettingsDialog::updateAudioVolume, this, &MainWindow::onUpdateAudioVolume); connect(dlg, &AudioSettingsDialog::updateAudioSettings, this, &MainWindow::onUpdateAudioSettings); connect(dlg, &AudioSettingsDialog::finished, this, &MainWindow::onAudioSettingsFinished); } @@ -1767,20 +1791,29 @@ void MainWindow::onPathSettingsFinished(int res) emuThread->emuUnpause(); } +void MainWindow::onUpdateAudioVolume(int vol, int dsisync) +{ + emuInstance->audioVolume = vol; + emuInstance->audioDSiVolumeSync = dsisync; +} + void MainWindow::onUpdateAudioSettings() { - assert(emuThread->NDS != nullptr); - emuThread->NDS->SPU.SetInterpolation(static_cast(Config::AudioInterp)); + assert(emuInstance->nds != nullptr); + + int interp = globalCfg.GetInt("Audio.Interpolation"); + emuInstance->nds->SPU.SetInterpolation(static_cast(interp)); - if (Config::AudioBitDepth == 0) - emuThread->NDS->SPU.SetDegrade10Bit(emuThread->NDS->ConsoleType == 0); + int bitdepth = globalCfg.GetInt("Audio.BitDepth"); + if (bitdepth == 0) + emuInstance->nds->SPU.SetDegrade10Bit(emuInstance->nds->ConsoleType == 0); else - emuThread->NDS->SPU.SetDegrade10Bit(Config::AudioBitDepth == 1); + emuInstance->nds->SPU.SetDegrade10Bit(bitdepth == 1); } void MainWindow::onAudioSettingsFinished(int res) { - AudioInOut::UpdateSettings(*emuThread->NDS); + //AudioInOut::UpdateSettings(*emuThread->NDS); } void MainWindow::onOpenMPSettings() @@ -1793,8 +1826,9 @@ void MainWindow::onOpenMPSettings() void MainWindow::onMPSettingsFinished(int res) { - AudioInOut::AudioMute(mainWindow); - LocalMP::SetRecvTimeout(Config::MPRecvTimeout); + emuInstance->mpAudioMode = globalCfg.GetInt("MP.AudioMode"); + emuInstance->audioMute(); + LocalMP::SetRecvTimeout(globalCfg.GetInt("MP.RecvTimeout")); emuThread->emuUnpause(); } @@ -1809,9 +1843,6 @@ void MainWindow::onOpenWifiSettings() void MainWindow::onWifiSettingsFinished(int res) { - Platform::LAN_DeInit(); - Platform::LAN_Init(); - if (WifiSettingsDialog::needsReset) onReset(); @@ -1823,12 +1854,16 @@ void MainWindow::onOpenInterfaceSettings() emuThread->emuPause(); InterfaceSettingsDialog* dlg = InterfaceSettingsDialog::openDlg(this); connect(dlg, &InterfaceSettingsDialog::finished, this, &MainWindow::onInterfaceSettingsFinished); - connect(dlg, &InterfaceSettingsDialog::updateMouseTimer, this, &MainWindow::onUpdateMouseTimer); + connect(dlg, &InterfaceSettingsDialog::updateInterfaceSettings, this, &MainWindow::onUpdateInterfaceSettings); } -void MainWindow::onUpdateMouseTimer() +void MainWindow::onUpdateInterfaceSettings() { - panel->mouseTimer->setInterval(Config::MouseHideSeconds*1000); + pauseOnLostFocus = globalCfg.GetBool("PauseLostFocus"); + emuInstance->maxFPS = globalCfg.GetInt("MaxFPS"); + + panel->setMouseHide(globalCfg.GetBool("MouseHide"), + globalCfg.GetInt("MouseHideSeconds")*1000); } void MainWindow::onInterfaceSettingsFinished(int res) @@ -1838,7 +1873,7 @@ void MainWindow::onInterfaceSettingsFinished(int res) void MainWindow::onChangeSavestateSRAMReloc(bool checked) { - Config::SavestateRelocSRAM = checked?1:0; + globalCfg.SetBool("Savestate.RelocSRAM", checked); } void MainWindow::onChangeScreenSize() @@ -1851,7 +1886,7 @@ void MainWindow::onChangeScreenSize() void MainWindow::onChangeScreenRotation(QAction* act) { int rot = act->data().toInt(); - Config::ScreenRotation = rot; + windowCfg.SetInt("ScreenRotation", rot); emit screenLayoutChange(); } @@ -1859,7 +1894,7 @@ void MainWindow::onChangeScreenRotation(QAction* act) void MainWindow::onChangeScreenGap(QAction* act) { int gap = act->data().toInt(); - Config::ScreenGap = gap; + windowCfg.SetInt("ScreenGap", gap); emit screenLayoutChange(); } @@ -1867,30 +1902,32 @@ void MainWindow::onChangeScreenGap(QAction* act) void MainWindow::onChangeScreenLayout(QAction* act) { int layout = act->data().toInt(); - Config::ScreenLayout = layout; + windowCfg.SetInt("ScreenLayout", layout); emit screenLayoutChange(); } void MainWindow::onChangeScreenSwap(bool checked) { - Config::ScreenSwap = checked?1:0; + windowCfg.SetBool("ScreenSwap", checked); // Swap between top and bottom screen when displaying one screen. - if (Config::ScreenSizing == Frontend::screenSizing_TopOnly) + int sizing = windowCfg.GetInt("ScreenSizing"); + if (sizing == screenSizing_TopOnly) { // Bottom Screen. - Config::ScreenSizing = Frontend::screenSizing_BotOnly; - actScreenSizing[Frontend::screenSizing_TopOnly]->setChecked(false); - actScreenSizing[Config::ScreenSizing]->setChecked(true); + sizing = screenSizing_BotOnly; + actScreenSizing[screenSizing_TopOnly]->setChecked(false); + actScreenSizing[sizing]->setChecked(true); } - else if (Config::ScreenSizing == Frontend::screenSizing_BotOnly) + else if (sizing == screenSizing_BotOnly) { // Top Screen. - Config::ScreenSizing = Frontend::screenSizing_TopOnly; - actScreenSizing[Frontend::screenSizing_BotOnly]->setChecked(false); - actScreenSizing[Config::ScreenSizing]->setChecked(true); + sizing = screenSizing_TopOnly; + actScreenSizing[screenSizing_BotOnly]->setChecked(false); + actScreenSizing[sizing]->setChecked(true); } + windowCfg.SetInt("ScreenSizing", sizing); emit screenLayoutChange(); } @@ -1898,7 +1935,7 @@ void MainWindow::onChangeScreenSwap(bool checked) void MainWindow::onChangeScreenSizing(QAction* act) { int sizing = act->data().toInt(); - Config::ScreenSizing = sizing; + windowCfg.SetInt("ScreenSizing", sizing); emit screenLayoutChange(); } @@ -1910,11 +1947,11 @@ void MainWindow::onChangeScreenAspect(QAction* act) if (group == grpScreenAspectTop) { - Config::ScreenAspectTop = aspect; + windowCfg.SetInt("ScreenAspectTop", aspect); } else { - Config::ScreenAspectBot = aspect; + windowCfg.SetInt("ScreenAspectBot", aspect); } emit screenLayoutChange(); @@ -1922,32 +1959,36 @@ void MainWindow::onChangeScreenAspect(QAction* act) void MainWindow::onChangeIntegerScaling(bool checked) { - Config::IntegerScaling = checked?1:0; + windowCfg.SetBool("IntegerScaling", checked); emit screenLayoutChange(); } void MainWindow::onChangeScreenFiltering(bool checked) { - Config::ScreenFilter = checked?1:0; + windowCfg.SetBool("ScreenFilter", checked); - emit screenLayoutChange(); + //emit screenLayoutChange(); + panel->setFilter(checked); } void MainWindow::onChangeShowOSD(bool checked) { - Config::ShowOSD = checked?1:0; - panel->osdSetEnabled(Config::ShowOSD); + showOSD = checked; + panel->osdSetEnabled(showOSD); + windowCfg.SetBool("ShowOSD", showOSD); } void MainWindow::onChangeLimitFramerate(bool checked) { - Config::LimitFPS = checked?1:0; + emuInstance->doLimitFPS = checked; + globalCfg.SetBool("LimitFPS", emuInstance->doLimitFPS); } void MainWindow::onChangeAudioSync(bool checked) { - Config::AudioSync = checked?1:0; + emuInstance->doAudioSync = checked; + globalCfg.SetBool("AudioSync", emuInstance->doAudioSync); } @@ -1978,15 +2019,16 @@ void MainWindow::onFullscreenToggled() void MainWindow::onScreenEmphasisToggled() { - int currentSizing = Config::ScreenSizing; - if (currentSizing == Frontend::screenSizing_EmphTop) + int currentSizing = windowCfg.GetInt("ScreenSizing"); + if (currentSizing == screenSizing_EmphTop) { - Config::ScreenSizing = Frontend::screenSizing_EmphBot; + currentSizing = screenSizing_EmphBot; } - else if (currentSizing == Frontend::screenSizing_EmphBot) + else if (currentSizing == screenSizing_EmphBot) { - Config::ScreenSizing = Frontend::screenSizing_EmphTop; + currentSizing = screenSizing_EmphTop; } + windowCfg.SetInt("ScreenSizing", currentSizing); emit screenLayoutChange(); } @@ -1996,7 +2038,7 @@ void MainWindow::onEmuStart() for (int i = 1; i < 9; i++) { actSaveState[i]->setEnabled(true); - actLoadState[i]->setEnabled(ROMManager::SavestateExists(i)); + actLoadState[i]->setEnabled(emuInstance->savestateExists(i)); } actSaveState[0]->setEnabled(true); actLoadState[0]->setEnabled(true); @@ -2016,8 +2058,6 @@ void MainWindow::onEmuStart() void MainWindow::onEmuStop() { - emuThread->emuPause(); - for (int i = 0; i < 9; i++) { actSaveState[i]->setEnabled(false); @@ -2033,7 +2073,17 @@ void MainWindow::onEmuStop() actDateTime->setEnabled(true); actPowerManagement->setEnabled(false); - actTitleManager->setEnabled(!Config::DSiNANDPath.empty()); + actTitleManager->setEnabled(!globalCfg.GetString("DSi.NANDPath").empty()); +} + +void MainWindow::onEmuPause(bool pause) +{ + actPause->setChecked(pause); +} + +void MainWindow::onEmuReset() +{ + actUndoStateLoad->setEnabled(false); } void MainWindow::onUpdateVideoSettings(bool glchange) @@ -2048,8 +2098,7 @@ void MainWindow::onUpdateVideoSettings(bool glchange) connect(emuThread, SIGNAL(windowUpdate()), panel, SLOT(repaint())); } - printf("update video settings\n"); - videoSettingsDirty = true; + emuThread->updateVideoSettings(); if (glchange) { diff --git a/src/frontend/qt_sdl/Window.h b/src/frontend/qt_sdl/Window.h index bc207480c0..389599fb2f 100644 --- a/src/frontend/qt_sdl/Window.h +++ b/src/frontend/qt_sdl/Window.h @@ -20,7 +20,7 @@ #define WINDOW_H #include "glad/glad.h" -#include "FrontendUtil.h" +#include "ScreenLayout.h" #include "duckstation/gl/context.h" #include @@ -34,10 +34,14 @@ #include #include "Screen.h" +#include "Config.h" +class EmuInstance; class EmuThread; +const int kMaxRecentROMs = 10; + /* class WindowBase : public QMainWindow { @@ -100,26 +104,28 @@ class MainWindow : public QMainWindow Q_OBJECT public: - explicit MainWindow(QWidget* parent = nullptr); + explicit MainWindow(int id, EmuInstance* inst, QWidget* parent = nullptr); ~MainWindow(); - bool hasOGL; + EmuInstance* getEmuInstance() { return emuInstance; } + Config::Table& getWindowConfig() { return windowCfg; } + + bool hasOpenGL() { return hasOGL; } GL::Context* getOGLContext(); - /*void initOpenGL(); + void initOpenGL(); void deinitOpenGL(); - void drawScreenGL();*/ + void setGLSwapInterval(int intv); + void makeCurrentGL(); + void drawScreenGL(); bool preloadROMs(QStringList file, QStringList gbafile, bool boot); QStringList splitArchivePath(const QString& filename, bool useMemberSyntax); void onAppStateChanged(Qt::ApplicationState state); - void osdAddMessage(unsigned int color, const char* fmt, ...); + void osdAddMessage(unsigned int color, const char* msg); protected: - void resizeEvent(QResizeEvent* event) override; - void changeEvent(QEvent* event) override; - void keyPressEvent(QKeyEvent* event) override; void keyReleaseEvent(QKeyEvent* event) override; @@ -170,6 +176,7 @@ private slots: void onOpenCameraSettings(); void onCameraSettingsFinished(int res); void onOpenAudioSettings(); + void onUpdateAudioVolume(int vol, int dsisync); void onUpdateAudioSettings(); void onAudioSettingsFinished(int res); void onOpenMPSettings(); @@ -182,7 +189,7 @@ private slots: void onPathSettingsFinished(int res); void onOpenInterfaceSettings(); void onInterfaceSettingsFinished(int res); - void onUpdateMouseTimer(); + void onUpdateInterfaceSettings(); void onChangeSavestateSRAMReloc(bool checked); void onChangeScreenSize(); void onChangeScreenRotation(QAction* act); @@ -201,6 +208,8 @@ private slots: void onEmuStart(); void onEmuStop(); + void onEmuPause(bool pause); + void onEmuReset(); void onUpdateVideoSettings(bool glchange); @@ -223,10 +232,21 @@ private slots: void createScreenPanel(); - bool pausedManually = false; + bool showOSD; - int oldW, oldH; - bool oldMax; + bool hasOGL; + + bool pauseOnLostFocus; + bool pausedManually; + + int windowID; + + EmuInstance* emuInstance; + EmuThread* emuThread; + + Config::Table& globalCfg; + Config::Table& localCfg; + Config::Table windowCfg; public: ScreenPanel* panel; @@ -275,14 +295,14 @@ private slots: QAction* actSavestateSRAMReloc; QAction* actScreenSize[4]; QActionGroup* grpScreenRotation; - QAction* actScreenRotation[Frontend::screenRot_MAX]; + QAction* actScreenRotation[screenRot_MAX]; QActionGroup* grpScreenGap; QAction* actScreenGap[6]; QActionGroup* grpScreenLayout; - QAction* actScreenLayout[Frontend::screenLayout_MAX]; + QAction* actScreenLayout[screenLayout_MAX]; QAction* actScreenSwap; QActionGroup* grpScreenSizing; - QAction* actScreenSizing[Frontend::screenSizing_MAX]; + QAction* actScreenSizing[screenSizing_MAX]; QAction* actIntegerScaling; QActionGroup* grpScreenAspectTop; QAction** actScreenAspectTop; diff --git a/src/frontend/qt_sdl/main.cpp b/src/frontend/qt_sdl/main.cpp index 54ade11931..2f54132431 100644 --- a/src/frontend/qt_sdl/main.cpp +++ b/src/frontend/qt_sdl/main.cpp @@ -39,6 +39,7 @@ #include #include #include +#include #ifndef _WIN32 #include #include @@ -56,207 +57,118 @@ #include "duckstation/gl/context.h" #include "main.h" -#include "Input.h" #include "CheatsDialog.h" #include "DateTimeDialog.h" #include "EmuSettingsDialog.h" #include "InputConfig/InputConfigDialog.h" #include "VideoSettingsDialog.h" -#include "CameraSettingsDialog.h" -#include "AudioSettingsDialog.h" -#include "FirmwareSettingsDialog.h" -#include "PathSettingsDialog.h" -#include "MPSettingsDialog.h" -#include "WifiSettingsDialog.h" -#include "InterfaceSettingsDialog.h" #include "ROMInfoDialog.h" #include "RAMInfoDialog.h" -#include "TitleManagerDialog.h" #include "PowerManagement/PowerManagementDialog.h" -#include "AudioInOut.h" -#include "types.h" #include "version.h" -#include "FrontendUtil.h" - -#include "Args.h" -#include "NDS.h" -#include "NDSCart.h" -#include "GBACart.h" -#include "GPU.h" -#include "SPU.h" -#include "Wifi.h" -#include "Platform.h" -#include "LocalMP.h" #include "Config.h" -#include "RTC.h" #include "DSi.h" -#include "DSi_I2C.h" -#include "GPU3D_Soft.h" -#include "GPU3D_OpenGL.h" - -#include "Savestate.h" - -//#include "main_shaders.h" -#include "ROMManager.h" +#include "EmuInstance.h" #include "ArchiveUtil.h" #include "CameraManager.h" +#include "LocalMP.h" +#include "Net.h" #include "CLI.h" -// TODO: uniform variable spelling using namespace melonDS; -QString NdsRomMimeType = "application/x-nintendo-ds-rom"; -QStringList NdsRomExtensions { ".nds", ".srl", ".dsi", ".ids" }; - -QString GbaRomMimeType = "application/x-gba-rom"; -QStringList GbaRomExtensions { ".gba", ".agb" }; QString* systemThemeName; -// This list of supported archive formats is based on libarchive(3) version 3.6.2 (2022-12-09). -QStringList ArchiveMimeTypes -{ -#ifdef ARCHIVE_SUPPORT_ENABLED - "application/zip", - "application/x-7z-compressed", - "application/vnd.rar", // *.rar - "application/x-tar", - - "application/x-compressed-tar", // *.tar.gz - "application/x-xz-compressed-tar", - "application/x-bzip-compressed-tar", - "application/x-lz4-compressed-tar", - "application/x-zstd-compressed-tar", - - "application/x-tarz", // *.tar.Z - "application/x-lzip-compressed-tar", - "application/x-lzma-compressed-tar", - "application/x-lrzip-compressed-tar", - "application/x-tzo", // *.tar.lzo -#endif -}; -QStringList ArchiveExtensions -{ -#ifdef ARCHIVE_SUPPORT_ENABLED - ".zip", ".7z", ".rar", ".tar", - - ".tar.gz", ".tgz", - ".tar.xz", ".txz", - ".tar.bz2", ".tbz2", - ".tar.lz4", ".tlz4", - ".tar.zst", ".tzst", - - ".tar.Z", ".taz", - ".tar.lz", - ".tar.lzma", ".tlz", - ".tar.lrz", ".tlrz", - ".tar.lzo", ".tzo" -#endif -}; - - -bool RunningSomething; -MainWindow* mainWindow; -EmuThread* emuThread; +QString emuDirectory; -int autoScreenSizing = 0; - -int videoRenderer; -bool videoSettingsDirty; +const int kMaxEmuInstances = 16; +EmuInstance* emuInstances[kMaxEmuInstances]; CameraManager* camManager[2]; bool camStarted[2]; -//extern int AspectRatiosNum; - - -static bool FileExtensionInList(const QString& filename, const QStringList& extensions, Qt::CaseSensitivity cs = Qt::CaseInsensitive) -{ - return std::any_of(extensions.cbegin(), extensions.cend(), [&](const auto& ext) { - return filename.endsWith(ext, cs); - }); -} - -static bool MimeTypeInList(const QMimeType& mimetype, const QStringList& superTypeNames) -{ - return std::any_of(superTypeNames.cbegin(), superTypeNames.cend(), [&](const auto& superTypeName) { - return mimetype.inherits(superTypeName); - }); -} - -static bool NdsRomByExtension(const QString& filename) +bool createEmuInstance() { - return FileExtensionInList(filename, NdsRomExtensions); -} - -static bool GbaRomByExtension(const QString& filename) -{ - return FileExtensionInList(filename, GbaRomExtensions); -} + int id = -1; + for (int i = 0; i < kMaxEmuInstances; i++) + { + if (!emuInstances[i]) + { + id = i; + break; + } + } -static bool SupportedArchiveByExtension(const QString& filename) -{ - return FileExtensionInList(filename, ArchiveExtensions); -} + if (id == -1) + return false; + auto inst = new EmuInstance(id); + emuInstances[id] = inst; -static bool NdsRomByMimetype(const QMimeType& mimetype) -{ - return mimetype.inherits(NdsRomMimeType); + return true; } -static bool GbaRomByMimetype(const QMimeType& mimetype) +void deleteEmuInstance(int id) { - return mimetype.inherits(GbaRomMimeType); -} + auto inst = emuInstances[id]; + if (!inst) return; -static bool SupportedArchiveByMimetype(const QMimeType& mimetype) -{ - return MimeTypeInList(mimetype, ArchiveMimeTypes); + delete inst; + emuInstances[id] = nullptr; } -static bool ZstdNdsRomByExtension(const QString& filename) +void deleteAllEmuInstances() { - return filename.endsWith(".zst", Qt::CaseInsensitive) && - NdsRomByExtension(filename.left(filename.size() - 4)); + for (int i = 0; i < kMaxEmuInstances; i++) + deleteEmuInstance(i); } -static bool ZstdGbaRomByExtension(const QString& filename) -{ - return filename.endsWith(".zst", Qt::CaseInsensitive) && - GbaRomByExtension(filename.left(filename.size() - 4)); -} -static bool FileIsSupportedFiletype(const QString& filename, bool insideArchive = false) +void pathInit() { - if (ZstdNdsRomByExtension(filename) || ZstdGbaRomByExtension(filename)) - return true; - - if (NdsRomByExtension(filename) || GbaRomByExtension(filename) || SupportedArchiveByExtension(filename)) - return true; + // First, check for the portable directory next to the executable. + QString appdirpath = QCoreApplication::applicationDirPath(); + QString portablepath = appdirpath + QDir::separator() + "portable"; + +#if defined(__APPLE__) + // On Apple platforms we may need to navigate outside an app bundle. + // The executable directory would be "melonDS.app/Contents/MacOS", so we need to go a total of three steps up. + QDir bundledir(appdirpath); + if (bundledir.cd("..") && bundledir.cd("..") && bundledir.dirName().endsWith(".app") && bundledir.cd("..")) + { + portablepath = bundledir.absolutePath() + QDir::separator() + "portable"; + } +#endif - const auto matchmode = insideArchive ? QMimeDatabase::MatchExtension : QMimeDatabase::MatchDefault; - const QMimeType mimetype = QMimeDatabase().mimeTypeForFile(filename, matchmode); - return NdsRomByMimetype(mimetype) || GbaRomByMimetype(mimetype) || SupportedArchiveByMimetype(mimetype); + QDir portabledir(portablepath); + if (portabledir.exists()) + { + emuDirectory = portabledir.absolutePath(); + } + else + { + // If no overrides are specified, use the default path. +#if defined(__WIN32__) && defined(WIN32_PORTABLE) + emuDirectory = appdirpath; +#else + QString confdir; + QDir config(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation)); + config.mkdir("melonDS"); + confdir = config.absolutePath() + QDir::separator() + "melonDS"; + emuDirectory = confdir; +#endif + } } - - -void emuStop() -{ - RunningSomething = false; - - emit emuThread->windowEmuStop(); -} - MelonApplication::MelonApplication(int& argc, char** argv) : QApplication(argc, argv) { @@ -268,16 +180,21 @@ MelonApplication::MelonApplication(int& argc, char** argv) #endif } +// TODO: ROM loading should be moved to EmuInstance +// especially so the preloading below and in main() can be done in a nicer fashion + bool MelonApplication::event(QEvent *event) { if (event->type() == QEvent::FileOpen) { + EmuInstance* inst = emuInstances[0]; + MainWindow* win = inst->getMainWindow(); QFileOpenEvent *openEvent = static_cast(event); - emuThread->emuPause(); - const QStringList file = mainWindow->splitArchivePath(openEvent->file(), true); - if (!mainWindow->preloadROMs(file, {}, true)) - emuThread->emuUnpause(); + inst->getEmuThread()->emuPause(); + const QStringList file = win->splitArchivePath(openEvent->file(), true); + if (!win->preloadROMs(file, {}, true)) + inst->getEmuThread()->emuUnpause(); } return QApplication::event(event); @@ -287,6 +204,9 @@ int main(int argc, char** argv) { srand(time(nullptr)); + for (int i = 0; i < kMaxEmuInstances; i++) + emuInstances[i] = nullptr; + qputenv("QT_SCALE_FACTOR", "1"); #if QT_VERSION_MAJOR == 6 && defined(__WIN32__) @@ -302,8 +222,7 @@ int main(int argc, char** argv) printf("did you just call me a derp???\n"); MelonApplication melon(argc, argv); - - Platform::Init(argc, argv); + pathInit(); CLI::CommandLineOptions* options = CLI::ManageArgs(melon); @@ -324,7 +243,7 @@ int main(int argc, char** argv) QString errorStr = "Failed to initialize SDL. This could indicate an issue with your audio driver.\n\nThe error was: "; errorStr += err; - QMessageBox::critical(NULL, "melonDS", errorStr); + QMessageBox::critical(nullptr, "melonDS", errorStr); return 1; } @@ -333,98 +252,74 @@ int main(int argc, char** argv) SDL_InitSubSystem(SDL_INIT_VIDEO); SDL_EnableScreenSaver(); SDL_DisableScreenSaver(); - if (!Config::Load()) QMessageBox::critical(NULL, "melonDS", "Unable to write to config.\nPlease check the write permissions of the folder you placed melonDS in."); - -#define SANITIZE(var, min, max) { var = std::clamp(var, min, max); } - SANITIZE(Config::ConsoleType, 0, 1); -#ifdef OGLRENDERER_ENABLED - SANITIZE(Config::_3DRenderer, 0, renderer3D_Max); -#else - SANITIZE(Config::_3DRenderer, 0, 0); -#endif - SANITIZE(Config::ScreenVSyncInterval, 1, 20); - SANITIZE(Config::GL_ScaleFactor, 1, 16); - SANITIZE(Config::AudioInterp, 0, 4); - SANITIZE(Config::AudioVolume, 0, 256); - SANITIZE(Config::MicInputType, 0, (int)micInputType_MAX); - SANITIZE(Config::ScreenRotation, 0, (int)Frontend::screenRot_MAX); - SANITIZE(Config::ScreenGap, 0, 500); - SANITIZE(Config::ScreenLayout, 0, (int)Frontend::screenLayout_MAX); - SANITIZE(Config::ScreenSizing, 0, (int)Frontend::screenSizing_MAX); - SANITIZE(Config::ScreenAspectTop, 0, AspectRatiosNum); - SANITIZE(Config::ScreenAspectBot, 0, AspectRatiosNum); -#undef SANITIZE + if (!Config::Load()) + QMessageBox::critical(nullptr, + "melonDS", + "Unable to write to config.\nPlease check the write permissions of the folder you placed melonDS in."); camStarted[0] = false; camStarted[1] = false; camManager[0] = new CameraManager(0, 640, 480, true); camManager[1] = new CameraManager(1, 640, 480, true); - camManager[0]->setXFlip(Config::Camera[0].XFlip); - camManager[1]->setXFlip(Config::Camera[1].XFlip); systemThemeName = new QString(QApplication::style()->objectName()); - if (!Config::UITheme.empty()) { - QApplication::setStyle(QString::fromStdString(Config::UITheme)); + Config::Table cfg = Config::GetGlobalTable(); + QString uitheme = cfg.GetQString("UITheme"); + if (!uitheme.isEmpty()) + { + QApplication::setStyle(uitheme); + } } - Input::JoystickID = Config::JoystickID; - Input::OpenJoystick(); - - mainWindow = new MainWindow(); - if (options->fullscreen) - ToggleFullscreen(mainWindow); + LocalMP::Init(); + Net::Init(); - emuThread = new EmuThread(); - emuThread->start(); - emuThread->emuPause(); + createEmuInstance(); - AudioInOut::Init(emuThread); - ROMManager::EnableCheats(*emuThread->NDS, Config::EnableCheats != 0); - AudioInOut::AudioMute(mainWindow); - - QObject::connect(&melon, &QApplication::applicationStateChanged, mainWindow, &MainWindow::onAppStateChanged); - - bool memberSyntaxUsed = false; - const auto prepareRomPath = [&](const std::optional& romPath, const std::optional& romArchivePath) -> QStringList { - if (!romPath.has_value()) - return {}; + MainWindow* win = emuInstances[0]->getMainWindow(); + bool memberSyntaxUsed = false; + const auto prepareRomPath = [&](const std::optional &romPath, + const std::optional &romArchivePath) -> QStringList + { + if (!romPath.has_value()) + return {}; - if (romArchivePath.has_value()) - return { *romPath, *romArchivePath }; + if (romArchivePath.has_value()) + return {*romPath, *romArchivePath}; - const QStringList path = mainWindow->splitArchivePath(*romPath, true); - if (path.size() > 1) memberSyntaxUsed = true; - return path; - }; + const QStringList path = win->splitArchivePath(*romPath, true); + if (path.size() > 1) memberSyntaxUsed = true; + return path; + }; - const QStringList dsfile = prepareRomPath(options->dsRomPath, options->dsRomArchivePath); - const QStringList gbafile = prepareRomPath(options->gbaRomPath, options->gbaRomArchivePath); + const QStringList dsfile = prepareRomPath(options->dsRomPath, options->dsRomArchivePath); + const QStringList gbafile = prepareRomPath(options->gbaRomPath, options->gbaRomArchivePath); - if (memberSyntaxUsed) printf("Warning: use the a.zip|b.nds format at your own risk!\n"); + if (memberSyntaxUsed) printf("Warning: use the a.zip|b.nds format at your own risk!\n"); - mainWindow->preloadROMs(dsfile, gbafile, options->boot); + win->preloadROMs(dsfile, gbafile, options->boot); + } int ret = melon.exec(); delete options; - emuThread->emuStop(); - emuThread->wait(); - delete emuThread; + // if we get here, all the existing emu instances should have been deleted already + // but with this we make extra sure they are all deleted + deleteAllEmuInstances(); - Input::CloseJoystick(); + LocalMP::DeInit(); + Net::DeInit(); - AudioInOut::DeInit(); delete camManager[0]; delete camManager[1]; Config::Save(); SDL_Quit(); - Platform::DeInit(); return ret; } diff --git a/src/frontend/qt_sdl/main.h b/src/frontend/qt_sdl/main.h index 5751f22931..77ee517e7d 100644 --- a/src/frontend/qt_sdl/main.h +++ b/src/frontend/qt_sdl/main.h @@ -31,9 +31,10 @@ #include #include +#include "EmuInstance.h" #include "Window.h" #include "EmuThread.h" -#include "FrontendUtil.h" +#include "ScreenLayout.h" class MelonApplication : public QApplication { @@ -45,5 +46,10 @@ class MelonApplication : public QApplication }; extern QString* systemThemeName; +extern QString emuDirectory; + +bool createEmuInstance(); +void deleteEmuInstance(int id); +void deleteAllEmuInstances(); #endif // MAIN_H diff --git a/src/frontend/qt_sdl/toml/toml.hpp b/src/frontend/qt_sdl/toml/toml.hpp new file mode 100644 index 0000000000..6b0ca1ed4d --- /dev/null +++ b/src/frontend/qt_sdl/toml/toml.hpp @@ -0,0 +1,38 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2017 Toru Niina + * + * 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. + */ + +#ifndef TOML_FOR_MODERN_CPP +#define TOML_FOR_MODERN_CPP + +#define TOML11_VERSION_MAJOR 3 +#define TOML11_VERSION_MINOR 7 +#define TOML11_VERSION_PATCH 1 + +#include "toml/parser.hpp" +#include "toml/literal.hpp" +#include "toml/serializer.hpp" +#include "toml/get.hpp" +#include "toml/macros.hpp" + +#endif// TOML_FOR_MODERN_CPP diff --git a/src/frontend/qt_sdl/toml/toml/color.hpp b/src/frontend/qt_sdl/toml/toml/color.hpp new file mode 100644 index 0000000000..4cb572cb08 --- /dev/null +++ b/src/frontend/qt_sdl/toml/toml/color.hpp @@ -0,0 +1,64 @@ +#ifndef TOML11_COLOR_HPP +#define TOML11_COLOR_HPP +#include +#include + +#ifdef TOML11_COLORIZE_ERROR_MESSAGE +#define TOML11_ERROR_MESSAGE_COLORIZED true +#else +#define TOML11_ERROR_MESSAGE_COLORIZED false +#endif + +namespace toml +{ + +// put ANSI escape sequence to ostream +namespace color_ansi +{ +namespace detail +{ +inline int colorize_index() +{ + static const int index = std::ios_base::xalloc(); + return index; +} +} // detail + +inline std::ostream& colorize(std::ostream& os) +{ + // by default, it is zero. + os.iword(detail::colorize_index()) = 1; + return os; +} +inline std::ostream& nocolorize(std::ostream& os) +{ + os.iword(detail::colorize_index()) = 0; + return os; +} +inline std::ostream& reset (std::ostream& os) +{if(os.iword(detail::colorize_index()) == 1) {os << "\033[00m";} return os;} +inline std::ostream& bold (std::ostream& os) +{if(os.iword(detail::colorize_index()) == 1) {os << "\033[01m";} return os;} +inline std::ostream& grey (std::ostream& os) +{if(os.iword(detail::colorize_index()) == 1) {os << "\033[30m";} return os;} +inline std::ostream& red (std::ostream& os) +{if(os.iword(detail::colorize_index()) == 1) {os << "\033[31m";} return os;} +inline std::ostream& green (std::ostream& os) +{if(os.iword(detail::colorize_index()) == 1) {os << "\033[32m";} return os;} +inline std::ostream& yellow (std::ostream& os) +{if(os.iword(detail::colorize_index()) == 1) {os << "\033[33m";} return os;} +inline std::ostream& blue (std::ostream& os) +{if(os.iword(detail::colorize_index()) == 1) {os << "\033[34m";} return os;} +inline std::ostream& magenta(std::ostream& os) +{if(os.iword(detail::colorize_index()) == 1) {os << "\033[35m";} return os;} +inline std::ostream& cyan (std::ostream& os) +{if(os.iword(detail::colorize_index()) == 1) {os << "\033[36m";} return os;} +inline std::ostream& white (std::ostream& os) +{if(os.iword(detail::colorize_index()) == 1) {os << "\033[37m";} return os;} +} // color_ansi + +// ANSI escape sequence is the only and default colorization method currently +namespace color = color_ansi; + +} // toml +#endif// TOML11_COLOR_HPP diff --git a/src/frontend/qt_sdl/toml/toml/combinator.hpp b/src/frontend/qt_sdl/toml/toml/combinator.hpp new file mode 100644 index 0000000000..33ecca1eb5 --- /dev/null +++ b/src/frontend/qt_sdl/toml/toml/combinator.hpp @@ -0,0 +1,306 @@ +// Copyright Toru Niina 2017. +// Distributed under the MIT License. +#ifndef TOML11_COMBINATOR_HPP +#define TOML11_COMBINATOR_HPP +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "region.hpp" +#include "result.hpp" +#include "traits.hpp" +#include "utility.hpp" + +// they scans characters and returns region if it matches to the condition. +// when they fail, it does not change the location. +// in lexer.hpp, these are used. + +namespace toml +{ +namespace detail +{ + +// to output character as an error message. +inline std::string show_char(const char c) +{ + // It suppresses an error that occurs only in Debug mode of MSVC++ on Windows. + // I'm not completely sure but they check the value of char to be in the + // range [0, 256) and some of the COMPLETELY VALID utf-8 character sometimes + // has negative value (if char has sign). So here it re-interprets c as + // unsigned char through pointer. In general, converting pointer to a + // pointer that has different type cause UB, but `(signed|unsigned)?char` + // are one of the exceptions. Converting pointer only to char and std::byte + // (c++17) are valid. + if(std::isgraph(*reinterpret_cast(std::addressof(c)))) + { + return std::string(1, c); + } + else + { + std::array buf; + buf.fill('\0'); + const auto r = std::snprintf( + buf.data(), buf.size(), "0x%02x", static_cast(c) & 0xFF); + (void) r; // Unused variable warning + assert(r == static_cast(buf.size()) - 1); + return std::string(buf.data()); + } +} + +template +struct character +{ + static constexpr char target = C; + + static result + invoke(location& loc) + { + if(loc.iter() == loc.end()) {return none();} + const auto first = loc.iter(); + + const char c = *(loc.iter()); + if(c != target) + { + return none(); + } + loc.advance(); // update location + + return ok(region(loc, first, loc.iter())); + } +}; +template +constexpr char character::target; + +// closed interval [Low, Up]. both Low and Up are included. +template +struct in_range +{ + // assuming ascii part of UTF-8... + static_assert(Low <= Up, "lower bound should be less than upper bound."); + + static constexpr char upper = Up; + static constexpr char lower = Low; + + static result + invoke(location& loc) + { + if(loc.iter() == loc.end()) {return none();} + const auto first = loc.iter(); + + const char c = *(loc.iter()); + if(c < lower || upper < c) + { + return none(); + } + + loc.advance(); + return ok(region(loc, first, loc.iter())); + } +}; +template constexpr char in_range::upper; +template constexpr char in_range::lower; + +// keep iterator if `Combinator` matches. otherwise, increment `iter` by 1 char. +// for detecting invalid characters, like control sequences in toml string. +template +struct exclude +{ + static result + invoke(location& loc) + { + if(loc.iter() == loc.end()) {return none();} + auto first = loc.iter(); + + auto rslt = Combinator::invoke(loc); + if(rslt.is_ok()) + { + loc.reset(first); + return none(); + } + loc.reset(std::next(first)); // XXX maybe loc.advance() is okay but... + return ok(region(loc, first, loc.iter())); + } +}; + +// increment `iter`, if matches. otherwise, just return empty string. +template +struct maybe +{ + static result + invoke(location& loc) + { + const auto rslt = Combinator::invoke(loc); + if(rslt.is_ok()) + { + return rslt; + } + return ok(region(loc)); + } +}; + +template +struct sequence; + +template +struct sequence +{ + static result + invoke(location& loc) + { + const auto first = loc.iter(); + auto rslt = Head::invoke(loc); + if(rslt.is_err()) + { + loc.reset(first); + return none(); + } + return sequence::invoke(loc, std::move(rslt.unwrap()), first); + } + + // called from the above function only, recursively. + template + static result + invoke(location& loc, region reg, Iterator first) + { + const auto rslt = Head::invoke(loc); + if(rslt.is_err()) + { + loc.reset(first); + return none(); + } + reg += rslt.unwrap(); // concat regions + return sequence::invoke(loc, std::move(reg), first); + } +}; + +template +struct sequence +{ + // would be called from sequence::invoke only. + template + static result + invoke(location& loc, region reg, Iterator first) + { + const auto rslt = Head::invoke(loc); + if(rslt.is_err()) + { + loc.reset(first); + return none(); + } + reg += rslt.unwrap(); // concat regions + return ok(reg); + } +}; + +template +struct either; + +template +struct either +{ + static result + invoke(location& loc) + { + const auto rslt = Head::invoke(loc); + if(rslt.is_ok()) {return rslt;} + return either::invoke(loc); + } +}; +template +struct either +{ + static result + invoke(location& loc) + { + return Head::invoke(loc); + } +}; + +template +struct repeat; + +template struct exactly{}; +template struct at_least{}; +struct unlimited{}; + +template +struct repeat> +{ + static result + invoke(location& loc) + { + region retval(loc); + const auto first = loc.iter(); + for(std::size_t i=0; i +struct repeat> +{ + static result + invoke(location& loc) + { + region retval(loc); + + const auto first = loc.iter(); + for(std::size_t i=0; i +struct repeat +{ + static result + invoke(location& loc) + { + region retval(loc); + while(true) + { + auto rslt = T::invoke(loc); + if(rslt.is_err()) + { + return ok(std::move(retval)); + } + retval += rslt.unwrap(); + } + } +}; + +} // detail +} // toml +#endif// TOML11_COMBINATOR_HPP diff --git a/src/frontend/qt_sdl/toml/toml/comments.hpp b/src/frontend/qt_sdl/toml/toml/comments.hpp new file mode 100644 index 0000000000..ec25041175 --- /dev/null +++ b/src/frontend/qt_sdl/toml/toml/comments.hpp @@ -0,0 +1,472 @@ +// Copyright Toru Niina 2019. +// Distributed under the MIT License. +#ifndef TOML11_COMMENTS_HPP +#define TOML11_COMMENTS_HPP +#include +#include +#include +#include +#include +#include +#include + +#ifdef TOML11_PRESERVE_COMMENTS_BY_DEFAULT +# define TOML11_DEFAULT_COMMENT_STRATEGY ::toml::preserve_comments +#else +# define TOML11_DEFAULT_COMMENT_STRATEGY ::toml::discard_comments +#endif + +// This file provides mainly two classes, `preserve_comments` and `discard_comments`. +// Those two are a container that have the same interface as `std::vector` +// but bahaves in the opposite way. `preserve_comments` is just the same as +// `std::vector` and each `std::string` corresponds to a comment line. +// Conversely, `discard_comments` discards all the strings and ignores everything +// assigned in it. `discard_comments` is always empty and you will encounter an +// error whenever you access to the element. +namespace toml +{ +struct discard_comments; // forward decl + +// use it in the following way +// +// const toml::basic_value data = +// toml::parse("example.toml"); +// +// the interface is almost the same as std::vector. +struct preserve_comments +{ + // `container_type` is not provided in discard_comments. + // do not use this inner-type in a generic code. + using container_type = std::vector; + + using size_type = container_type::size_type; + using difference_type = container_type::difference_type; + using value_type = container_type::value_type; + using reference = container_type::reference; + using const_reference = container_type::const_reference; + using pointer = container_type::pointer; + using const_pointer = container_type::const_pointer; + using iterator = container_type::iterator; + using const_iterator = container_type::const_iterator; + using reverse_iterator = container_type::reverse_iterator; + using const_reverse_iterator = container_type::const_reverse_iterator; + + preserve_comments() = default; + ~preserve_comments() = default; + preserve_comments(preserve_comments const&) = default; + preserve_comments(preserve_comments &&) = default; + preserve_comments& operator=(preserve_comments const&) = default; + preserve_comments& operator=(preserve_comments &&) = default; + + explicit preserve_comments(const std::vector& c): comments(c){} + explicit preserve_comments(std::vector&& c) + : comments(std::move(c)) + {} + preserve_comments& operator=(const std::vector& c) + { + comments = c; + return *this; + } + preserve_comments& operator=(std::vector&& c) + { + comments = std::move(c); + return *this; + } + + explicit preserve_comments(const discard_comments&) {} + + explicit preserve_comments(size_type n): comments(n) {} + preserve_comments(size_type n, const std::string& x): comments(n, x) {} + preserve_comments(std::initializer_list x): comments(x) {} + template + preserve_comments(InputIterator first, InputIterator last) + : comments(first, last) + {} + + template + void assign(InputIterator first, InputIterator last) {comments.assign(first, last);} + void assign(std::initializer_list ini) {comments.assign(ini);} + void assign(size_type n, const std::string& val) {comments.assign(n, val);} + + // Related to the issue #97. + // + // It is known that `std::vector::insert` and `std::vector::erase` in + // the standard library implementation included in GCC 4.8.5 takes + // `std::vector::iterator` instead of `std::vector::const_iterator`. + // Because of the const-correctness, we cannot convert a `const_iterator` to + // an `iterator`. It causes compilation error in GCC 4.8.5. +#if defined(__GNUC__) && defined(__GNUC_MINOR__) && defined(__GNUC_PATCHLEVEL__) && !defined(__clang__) +# if (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) <= 40805 +# define TOML11_WORKAROUND_GCC_4_8_X_STANDARD_LIBRARY_IMPLEMENTATION +# endif +#endif + +#ifdef TOML11_WORKAROUND_GCC_4_8_X_STANDARD_LIBRARY_IMPLEMENTATION + iterator insert(iterator p, const std::string& x) + { + return comments.insert(p, x); + } + iterator insert(iterator p, std::string&& x) + { + return comments.insert(p, std::move(x)); + } + void insert(iterator p, size_type n, const std::string& x) + { + return comments.insert(p, n, x); + } + template + void insert(iterator p, InputIterator first, InputIterator last) + { + return comments.insert(p, first, last); + } + void insert(iterator p, std::initializer_list ini) + { + return comments.insert(p, ini); + } + + template + iterator emplace(iterator p, Ts&& ... args) + { + return comments.emplace(p, std::forward(args)...); + } + + iterator erase(iterator pos) {return comments.erase(pos);} + iterator erase(iterator first, iterator last) + { + return comments.erase(first, last); + } +#else + iterator insert(const_iterator p, const std::string& x) + { + return comments.insert(p, x); + } + iterator insert(const_iterator p, std::string&& x) + { + return comments.insert(p, std::move(x)); + } + iterator insert(const_iterator p, size_type n, const std::string& x) + { + return comments.insert(p, n, x); + } + template + iterator insert(const_iterator p, InputIterator first, InputIterator last) + { + return comments.insert(p, first, last); + } + iterator insert(const_iterator p, std::initializer_list ini) + { + return comments.insert(p, ini); + } + + template + iterator emplace(const_iterator p, Ts&& ... args) + { + return comments.emplace(p, std::forward(args)...); + } + + iterator erase(const_iterator pos) {return comments.erase(pos);} + iterator erase(const_iterator first, const_iterator last) + { + return comments.erase(first, last); + } +#endif + + void swap(preserve_comments& other) {comments.swap(other.comments);} + + void push_back(const std::string& v) {comments.push_back(v);} + void push_back(std::string&& v) {comments.push_back(std::move(v));} + void pop_back() {comments.pop_back();} + + template + void emplace_back(Ts&& ... args) {comments.emplace_back(std::forward(args)...);} + + void clear() {comments.clear();} + + size_type size() const noexcept {return comments.size();} + size_type max_size() const noexcept {return comments.max_size();} + size_type capacity() const noexcept {return comments.capacity();} + bool empty() const noexcept {return comments.empty();} + + void reserve(size_type n) {comments.reserve(n);} + void resize(size_type n) {comments.resize(n);} + void resize(size_type n, const std::string& c) {comments.resize(n, c);} + void shrink_to_fit() {comments.shrink_to_fit();} + + reference operator[](const size_type n) noexcept {return comments[n];} + const_reference operator[](const size_type n) const noexcept {return comments[n];} + reference at(const size_type n) {return comments.at(n);} + const_reference at(const size_type n) const {return comments.at(n);} + reference front() noexcept {return comments.front();} + const_reference front() const noexcept {return comments.front();} + reference back() noexcept {return comments.back();} + const_reference back() const noexcept {return comments.back();} + + pointer data() noexcept {return comments.data();} + const_pointer data() const noexcept {return comments.data();} + + iterator begin() noexcept {return comments.begin();} + iterator end() noexcept {return comments.end();} + const_iterator begin() const noexcept {return comments.begin();} + const_iterator end() const noexcept {return comments.end();} + const_iterator cbegin() const noexcept {return comments.cbegin();} + const_iterator cend() const noexcept {return comments.cend();} + + reverse_iterator rbegin() noexcept {return comments.rbegin();} + reverse_iterator rend() noexcept {return comments.rend();} + const_reverse_iterator rbegin() const noexcept {return comments.rbegin();} + const_reverse_iterator rend() const noexcept {return comments.rend();} + const_reverse_iterator crbegin() const noexcept {return comments.crbegin();} + const_reverse_iterator crend() const noexcept {return comments.crend();} + + friend bool operator==(const preserve_comments&, const preserve_comments&); + friend bool operator!=(const preserve_comments&, const preserve_comments&); + friend bool operator< (const preserve_comments&, const preserve_comments&); + friend bool operator<=(const preserve_comments&, const preserve_comments&); + friend bool operator> (const preserve_comments&, const preserve_comments&); + friend bool operator>=(const preserve_comments&, const preserve_comments&); + + friend void swap(preserve_comments&, std::vector&); + friend void swap(std::vector&, preserve_comments&); + + private: + + container_type comments; +}; + +inline bool operator==(const preserve_comments& lhs, const preserve_comments& rhs) {return lhs.comments == rhs.comments;} +inline bool operator!=(const preserve_comments& lhs, const preserve_comments& rhs) {return lhs.comments != rhs.comments;} +inline bool operator< (const preserve_comments& lhs, const preserve_comments& rhs) {return lhs.comments < rhs.comments;} +inline bool operator<=(const preserve_comments& lhs, const preserve_comments& rhs) {return lhs.comments <= rhs.comments;} +inline bool operator> (const preserve_comments& lhs, const preserve_comments& rhs) {return lhs.comments > rhs.comments;} +inline bool operator>=(const preserve_comments& lhs, const preserve_comments& rhs) {return lhs.comments >= rhs.comments;} + +inline void swap(preserve_comments& lhs, preserve_comments& rhs) +{ + lhs.swap(rhs); + return; +} +inline void swap(preserve_comments& lhs, std::vector& rhs) +{ + lhs.comments.swap(rhs); + return; +} +inline void swap(std::vector& lhs, preserve_comments& rhs) +{ + lhs.swap(rhs.comments); + return; +} + +template +std::basic_ostream& +operator<<(std::basic_ostream& os, const preserve_comments& com) +{ + for(const auto& c : com) + { + os << '#' << c << '\n'; + } + return os; +} + +namespace detail +{ + +// To provide the same interface with `preserve_comments`, `discard_comments` +// should have an iterator. But it does not contain anything, so we need to +// add an iterator that points nothing. +// +// It always points null, so DO NOT unwrap this iterator. It always crashes +// your program. +template +struct empty_iterator +{ + using value_type = T; + using reference_type = typename std::conditional::type; + using pointer_type = typename std::conditional::type; + using difference_type = std::ptrdiff_t; + using iterator_category = std::random_access_iterator_tag; + + empty_iterator() = default; + ~empty_iterator() = default; + empty_iterator(empty_iterator const&) = default; + empty_iterator(empty_iterator &&) = default; + empty_iterator& operator=(empty_iterator const&) = default; + empty_iterator& operator=(empty_iterator &&) = default; + + // DO NOT call these operators. + reference_type operator*() const noexcept {std::terminate();} + pointer_type operator->() const noexcept {return nullptr;} + reference_type operator[](difference_type) const noexcept {return this->operator*();} + + // These operators do nothing. + empty_iterator& operator++() noexcept {return *this;} + empty_iterator operator++(int) noexcept {return *this;} + empty_iterator& operator--() noexcept {return *this;} + empty_iterator operator--(int) noexcept {return *this;} + + empty_iterator& operator+=(difference_type) noexcept {return *this;} + empty_iterator& operator-=(difference_type) noexcept {return *this;} + + empty_iterator operator+(difference_type) const noexcept {return *this;} + empty_iterator operator-(difference_type) const noexcept {return *this;} +}; + +template +bool operator==(const empty_iterator&, const empty_iterator&) noexcept {return true;} +template +bool operator!=(const empty_iterator&, const empty_iterator&) noexcept {return false;} +template +bool operator< (const empty_iterator&, const empty_iterator&) noexcept {return false;} +template +bool operator<=(const empty_iterator&, const empty_iterator&) noexcept {return true;} +template +bool operator> (const empty_iterator&, const empty_iterator&) noexcept {return false;} +template +bool operator>=(const empty_iterator&, const empty_iterator&) noexcept {return true;} + +template +typename empty_iterator::difference_type +operator-(const empty_iterator&, const empty_iterator&) noexcept {return 0;} + +template +empty_iterator +operator+(typename empty_iterator::difference_type, const empty_iterator& rhs) noexcept {return rhs;} +template +empty_iterator +operator+(const empty_iterator& lhs, typename empty_iterator::difference_type) noexcept {return lhs;} + +} // detail + +// The default comment type. It discards all the comments. It requires only one +// byte to contain, so the memory footprint is smaller than preserve_comments. +// +// It just ignores `push_back`, `insert`, `erase`, and any other modifications. +// IT always returns size() == 0, the iterator taken by `begin()` is always the +// same as that of `end()`, and accessing through `operator[]` or iterators +// always causes a segmentation fault. DO NOT access to the element of this. +// +// Why this is chose as the default type is because the last version (2.x.y) +// does not contain any comments in a value. To minimize the impact on the +// efficiency, this is chosen as a default. +// +// To reduce the memory footprint, later we can try empty base optimization (EBO). +struct discard_comments +{ + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using value_type = std::string; + using reference = std::string&; + using const_reference = std::string const&; + using pointer = std::string*; + using const_pointer = std::string const*; + using iterator = detail::empty_iterator; + using const_iterator = detail::empty_iterator; + using reverse_iterator = detail::empty_iterator; + using const_reverse_iterator = detail::empty_iterator; + + discard_comments() = default; + ~discard_comments() = default; + discard_comments(discard_comments const&) = default; + discard_comments(discard_comments &&) = default; + discard_comments& operator=(discard_comments const&) = default; + discard_comments& operator=(discard_comments &&) = default; + + explicit discard_comments(const std::vector&) noexcept {} + explicit discard_comments(std::vector&&) noexcept {} + discard_comments& operator=(const std::vector&) noexcept {return *this;} + discard_comments& operator=(std::vector&&) noexcept {return *this;} + + explicit discard_comments(const preserve_comments&) noexcept {} + + explicit discard_comments(size_type) noexcept {} + discard_comments(size_type, const std::string&) noexcept {} + discard_comments(std::initializer_list) noexcept {} + template + discard_comments(InputIterator, InputIterator) noexcept {} + + template + void assign(InputIterator, InputIterator) noexcept {} + void assign(std::initializer_list) noexcept {} + void assign(size_type, const std::string&) noexcept {} + + iterator insert(const_iterator, const std::string&) {return iterator{};} + iterator insert(const_iterator, std::string&&) {return iterator{};} + iterator insert(const_iterator, size_type, const std::string&) {return iterator{};} + template + iterator insert(const_iterator, InputIterator, InputIterator) {return iterator{};} + iterator insert(const_iterator, std::initializer_list) {return iterator{};} + + template + iterator emplace(const_iterator, Ts&& ...) {return iterator{};} + iterator erase(const_iterator) {return iterator{};} + iterator erase(const_iterator, const_iterator) {return iterator{};} + + void swap(discard_comments&) {return;} + + void push_back(const std::string&) {return;} + void push_back(std::string&& ) {return;} + void pop_back() {return;} + + template + void emplace_back(Ts&& ...) {return;} + + void clear() {return;} + + size_type size() const noexcept {return 0;} + size_type max_size() const noexcept {return 0;} + size_type capacity() const noexcept {return 0;} + bool empty() const noexcept {return true;} + + void reserve(size_type) {return;} + void resize(size_type) {return;} + void resize(size_type, const std::string&) {return;} + void shrink_to_fit() {return;} + + // DO NOT access to the element of this container. This container is always + // empty, so accessing through operator[], front/back, data causes address + // error. + + reference operator[](const size_type) noexcept {return *data();} + const_reference operator[](const size_type) const noexcept {return *data();} + reference at(const size_type) {throw std::out_of_range("toml::discard_comment is always empty.");} + const_reference at(const size_type) const {throw std::out_of_range("toml::discard_comment is always empty.");} + reference front() noexcept {return *data();} + const_reference front() const noexcept {return *data();} + reference back() noexcept {return *data();} + const_reference back() const noexcept {return *data();} + + pointer data() noexcept {return nullptr;} + const_pointer data() const noexcept {return nullptr;} + + iterator begin() noexcept {return iterator{};} + iterator end() noexcept {return iterator{};} + const_iterator begin() const noexcept {return const_iterator{};} + const_iterator end() const noexcept {return const_iterator{};} + const_iterator cbegin() const noexcept {return const_iterator{};} + const_iterator cend() const noexcept {return const_iterator{};} + + reverse_iterator rbegin() noexcept {return iterator{};} + reverse_iterator rend() noexcept {return iterator{};} + const_reverse_iterator rbegin() const noexcept {return const_iterator{};} + const_reverse_iterator rend() const noexcept {return const_iterator{};} + const_reverse_iterator crbegin() const noexcept {return const_iterator{};} + const_reverse_iterator crend() const noexcept {return const_iterator{};} +}; + +inline bool operator==(const discard_comments&, const discard_comments&) noexcept {return true;} +inline bool operator!=(const discard_comments&, const discard_comments&) noexcept {return false;} +inline bool operator< (const discard_comments&, const discard_comments&) noexcept {return false;} +inline bool operator<=(const discard_comments&, const discard_comments&) noexcept {return true;} +inline bool operator> (const discard_comments&, const discard_comments&) noexcept {return false;} +inline bool operator>=(const discard_comments&, const discard_comments&) noexcept {return true;} + +inline void swap(const discard_comments&, const discard_comments&) noexcept {return;} + +template +std::basic_ostream& +operator<<(std::basic_ostream& os, const discard_comments&) +{ + return os; +} + +} // toml11 +#endif// TOML11_COMMENTS_HPP diff --git a/src/frontend/qt_sdl/toml/toml/datetime.hpp b/src/frontend/qt_sdl/toml/toml/datetime.hpp new file mode 100644 index 0000000000..36db1e101c --- /dev/null +++ b/src/frontend/qt_sdl/toml/toml/datetime.hpp @@ -0,0 +1,631 @@ +// Copyright Toru Niina 2017. +// Distributed under the MIT License. +#ifndef TOML11_DATETIME_HPP +#define TOML11_DATETIME_HPP +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace toml +{ + +// To avoid non-threadsafe std::localtime. In C11 (not C++11!), localtime_s is +// provided in the absolutely same purpose, but C++11 is actually not compatible +// with C11. We need to dispatch the function depending on the OS. +namespace detail +{ +// TODO: find more sophisticated way to handle this +#if defined(_MSC_VER) +inline std::tm localtime_s(const std::time_t* src) +{ + std::tm dst; + const auto result = ::localtime_s(&dst, src); + if (result) { throw std::runtime_error("localtime_s failed."); } + return dst; +} +inline std::tm gmtime_s(const std::time_t* src) +{ + std::tm dst; + const auto result = ::gmtime_s(&dst, src); + if (result) { throw std::runtime_error("gmtime_s failed."); } + return dst; +} +#elif (defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 1) || defined(_XOPEN_SOURCE) || defined(_BSD_SOURCE) || defined(_SVID_SOURCE) || defined(_POSIX_SOURCE) +inline std::tm localtime_s(const std::time_t* src) +{ + std::tm dst; + const auto result = ::localtime_r(src, &dst); + if (!result) { throw std::runtime_error("localtime_r failed."); } + return dst; +} +inline std::tm gmtime_s(const std::time_t* src) +{ + std::tm dst; + const auto result = ::gmtime_r(src, &dst); + if (!result) { throw std::runtime_error("gmtime_r failed."); } + return dst; +} +#else // fallback. not threadsafe +inline std::tm localtime_s(const std::time_t* src) +{ + const auto result = std::localtime(src); + if (!result) { throw std::runtime_error("localtime failed."); } + return *result; +} +inline std::tm gmtime_s(const std::time_t* src) +{ + const auto result = std::gmtime(src); + if (!result) { throw std::runtime_error("gmtime failed."); } + return *result; +} +#endif +} // detail + +enum class month_t : std::uint8_t +{ + Jan = 0, + Feb = 1, + Mar = 2, + Apr = 3, + May = 4, + Jun = 5, + Jul = 6, + Aug = 7, + Sep = 8, + Oct = 9, + Nov = 10, + Dec = 11 +}; + +struct local_date +{ + std::int16_t year; // A.D. (like, 2018) + std::uint8_t month; // [0, 11] + std::uint8_t day; // [1, 31] + + local_date(int y, month_t m, int d) + : year (static_cast(y)), + month(static_cast(m)), + day (static_cast(d)) + {} + + explicit local_date(const std::tm& t) + : year (static_cast(t.tm_year + 1900)), + month(static_cast(t.tm_mon)), + day (static_cast(t.tm_mday)) + {} + + explicit local_date(const std::chrono::system_clock::time_point& tp) + { + const auto t = std::chrono::system_clock::to_time_t(tp); + const auto time = detail::localtime_s(&t); + *this = local_date(time); + } + + explicit local_date(const std::time_t t) + : local_date(std::chrono::system_clock::from_time_t(t)) + {} + + operator std::chrono::system_clock::time_point() const + { + // std::mktime returns date as local time zone. no conversion needed + std::tm t; + t.tm_sec = 0; + t.tm_min = 0; + t.tm_hour = 0; + t.tm_mday = static_cast(this->day); + t.tm_mon = static_cast(this->month); + t.tm_year = static_cast(this->year) - 1900; + t.tm_wday = 0; // the value will be ignored + t.tm_yday = 0; // the value will be ignored + t.tm_isdst = -1; + return std::chrono::system_clock::from_time_t(std::mktime(&t)); + } + + operator std::time_t() const + { + return std::chrono::system_clock::to_time_t( + std::chrono::system_clock::time_point(*this)); + } + + local_date() = default; + ~local_date() = default; + local_date(local_date const&) = default; + local_date(local_date&&) = default; + local_date& operator=(local_date const&) = default; + local_date& operator=(local_date&&) = default; +}; + +inline bool operator==(const local_date& lhs, const local_date& rhs) +{ + return std::make_tuple(lhs.year, lhs.month, lhs.day) == + std::make_tuple(rhs.year, rhs.month, rhs.day); +} +inline bool operator!=(const local_date& lhs, const local_date& rhs) +{ + return !(lhs == rhs); +} +inline bool operator< (const local_date& lhs, const local_date& rhs) +{ + return std::make_tuple(lhs.year, lhs.month, lhs.day) < + std::make_tuple(rhs.year, rhs.month, rhs.day); +} +inline bool operator<=(const local_date& lhs, const local_date& rhs) +{ + return (lhs < rhs) || (lhs == rhs); +} +inline bool operator> (const local_date& lhs, const local_date& rhs) +{ + return !(lhs <= rhs); +} +inline bool operator>=(const local_date& lhs, const local_date& rhs) +{ + return !(lhs < rhs); +} + +template +std::basic_ostream& +operator<<(std::basic_ostream& os, const local_date& date) +{ + os << std::setfill('0') << std::setw(4) << static_cast(date.year ) << '-'; + os << std::setfill('0') << std::setw(2) << static_cast(date.month) + 1 << '-'; + os << std::setfill('0') << std::setw(2) << static_cast(date.day ) ; + return os; +} + +struct local_time +{ + std::uint8_t hour; // [0, 23] + std::uint8_t minute; // [0, 59] + std::uint8_t second; // [0, 60] + std::uint16_t millisecond; // [0, 999] + std::uint16_t microsecond; // [0, 999] + std::uint16_t nanosecond; // [0, 999] + + local_time(int h, int m, int s, + int ms = 0, int us = 0, int ns = 0) + : hour (static_cast(h)), + minute(static_cast(m)), + second(static_cast(s)), + millisecond(static_cast(ms)), + microsecond(static_cast(us)), + nanosecond (static_cast(ns)) + {} + + explicit local_time(const std::tm& t) + : hour (static_cast(t.tm_hour)), + minute(static_cast(t.tm_min)), + second(static_cast(t.tm_sec)), + millisecond(0), microsecond(0), nanosecond(0) + {} + + template + explicit local_time(const std::chrono::duration& t) + { + const auto h = std::chrono::duration_cast(t); + this->hour = static_cast(h.count()); + const auto t2 = t - h; + const auto m = std::chrono::duration_cast(t2); + this->minute = static_cast(m.count()); + const auto t3 = t2 - m; + const auto s = std::chrono::duration_cast(t3); + this->second = static_cast(s.count()); + const auto t4 = t3 - s; + const auto ms = std::chrono::duration_cast(t4); + this->millisecond = static_cast(ms.count()); + const auto t5 = t4 - ms; + const auto us = std::chrono::duration_cast(t5); + this->microsecond = static_cast(us.count()); + const auto t6 = t5 - us; + const auto ns = std::chrono::duration_cast(t6); + this->nanosecond = static_cast(ns.count()); + } + + operator std::chrono::nanoseconds() const + { + return std::chrono::nanoseconds (this->nanosecond) + + std::chrono::microseconds(this->microsecond) + + std::chrono::milliseconds(this->millisecond) + + std::chrono::seconds(this->second) + + std::chrono::minutes(this->minute) + + std::chrono::hours(this->hour); + } + + local_time() = default; + ~local_time() = default; + local_time(local_time const&) = default; + local_time(local_time&&) = default; + local_time& operator=(local_time const&) = default; + local_time& operator=(local_time&&) = default; +}; + +inline bool operator==(const local_time& lhs, const local_time& rhs) +{ + return std::make_tuple(lhs.hour, lhs.minute, lhs.second, lhs.millisecond, lhs.microsecond, lhs.nanosecond) == + std::make_tuple(rhs.hour, rhs.minute, rhs.second, rhs.millisecond, rhs.microsecond, rhs.nanosecond); +} +inline bool operator!=(const local_time& lhs, const local_time& rhs) +{ + return !(lhs == rhs); +} +inline bool operator< (const local_time& lhs, const local_time& rhs) +{ + return std::make_tuple(lhs.hour, lhs.minute, lhs.second, lhs.millisecond, lhs.microsecond, lhs.nanosecond) < + std::make_tuple(rhs.hour, rhs.minute, rhs.second, rhs.millisecond, rhs.microsecond, rhs.nanosecond); +} +inline bool operator<=(const local_time& lhs, const local_time& rhs) +{ + return (lhs < rhs) || (lhs == rhs); +} +inline bool operator> (const local_time& lhs, const local_time& rhs) +{ + return !(lhs <= rhs); +} +inline bool operator>=(const local_time& lhs, const local_time& rhs) +{ + return !(lhs < rhs); +} + +template +std::basic_ostream& +operator<<(std::basic_ostream& os, const local_time& time) +{ + os << std::setfill('0') << std::setw(2) << static_cast(time.hour ) << ':'; + os << std::setfill('0') << std::setw(2) << static_cast(time.minute) << ':'; + os << std::setfill('0') << std::setw(2) << static_cast(time.second); + if(time.millisecond != 0 || time.microsecond != 0 || time.nanosecond != 0) + { + os << '.'; + os << std::setfill('0') << std::setw(3) << static_cast(time.millisecond); + if(time.microsecond != 0 || time.nanosecond != 0) + { + os << std::setfill('0') << std::setw(3) << static_cast(time.microsecond); + if(time.nanosecond != 0) + { + os << std::setfill('0') << std::setw(3) << static_cast(time.nanosecond); + } + } + } + return os; +} + +struct time_offset +{ + std::int8_t hour; // [-12, 12] + std::int8_t minute; // [-59, 59] + + time_offset(int h, int m) + : hour (static_cast(h)), + minute(static_cast(m)) + {} + + operator std::chrono::minutes() const + { + return std::chrono::minutes(this->minute) + + std::chrono::hours(this->hour); + } + + time_offset() = default; + ~time_offset() = default; + time_offset(time_offset const&) = default; + time_offset(time_offset&&) = default; + time_offset& operator=(time_offset const&) = default; + time_offset& operator=(time_offset&&) = default; +}; + +inline bool operator==(const time_offset& lhs, const time_offset& rhs) +{ + return std::make_tuple(lhs.hour, lhs.minute) == + std::make_tuple(rhs.hour, rhs.minute); +} +inline bool operator!=(const time_offset& lhs, const time_offset& rhs) +{ + return !(lhs == rhs); +} +inline bool operator< (const time_offset& lhs, const time_offset& rhs) +{ + return std::make_tuple(lhs.hour, lhs.minute) < + std::make_tuple(rhs.hour, rhs.minute); +} +inline bool operator<=(const time_offset& lhs, const time_offset& rhs) +{ + return (lhs < rhs) || (lhs == rhs); +} +inline bool operator> (const time_offset& lhs, const time_offset& rhs) +{ + return !(lhs <= rhs); +} +inline bool operator>=(const time_offset& lhs, const time_offset& rhs) +{ + return !(lhs < rhs); +} + +template +std::basic_ostream& +operator<<(std::basic_ostream& os, const time_offset& offset) +{ + if(offset.hour == 0 && offset.minute == 0) + { + os << 'Z'; + return os; + } + int minute = static_cast(offset.hour) * 60 + offset.minute; + if(minute < 0){os << '-'; minute = std::abs(minute);} else {os << '+';} + os << std::setfill('0') << std::setw(2) << minute / 60 << ':'; + os << std::setfill('0') << std::setw(2) << minute % 60; + return os; +} + +struct local_datetime +{ + local_date date; + local_time time; + + local_datetime(local_date d, local_time t): date(d), time(t) {} + + explicit local_datetime(const std::tm& t): date(t), time(t){} + + explicit local_datetime(const std::chrono::system_clock::time_point& tp) + { + const auto t = std::chrono::system_clock::to_time_t(tp); + std::tm ltime = detail::localtime_s(&t); + + this->date = local_date(ltime); + this->time = local_time(ltime); + + // std::tm lacks subsecond information, so diff between tp and tm + // can be used to get millisecond & microsecond information. + const auto t_diff = tp - + std::chrono::system_clock::from_time_t(std::mktime(<ime)); + this->time.millisecond = static_cast( + std::chrono::duration_cast(t_diff).count()); + this->time.microsecond = static_cast( + std::chrono::duration_cast(t_diff).count()); + this->time.nanosecond = static_cast( + std::chrono::duration_cast(t_diff).count()); + } + + explicit local_datetime(const std::time_t t) + : local_datetime(std::chrono::system_clock::from_time_t(t)) + {} + + operator std::chrono::system_clock::time_point() const + { + using internal_duration = + typename std::chrono::system_clock::time_point::duration; + + // Normally DST begins at A.M. 3 or 4. If we re-use conversion operator + // of local_date and local_time independently, the conversion fails if + // it is the day when DST begins or ends. Since local_date considers the + // time is 00:00 A.M. and local_time does not consider DST because it + // does not have any date information. We need to consider both date and + // time information at the same time to convert it correctly. + + std::tm t; + t.tm_sec = static_cast(this->time.second); + t.tm_min = static_cast(this->time.minute); + t.tm_hour = static_cast(this->time.hour); + t.tm_mday = static_cast(this->date.day); + t.tm_mon = static_cast(this->date.month); + t.tm_year = static_cast(this->date.year) - 1900; + t.tm_wday = 0; // the value will be ignored + t.tm_yday = 0; // the value will be ignored + t.tm_isdst = -1; + + // std::mktime returns date as local time zone. no conversion needed + auto dt = std::chrono::system_clock::from_time_t(std::mktime(&t)); + dt += std::chrono::duration_cast( + std::chrono::milliseconds(this->time.millisecond) + + std::chrono::microseconds(this->time.microsecond) + + std::chrono::nanoseconds (this->time.nanosecond)); + return dt; + } + + operator std::time_t() const + { + return std::chrono::system_clock::to_time_t( + std::chrono::system_clock::time_point(*this)); + } + + local_datetime() = default; + ~local_datetime() = default; + local_datetime(local_datetime const&) = default; + local_datetime(local_datetime&&) = default; + local_datetime& operator=(local_datetime const&) = default; + local_datetime& operator=(local_datetime&&) = default; +}; + +inline bool operator==(const local_datetime& lhs, const local_datetime& rhs) +{ + return std::make_tuple(lhs.date, lhs.time) == + std::make_tuple(rhs.date, rhs.time); +} +inline bool operator!=(const local_datetime& lhs, const local_datetime& rhs) +{ + return !(lhs == rhs); +} +inline bool operator< (const local_datetime& lhs, const local_datetime& rhs) +{ + return std::make_tuple(lhs.date, lhs.time) < + std::make_tuple(rhs.date, rhs.time); +} +inline bool operator<=(const local_datetime& lhs, const local_datetime& rhs) +{ + return (lhs < rhs) || (lhs == rhs); +} +inline bool operator> (const local_datetime& lhs, const local_datetime& rhs) +{ + return !(lhs <= rhs); +} +inline bool operator>=(const local_datetime& lhs, const local_datetime& rhs) +{ + return !(lhs < rhs); +} + +template +std::basic_ostream& +operator<<(std::basic_ostream& os, const local_datetime& dt) +{ + os << dt.date << 'T' << dt.time; + return os; +} + +struct offset_datetime +{ + local_date date; + local_time time; + time_offset offset; + + offset_datetime(local_date d, local_time t, time_offset o) + : date(d), time(t), offset(o) + {} + offset_datetime(const local_datetime& dt, time_offset o) + : date(dt.date), time(dt.time), offset(o) + {} + explicit offset_datetime(const local_datetime& ld) + : date(ld.date), time(ld.time), offset(get_local_offset(nullptr)) + // use the current local timezone offset + {} + explicit offset_datetime(const std::chrono::system_clock::time_point& tp) + : offset(0, 0) // use gmtime + { + const auto timet = std::chrono::system_clock::to_time_t(tp); + const auto tm = detail::gmtime_s(&timet); + this->date = local_date(tm); + this->time = local_time(tm); + } + explicit offset_datetime(const std::time_t& t) + : offset(0, 0) // use gmtime + { + const auto tm = detail::gmtime_s(&t); + this->date = local_date(tm); + this->time = local_time(tm); + } + explicit offset_datetime(const std::tm& t) + : offset(0, 0) // assume gmtime + { + this->date = local_date(t); + this->time = local_time(t); + } + + operator std::chrono::system_clock::time_point() const + { + // get date-time + using internal_duration = + typename std::chrono::system_clock::time_point::duration; + + // first, convert it to local date-time information in the same way as + // local_datetime does. later we will use time_t to adjust time offset. + std::tm t; + t.tm_sec = static_cast(this->time.second); + t.tm_min = static_cast(this->time.minute); + t.tm_hour = static_cast(this->time.hour); + t.tm_mday = static_cast(this->date.day); + t.tm_mon = static_cast(this->date.month); + t.tm_year = static_cast(this->date.year) - 1900; + t.tm_wday = 0; // the value will be ignored + t.tm_yday = 0; // the value will be ignored + t.tm_isdst = -1; + const std::time_t tp_loc = std::mktime(std::addressof(t)); + + auto tp = std::chrono::system_clock::from_time_t(tp_loc); + tp += std::chrono::duration_cast( + std::chrono::milliseconds(this->time.millisecond) + + std::chrono::microseconds(this->time.microsecond) + + std::chrono::nanoseconds (this->time.nanosecond)); + + // Since mktime uses local time zone, it should be corrected. + // `12:00:00+09:00` means `03:00:00Z`. So mktime returns `03:00:00Z` if + // we are in `+09:00` timezone. To represent `12:00:00Z` there, we need + // to add `+09:00` to `03:00:00Z`. + // Here, it uses the time_t converted from date-time info to handle + // daylight saving time. + const auto ofs = get_local_offset(std::addressof(tp_loc)); + tp += std::chrono::hours (ofs.hour); + tp += std::chrono::minutes(ofs.minute); + + // We got `12:00:00Z` by correcting local timezone applied by mktime. + // Then we will apply the offset. Let's say `12:00:00-08:00` is given. + // And now, we have `12:00:00Z`. `12:00:00-08:00` means `20:00:00Z`. + // So we need to subtract the offset. + tp -= std::chrono::minutes(this->offset); + return tp; + } + + operator std::time_t() const + { + return std::chrono::system_clock::to_time_t( + std::chrono::system_clock::time_point(*this)); + } + + offset_datetime() = default; + ~offset_datetime() = default; + offset_datetime(offset_datetime const&) = default; + offset_datetime(offset_datetime&&) = default; + offset_datetime& operator=(offset_datetime const&) = default; + offset_datetime& operator=(offset_datetime&&) = default; + + private: + + static time_offset get_local_offset(const std::time_t* tp) + { + // get local timezone with the same date-time information as mktime + const auto t = detail::localtime_s(tp); + + std::array buf; + const auto result = std::strftime(buf.data(), 6, "%z", &t); // +hhmm\0 + if(result != 5) + { + throw std::runtime_error("toml::offset_datetime: cannot obtain " + "timezone information of current env"); + } + const int ofs = std::atoi(buf.data()); + const int ofs_h = ofs / 100; + const int ofs_m = ofs - (ofs_h * 100); + return time_offset(ofs_h, ofs_m); + } +}; + +inline bool operator==(const offset_datetime& lhs, const offset_datetime& rhs) +{ + return std::make_tuple(lhs.date, lhs.time, lhs.offset) == + std::make_tuple(rhs.date, rhs.time, rhs.offset); +} +inline bool operator!=(const offset_datetime& lhs, const offset_datetime& rhs) +{ + return !(lhs == rhs); +} +inline bool operator< (const offset_datetime& lhs, const offset_datetime& rhs) +{ + return std::make_tuple(lhs.date, lhs.time, lhs.offset) < + std::make_tuple(rhs.date, rhs.time, rhs.offset); +} +inline bool operator<=(const offset_datetime& lhs, const offset_datetime& rhs) +{ + return (lhs < rhs) || (lhs == rhs); +} +inline bool operator> (const offset_datetime& lhs, const offset_datetime& rhs) +{ + return !(lhs <= rhs); +} +inline bool operator>=(const offset_datetime& lhs, const offset_datetime& rhs) +{ + return !(lhs < rhs); +} + +template +std::basic_ostream& +operator<<(std::basic_ostream& os, const offset_datetime& dt) +{ + os << dt.date << 'T' << dt.time << dt.offset; + return os; +} + +}//toml +#endif// TOML11_DATETIME diff --git a/src/frontend/qt_sdl/toml/toml/exception.hpp b/src/frontend/qt_sdl/toml/toml/exception.hpp new file mode 100644 index 0000000000..c64651d0ad --- /dev/null +++ b/src/frontend/qt_sdl/toml/toml/exception.hpp @@ -0,0 +1,65 @@ +// Copyright Toru Niina 2017. +// Distributed under the MIT License. +#ifndef TOML11_EXCEPTION_HPP +#define TOML11_EXCEPTION_HPP +#include +#include + +#include "source_location.hpp" + +namespace toml +{ + +struct exception : public std::exception +{ + public: + explicit exception(const source_location& loc): loc_(loc) {} + virtual ~exception() noexcept override = default; + virtual const char* what() const noexcept override {return "";} + virtual source_location const& location() const noexcept {return loc_;} + + protected: + source_location loc_; +}; + +struct syntax_error : public toml::exception +{ + public: + explicit syntax_error(const std::string& what_arg, const source_location& loc) + : exception(loc), what_(what_arg) + {} + virtual ~syntax_error() noexcept override = default; + virtual const char* what() const noexcept override {return what_.c_str();} + + protected: + std::string what_; +}; + +struct type_error : public toml::exception +{ + public: + explicit type_error(const std::string& what_arg, const source_location& loc) + : exception(loc), what_(what_arg) + {} + virtual ~type_error() noexcept override = default; + virtual const char* what() const noexcept override {return what_.c_str();} + + protected: + std::string what_; +}; + +struct internal_error : public toml::exception +{ + public: + explicit internal_error(const std::string& what_arg, const source_location& loc) + : exception(loc), what_(what_arg) + {} + virtual ~internal_error() noexcept override = default; + virtual const char* what() const noexcept override {return what_.c_str();} + + protected: + std::string what_; +}; + +} // toml +#endif // TOML_EXCEPTION diff --git a/src/frontend/qt_sdl/toml/toml/from.hpp b/src/frontend/qt_sdl/toml/toml/from.hpp new file mode 100644 index 0000000000..10815caf56 --- /dev/null +++ b/src/frontend/qt_sdl/toml/toml/from.hpp @@ -0,0 +1,19 @@ +// Copyright Toru Niina 2019. +// Distributed under the MIT License. +#ifndef TOML11_FROM_HPP +#define TOML11_FROM_HPP + +namespace toml +{ + +template +struct from; +// { +// static T from_toml(const toml::value& v) +// { +// // User-defined conversions ... +// } +// }; + +} // toml +#endif // TOML11_FROM_HPP diff --git a/src/frontend/qt_sdl/toml/toml/get.hpp b/src/frontend/qt_sdl/toml/toml/get.hpp new file mode 100644 index 0000000000..901790b551 --- /dev/null +++ b/src/frontend/qt_sdl/toml/toml/get.hpp @@ -0,0 +1,1119 @@ +// Copyright Toru Niina 2017. +// Distributed under the MIT License. +#ifndef TOML11_GET_HPP +#define TOML11_GET_HPP +#include + +#include "from.hpp" +#include "result.hpp" +#include "value.hpp" + +namespace toml +{ + +// ============================================================================ +// exact toml::* type + +template class M, template class V> +detail::enable_if_t>::value, T> & +get(basic_value& v) +{ + return v.template cast>::value>(); +} + +template class M, template class V> +detail::enable_if_t>::value, T> const& +get(const basic_value& v) +{ + return v.template cast>::value>(); +} + +template class M, template class V> +detail::enable_if_t>::value, T> +get(basic_value&& v) +{ + return T(std::move(v).template cast>::value>()); +} + +// ============================================================================ +// T == toml::value; identity transformation. + +template class M, template class V> +inline detail::enable_if_t>::value, T>& +get(basic_value& v) +{ + return v; +} + +template class M, template class V> +inline detail::enable_if_t>::value, T> const& +get(const basic_value& v) +{ + return v; +} + +template class M, template class V> +inline detail::enable_if_t>::value, T> +get(basic_value&& v) +{ + return basic_value(std::move(v)); +} + +// ============================================================================ +// T == toml::basic_value; basic_value -> basic_value + +template class M, template class V> +inline detail::enable_if_t, + detail::negation>> + >::value, T> +get(const basic_value& v) +{ + return T(v); +} + +// ============================================================================ +// integer convertible from toml::Integer + +template class M, template class V> +inline detail::enable_if_t, // T is integral + detail::negation>, // but not bool + detail::negation< // but not toml::integer + detail::is_exact_toml_type>> + >::value, T> +get(const basic_value& v) +{ + return static_cast(v.as_integer()); +} + +// ============================================================================ +// floating point convertible from toml::Float + +template class M, template class V> +inline detail::enable_if_t, // T is floating_point + detail::negation< // but not toml::floating + detail::is_exact_toml_type>> + >::value, T> +get(const basic_value& v) +{ + return static_cast(v.as_floating()); +} + +// ============================================================================ +// std::string; toml uses its own toml::string, but it should be convertible to +// std::string seamlessly + +template class M, template class V> +inline detail::enable_if_t::value, std::string>& +get(basic_value& v) +{ + return v.as_string().str; +} + +template class M, template class V> +inline detail::enable_if_t::value, std::string> const& +get(const basic_value& v) +{ + return v.as_string().str; +} + +template class M, template class V> +inline detail::enable_if_t::value, std::string> +get(basic_value&& v) +{ + return std::string(std::move(v.as_string().str)); +} + +// ============================================================================ +// std::string_view + +#if defined(TOML11_USING_STRING_VIEW) && TOML11_USING_STRING_VIEW>0 +template class M, template class V> +inline detail::enable_if_t::value, std::string_view> +get(const basic_value& v) +{ + return std::string_view(v.as_string().str); +} +#endif + +// ============================================================================ +// std::chrono::duration from toml::local_time. + +template class M, template class V> +inline detail::enable_if_t::value, T> +get(const basic_value& v) +{ + return std::chrono::duration_cast( + std::chrono::nanoseconds(v.as_local_time())); +} + +// ============================================================================ +// std::chrono::system_clock::time_point from toml::datetime variants + +template class M, template class V> +inline detail::enable_if_t< + std::is_same::value, T> +get(const basic_value& v) +{ + switch(v.type()) + { + case value_t::local_date: + { + return std::chrono::system_clock::time_point(v.as_local_date()); + } + case value_t::local_datetime: + { + return std::chrono::system_clock::time_point(v.as_local_datetime()); + } + case value_t::offset_datetime: + { + return std::chrono::system_clock::time_point(v.as_offset_datetime()); + } + default: + { + throw type_error(detail::format_underline("toml::value: " + "bad_cast to std::chrono::system_clock::time_point", { + {v.location(), concat_to_string("the actual type is ", v.type())} + }), v.location()); + } + } +} + +// ============================================================================ +// forward declaration to use this recursively. ignore this and go ahead. + +// array-like type with push_back(value) method +template class M, template class V> +detail::enable_if_t, // T is a container + detail::has_push_back_method, // T::push_back(value) works + detail::negation< // but not toml::array + detail::is_exact_toml_type>> + >::value, T> +get(const basic_value&); + +// array-like type without push_back(value) method +template class M, template class V> +detail::enable_if_t, // T is a container + detail::negation>, // w/o push_back(...) + detail::negation>, // T does not have special conversion + detail::negation< // not toml::array + detail::is_exact_toml_type>> + >::value, T> +get(const basic_value&); + +// std::pair +template class M, template class V> +detail::enable_if_t::value, T> +get(const basic_value&); + +// std::tuple +template class M, template class V> +detail::enable_if_t::value, T> +get(const basic_value&); + +// map-like classes +template class M, template class V> +detail::enable_if_t, // T is map + detail::negation< // but not toml::table + detail::is_exact_toml_type>> + >::value, T> +get(const basic_value&); + +// T.from_toml(v) +template class M, template class V> +detail::enable_if_t>>, + detail::has_from_toml_method, // but has from_toml(toml::value) + std::is_default_constructible // and default constructible + >::value, T> +get(const basic_value&); + +// toml::from::from_toml(v) +template class M, template class V> +detail::enable_if_t::value, T> +get(const basic_value&); + +// T(const toml::value&) and T is not toml::basic_value, +// and it does not have `from` nor `from_toml`. +template class M, template class V> +detail::enable_if_t>, + std::is_constructible&>, + detail::negation>, + detail::negation> + >::value, T> +get(const basic_value&); + +// ============================================================================ +// array-like types; most likely STL container, like std::vector, etc. + +template class M, template class V> +detail::enable_if_t, // T is a container + detail::has_push_back_method, // container.push_back(elem) works + detail::negation< // but not toml::array + detail::is_exact_toml_type>> + >::value, T> +get(const basic_value& v) +{ + using value_type = typename T::value_type; + const auto& ary = v.as_array(); + + T container; + try_reserve(container, ary.size()); + + for(const auto& elem : ary) + { + container.push_back(get(elem)); + } + return container; +} + +// ============================================================================ +// std::forward_list does not have push_back, insert, or emplace. +// It has insert_after, emplace_after, push_front. + +template class M, template class V> +detail::enable_if_t::value, T> +get(const basic_value& v) +{ + using value_type = typename T::value_type; + T container; + for(const auto& elem : v.as_array()) + { + container.push_front(get(elem)); + } + container.reverse(); + return container; +} + +// ============================================================================ +// array-like types, without push_back(). most likely [std|boost]::array. + +template class M, template class V> +detail::enable_if_t, // T is a container + detail::negation>, // w/o push_back + detail::negation>, // T does not have special conversion + detail::negation< // T is not toml::array + detail::is_exact_toml_type>> + >::value, T> +get(const basic_value& v) +{ + using value_type = typename T::value_type; + const auto& ar = v.as_array(); + + T container; + if(ar.size() != container.size()) + { + throw std::out_of_range(detail::format_underline(concat_to_string( + "toml::get: specified container size is ", container.size(), + " but there are ", ar.size(), " elements in toml array."), { + {v.location(), "here"} + })); + } + for(std::size_t i=0; i(ar[i]); + } + return container; +} + +// ============================================================================ +// std::pair. + +template class M, template class V> +detail::enable_if_t::value, T> +get(const basic_value& v) +{ + using first_type = typename T::first_type; + using second_type = typename T::second_type; + + const auto& ar = v.as_array(); + if(ar.size() != 2) + { + throw std::out_of_range(detail::format_underline(concat_to_string( + "toml::get: specified std::pair but there are ", ar.size(), + " elements in toml array."), {{v.location(), "here"}})); + } + return std::make_pair(::toml::get(ar.at(0)), + ::toml::get(ar.at(1))); +} + +// ============================================================================ +// std::tuple. + +namespace detail +{ +template +T get_tuple_impl(const Array& a, index_sequence) +{ + return std::make_tuple( + ::toml::get::type>(a.at(I))...); +} +} // detail + +template class M, template class V> +detail::enable_if_t::value, T> +get(const basic_value& v) +{ + const auto& ar = v.as_array(); + if(ar.size() != std::tuple_size::value) + { + throw std::out_of_range(detail::format_underline(concat_to_string( + "toml::get: specified std::tuple with ", + std::tuple_size::value, " elements, but there are ", ar.size(), + " elements in toml array."), {{v.location(), "here"}})); + } + return detail::get_tuple_impl(ar, + detail::make_index_sequence::value>{}); +} + +// ============================================================================ +// map-like types; most likely STL map, like std::map or std::unordered_map. + +template class M, template class V> +detail::enable_if_t, // T is map + detail::negation< // but not toml::array + detail::is_exact_toml_type>> + >::value, T> +get(const basic_value& v) +{ + using key_type = typename T::key_type; + using mapped_type = typename T::mapped_type; + static_assert(std::is_convertible::value, + "toml::get only supports map type of which key_type is " + "convertible from std::string."); + T map; + for(const auto& kv : v.as_table()) + { + map.emplace(key_type(kv.first), get(kv.second)); + } + return map; +} + +// ============================================================================ +// user-defined, but compatible types. + +template class M, template class V> +detail::enable_if_t>>, + detail::has_from_toml_method, // but has from_toml(toml::value) memfn + std::is_default_constructible // and default constructible + >::value, T> +get(const basic_value& v) +{ + T ud; + ud.from_toml(v); + return ud; +} +template class M, template class V> +detail::enable_if_t::value, T> +get(const basic_value& v) +{ + return ::toml::from::from_toml(v); +} + +template class M, template class V> +detail::enable_if_t>, // T is not a toml::value + std::is_constructible&>, // T is constructible from toml::value + detail::negation>, // and T does not have T.from_toml(v); + detail::negation> // and T does not have toml::from{}; + >::value, T> +get(const basic_value& v) +{ + return T(v); +} + +// ============================================================================ +// find + +// ---------------------------------------------------------------------------- +// these overloads do not require to set T. and returns value itself. +template class M, template class V> +basic_value const& find(const basic_value& v, const key& ky) +{ + const auto& tab = v.as_table(); + if(tab.count(ky) == 0) + { + detail::throw_key_not_found_error(v, ky); + } + return tab.at(ky); +} +template class M, template class V> +basic_value& find(basic_value& v, const key& ky) +{ + auto& tab = v.as_table(); + if(tab.count(ky) == 0) + { + detail::throw_key_not_found_error(v, ky); + } + return tab.at(ky); +} +template class M, template class V> +basic_value find(basic_value&& v, const key& ky) +{ + typename basic_value::table_type tab = std::move(v).as_table(); + if(tab.count(ky) == 0) + { + detail::throw_key_not_found_error(v, ky); + } + return basic_value(std::move(tab.at(ky))); +} + +// ---------------------------------------------------------------------------- +// find(value, idx) +template class M, template class V> +basic_value const& +find(const basic_value& v, const std::size_t idx) +{ + const auto& ary = v.as_array(); + if(ary.size() <= idx) + { + throw std::out_of_range(detail::format_underline(concat_to_string( + "index ", idx, " is out of range"), {{v.location(), "in this array"}})); + } + return ary.at(idx); +} +template class M, template class V> +basic_value& find(basic_value& v, const std::size_t idx) +{ + auto& ary = v.as_array(); + if(ary.size() <= idx) + { + throw std::out_of_range(detail::format_underline(concat_to_string( + "index ", idx, " is out of range"), {{v.location(), "in this array"}})); + } + return ary.at(idx); +} +template class M, template class V> +basic_value find(basic_value&& v, const std::size_t idx) +{ + auto& ary = v.as_array(); + if(ary.size() <= idx) + { + throw std::out_of_range(detail::format_underline(concat_to_string( + "index ", idx, " is out of range"), {{v.location(), "in this array"}})); + } + return basic_value(std::move(ary.at(idx))); +} + +// ---------------------------------------------------------------------------- +// find(value, key); + +template class M, template class V> +decltype(::toml::get(std::declval const&>())) +find(const basic_value& v, const key& ky) +{ + const auto& tab = v.as_table(); + if(tab.count(ky) == 0) + { + detail::throw_key_not_found_error(v, ky); + } + return ::toml::get(tab.at(ky)); +} + +template class M, template class V> +decltype(::toml::get(std::declval&>())) +find(basic_value& v, const key& ky) +{ + auto& tab = v.as_table(); + if(tab.count(ky) == 0) + { + detail::throw_key_not_found_error(v, ky); + } + return ::toml::get(tab.at(ky)); +} + +template class M, template class V> +decltype(::toml::get(std::declval&&>())) +find(basic_value&& v, const key& ky) +{ + typename basic_value::table_type tab = std::move(v).as_table(); + if(tab.count(ky) == 0) + { + detail::throw_key_not_found_error(v, ky); + } + return ::toml::get(std::move(tab.at(ky))); +} + +// ---------------------------------------------------------------------------- +// find(value, idx) +template class M, template class V> +decltype(::toml::get(std::declval const&>())) +find(const basic_value& v, const std::size_t idx) +{ + const auto& ary = v.as_array(); + if(ary.size() <= idx) + { + throw std::out_of_range(detail::format_underline(concat_to_string( + "index ", idx, " is out of range"), {{v.location(), "in this array"}})); + } + return ::toml::get(ary.at(idx)); +} +template class M, template class V> +decltype(::toml::get(std::declval&>())) +find(basic_value& v, const std::size_t idx) +{ + auto& ary = v.as_array(); + if(ary.size() <= idx) + { + throw std::out_of_range(detail::format_underline(concat_to_string( + "index ", idx, " is out of range"), {{v.location(), "in this array"}})); + } + return ::toml::get(ary.at(idx)); +} +template class M, template class V> +decltype(::toml::get(std::declval&&>())) +find(basic_value&& v, const std::size_t idx) +{ + typename basic_value::array_type ary = std::move(v).as_array(); + if(ary.size() <= idx) + { + throw std::out_of_range(detail::format_underline(concat_to_string( + "index ", idx, " is out of range"), {{v.location(), "in this array"}})); + } + return ::toml::get(std::move(ary.at(idx))); +} + +// -------------------------------------------------------------------------- +// toml::find(toml::value, toml::key, Ts&& ... keys) + +namespace detail +{ +// It suppresses warnings by -Wsign-conversion. Let's say we have the following +// code. +// ```cpp +// const auto x = toml::find(data, "array", 0); +// ``` +// Here, the type of literal number `0` is `int`. `int` is a signed integer. +// `toml::find` takes `std::size_t` as an index. So it causes implicit sign +// conversion and `-Wsign-conversion` warns about it. Using `0u` instead of `0` +// suppresses the warning, but it makes user code messy. +// To suppress this warning, we need to be aware of type conversion caused +// by `toml::find(v, key1, key2, ... keys)`. But the thing is that the types of +// keys can be any combination of {string-like, size_t-like}. Of course we can't +// write down all the combinations. Thus we need to use some function that +// recognize the type of argument and cast it into `std::string` or +// `std::size_t` depending on the context. +// `key_cast` does the job. It has 2 overloads. One is invoked when the +// argument type is an integer and cast the argument into `std::size_t`. The +// other is invoked when the argument type is not an integer, possibly one of +// std::string, const char[N] or const char*, and construct std::string from +// the argument. +// `toml::find(v, k1, k2, ... ks)` uses `key_cast` before passing `ks` to +// `toml::find(v, k)` to suppress -Wsign-conversion. + +template +enable_if_t>, + negation, bool>>>::value, std::size_t> +key_cast(T&& v) noexcept +{ + return std::size_t(v); +} +template +enable_if_t>, + negation, bool>>>>::value, std::string> +key_cast(T&& v) noexcept +{ + return std::string(std::forward(v)); +} +} // detail + +template class M, template class V, + typename Key1, typename Key2, typename ... Keys> +const basic_value& +find(const basic_value& v, Key1&& k1, Key2&& k2, Keys&& ... keys) +{ + return ::toml::find(::toml::find(v, detail::key_cast(k1)), + detail::key_cast(k2), std::forward(keys)...); +} +template class M, template class V, + typename Key1, typename Key2, typename ... Keys> +basic_value& +find(basic_value& v, Key1&& k1, Key2&& k2, Keys&& ... keys) +{ + return ::toml::find(::toml::find(v, detail::key_cast(k1)), + detail::key_cast(k2), std::forward(keys)...); +} +template class M, template class V, + typename Key1, typename Key2, typename ... Keys> +basic_value +find(basic_value&& v, Key1&& k1, Key2&& k2, Keys&& ... keys) +{ + return ::toml::find(::toml::find(std::move(v), std::forward(k1)), + detail::key_cast(k2), std::forward(keys)...); +} + +template class M, template class V, + typename Key1, typename Key2, typename ... Keys> +decltype(::toml::get(std::declval&>())) +find(const basic_value& v, Key1&& k1, Key2&& k2, Keys&& ... keys) +{ + return ::toml::find(::toml::find(v, detail::key_cast(k1)), + detail::key_cast(k2), std::forward(keys)...); +} +template class M, template class V, + typename Key1, typename Key2, typename ... Keys> +decltype(::toml::get(std::declval&>())) +find(basic_value& v, Key1&& k1, Key2&& k2, Keys&& ... keys) +{ + return ::toml::find(::toml::find(v, detail::key_cast(k1)), + detail::key_cast(k2), std::forward(keys)...); +} +template class M, template class V, + typename Key1, typename Key2, typename ... Keys> +decltype(::toml::get(std::declval&&>())) +find(basic_value&& v, Key1&& k1, Key2&& k2, Keys&& ... keys) +{ + return ::toml::find(::toml::find(std::move(v), detail::key_cast(k1)), + detail::key_cast(k2), std::forward(keys)...); +} + +// ============================================================================ +// get_or(value, fallback) + +template class M, template class V> +basic_value const& +get_or(const basic_value& v, const basic_value&) +{ + return v; +} +template class M, template class V> +basic_value& +get_or(basic_value& v, basic_value&) +{ + return v; +} +template class M, template class V> +basic_value +get_or(basic_value&& v, basic_value&&) +{ + return v; +} + +// ---------------------------------------------------------------------------- +// specialization for the exact toml types (return type becomes lvalue ref) + +template class M, template class V> +detail::enable_if_t< + detail::is_exact_toml_type>::value, T> const& +get_or(const basic_value& v, const T& opt) +{ + try + { + return get>(v); + } + catch(...) + { + return opt; + } +} +template class M, template class V> +detail::enable_if_t< + detail::is_exact_toml_type>::value, T>& +get_or(basic_value& v, T& opt) +{ + try + { + return get>(v); + } + catch(...) + { + return opt; + } +} +template class M, template class V> +detail::enable_if_t, + basic_value>::value, detail::remove_cvref_t> +get_or(basic_value&& v, T&& opt) +{ + try + { + return get>(std::move(v)); + } + catch(...) + { + return detail::remove_cvref_t(std::forward(opt)); + } +} + +// ---------------------------------------------------------------------------- +// specialization for std::string (return type becomes lvalue ref) + +template class M, template class V> +detail::enable_if_t, std::string>::value, + std::string> const& +get_or(const basic_value& v, const T& opt) +{ + try + { + return v.as_string().str; + } + catch(...) + { + return opt; + } +} +template class M, template class V> +detail::enable_if_t::value, std::string>& +get_or(basic_value& v, T& opt) +{ + try + { + return v.as_string().str; + } + catch(...) + { + return opt; + } +} +template class M, template class V> +detail::enable_if_t< + std::is_same, std::string>::value, std::string> +get_or(basic_value&& v, T&& opt) +{ + try + { + return std::move(v.as_string().str); + } + catch(...) + { + return std::string(std::forward(opt)); + } +} + +// ---------------------------------------------------------------------------- +// specialization for string literal + +template class M, template class V> +detail::enable_if_t::type>::value, std::string> +get_or(const basic_value& v, T&& opt) +{ + try + { + return std::move(v.as_string().str); + } + catch(...) + { + return std::string(std::forward(opt)); + } +} + +// ---------------------------------------------------------------------------- +// others (require type conversion and return type cannot be lvalue reference) + +template class M, template class V> +detail::enable_if_t, + basic_value>>, + detail::negation>>, + detail::negation::type>> + >::value, detail::remove_cvref_t> +get_or(const basic_value& v, T&& opt) +{ + try + { + return get>(v); + } + catch(...) + { + return detail::remove_cvref_t(std::forward(opt)); + } +} + +// =========================================================================== +// find_or(value, key, fallback) + +template class M, template class V> +basic_value const& +find_or(const basic_value& v, const key& ky, + const basic_value& opt) +{ + if(!v.is_table()) {return opt;} + const auto& tab = v.as_table(); + if(tab.count(ky) == 0) {return opt;} + return tab.at(ky); +} + +template class M, template class V> +basic_value& +find_or(basic_value& v, const toml::key& ky, basic_value& opt) +{ + if(!v.is_table()) {return opt;} + auto& tab = v.as_table(); + if(tab.count(ky) == 0) {return opt;} + return tab.at(ky); +} + +template class M, template class V> +basic_value +find_or(basic_value&& v, const toml::key& ky, basic_value&& opt) +{ + if(!v.is_table()) {return opt;} + auto tab = std::move(v).as_table(); + if(tab.count(ky) == 0) {return opt;} + return basic_value(std::move(tab.at(ky))); +} + +// --------------------------------------------------------------------------- +// exact types (return type can be a reference) +template class M, template class V> +detail::enable_if_t< + detail::is_exact_toml_type>::value, T> const& +find_or(const basic_value& v, const key& ky, const T& opt) +{ + if(!v.is_table()) {return opt;} + const auto& tab = v.as_table(); + if(tab.count(ky) == 0) {return opt;} + return get_or(tab.at(ky), opt); +} + +template class M, template class V> +detail::enable_if_t< + detail::is_exact_toml_type>::value, T>& +find_or(basic_value& v, const toml::key& ky, T& opt) +{ + if(!v.is_table()) {return opt;} + auto& tab = v.as_table(); + if(tab.count(ky) == 0) {return opt;} + return get_or(tab.at(ky), opt); +} + +template class M, template class V> +detail::enable_if_t< + detail::is_exact_toml_type>::value, + detail::remove_cvref_t> +find_or(basic_value&& v, const toml::key& ky, T&& opt) +{ + if(!v.is_table()) {return std::forward(opt);} + auto tab = std::move(v).as_table(); + if(tab.count(ky) == 0) {return std::forward(opt);} + return get_or(std::move(tab.at(ky)), std::forward(opt)); +} + +// --------------------------------------------------------------------------- +// std::string (return type can be a reference) + +template class M, template class V> +detail::enable_if_t::value, std::string> const& +find_or(const basic_value& v, const key& ky, const T& opt) +{ + if(!v.is_table()) {return opt;} + const auto& tab = v.as_table(); + if(tab.count(ky) == 0) {return opt;} + return get_or(tab.at(ky), opt); +} +template class M, template class V> +detail::enable_if_t::value, std::string>& +find_or(basic_value& v, const toml::key& ky, T& opt) +{ + if(!v.is_table()) {return opt;} + auto& tab = v.as_table(); + if(tab.count(ky) == 0) {return opt;} + return get_or(tab.at(ky), opt); +} +template class M, template class V> +detail::enable_if_t::value, std::string> +find_or(basic_value&& v, const toml::key& ky, T&& opt) +{ + if(!v.is_table()) {return std::forward(opt);} + auto tab = std::move(v).as_table(); + if(tab.count(ky) == 0) {return std::forward(opt);} + return get_or(std::move(tab.at(ky)), std::forward(opt)); +} + +// --------------------------------------------------------------------------- +// string literal (deduced as std::string) +template class M, template class V> +detail::enable_if_t< + detail::is_string_literal::type>::value, + std::string> +find_or(const basic_value& v, const toml::key& ky, T&& opt) +{ + if(!v.is_table()) {return std::string(opt);} + const auto& tab = v.as_table(); + if(tab.count(ky) == 0) {return std::string(opt);} + return get_or(tab.at(ky), std::forward(opt)); +} + +// --------------------------------------------------------------------------- +// others (require type conversion and return type cannot be lvalue reference) +template class M, template class V> +detail::enable_if_t, basic_value>>, + // T is not std::string + detail::negation>>, + // T is not a string literal + detail::negation::type>> + >::value, detail::remove_cvref_t> +find_or(const basic_value& v, const toml::key& ky, T&& opt) +{ + if(!v.is_table()) {return std::forward(opt);} + const auto& tab = v.as_table(); + if(tab.count(ky) == 0) {return std::forward(opt);} + return get_or(tab.at(ky), std::forward(opt)); +} + +// --------------------------------------------------------------------------- +// recursive find-or with type deduction (find_or(value, keys, opt)) + +template 1), std::nullptr_t> = nullptr> + // here we need to add SFINAE in the template parameter to avoid + // infinite recursion in type deduction on gcc +auto find_or(Value&& v, const toml::key& ky, Ks&& ... keys) + -> decltype(find_or(std::forward(v), ky, detail::last_one(std::forward(keys)...))) +{ + if(!v.is_table()) + { + return detail::last_one(std::forward(keys)...); + } + auto&& tab = std::forward(v).as_table(); + if(tab.count(ky) == 0) + { + return detail::last_one(std::forward(keys)...); + } + return find_or(std::forward(tab).at(ky), std::forward(keys)...); +} + +// --------------------------------------------------------------------------- +// recursive find_or with explicit type specialization, find_or(value, keys...) + +template 1), std::nullptr_t> = nullptr> + // here we need to add SFINAE in the template parameter to avoid + // infinite recursion in type deduction on gcc +auto find_or(Value&& v, const toml::key& ky, Ks&& ... keys) + -> decltype(find_or(std::forward(v), ky, detail::last_one(std::forward(keys)...))) +{ + if(!v.is_table()) + { + return detail::last_one(std::forward(keys)...); + } + auto&& tab = std::forward(v).as_table(); + if(tab.count(ky) == 0) + { + return detail::last_one(std::forward(keys)...); + } + return find_or(std::forward(tab).at(ky), std::forward(keys)...); +} + +// ============================================================================ +// expect + +template class M, template class V> +result expect(const basic_value& v) noexcept +{ + try + { + return ok(get(v)); + } + catch(const std::exception& e) + { + return err(e.what()); + } +} +template class M, template class V> +result +expect(const basic_value& v, const toml::key& k) noexcept +{ + try + { + return ok(find(v, k)); + } + catch(const std::exception& e) + { + return err(e.what()); + } +} + +} // toml +#endif// TOML11_GET diff --git a/src/frontend/qt_sdl/toml/toml/into.hpp b/src/frontend/qt_sdl/toml/toml/into.hpp new file mode 100644 index 0000000000..74495560e7 --- /dev/null +++ b/src/frontend/qt_sdl/toml/toml/into.hpp @@ -0,0 +1,19 @@ +// Copyright Toru Niina 2019. +// Distributed under the MIT License. +#ifndef TOML11_INTO_HPP +#define TOML11_INTO_HPP + +namespace toml +{ + +template +struct into; +// { +// static toml::value into_toml(const T& user_defined_type) +// { +// // User-defined conversions ... +// } +// }; + +} // toml +#endif // TOML11_INTO_HPP diff --git a/src/frontend/qt_sdl/toml/toml/lexer.hpp b/src/frontend/qt_sdl/toml/toml/lexer.hpp new file mode 100644 index 0000000000..ea5050b8dd --- /dev/null +++ b/src/frontend/qt_sdl/toml/toml/lexer.hpp @@ -0,0 +1,293 @@ +// Copyright Toru Niina 2017. +// Distributed under the MIT License. +#ifndef TOML11_LEXER_HPP +#define TOML11_LEXER_HPP +#include +#include +#include +#include + +#include "combinator.hpp" + +namespace toml +{ +namespace detail +{ + +// these scans contents from current location in a container of char +// and extract a region that matches their own pattern. +// to see the implementation of each component, see combinator.hpp. + +using lex_wschar = either, character<'\t'>>; +using lex_ws = repeat>; +using lex_newline = either, + sequence, character<'\n'>>>; +using lex_lower = in_range<'a', 'z'>; +using lex_upper = in_range<'A', 'Z'>; +using lex_alpha = either; +using lex_digit = in_range<'0', '9'>; +using lex_nonzero = in_range<'1', '9'>; +using lex_oct_dig = in_range<'0', '7'>; +using lex_bin_dig = in_range<'0', '1'>; +using lex_hex_dig = either, in_range<'a', 'f'>>; + +using lex_hex_prefix = sequence, character<'x'>>; +using lex_oct_prefix = sequence, character<'o'>>; +using lex_bin_prefix = sequence, character<'b'>>; +using lex_underscore = character<'_'>; +using lex_plus = character<'+'>; +using lex_minus = character<'-'>; +using lex_sign = either; + +// digit | nonzero 1*(digit | _ digit) +using lex_unsigned_dec_int = either>, at_least<1>>>, + lex_digit>; +// (+|-)? unsigned_dec_int +using lex_dec_int = sequence, lex_unsigned_dec_int>; + +// hex_prefix hex_dig *(hex_dig | _ hex_dig) +using lex_hex_int = sequence>, unlimited>>>; +// oct_prefix oct_dig *(oct_dig | _ oct_dig) +using lex_oct_int = sequence>, unlimited>>>; +// bin_prefix bin_dig *(bin_dig | _ bin_dig) +using lex_bin_int = sequence>, unlimited>>>; + +// (dec_int | hex_int | oct_int | bin_int) +using lex_integer = either; + +// =========================================================================== + +using lex_inf = sequence, character<'n'>, character<'f'>>; +using lex_nan = sequence, character<'a'>, character<'n'>>; +using lex_special_float = sequence, either>; + +using lex_zero_prefixable_int = sequence>, unlimited>>; + +using lex_fractional_part = sequence, lex_zero_prefixable_int>; + +using lex_exponent_part = sequence, character<'E'>>, + maybe, lex_zero_prefixable_int>; + +using lex_float = either>>>>; + +// =========================================================================== + +using lex_true = sequence, character<'r'>, + character<'u'>, character<'e'>>; +using lex_false = sequence, character<'a'>, character<'l'>, + character<'s'>, character<'e'>>; +using lex_boolean = either; + +// =========================================================================== + +using lex_date_fullyear = repeat>; +using lex_date_month = repeat>; +using lex_date_mday = repeat>; +using lex_time_delim = either, character<'t'>, character<' '>>; +using lex_time_hour = repeat>; +using lex_time_minute = repeat>; +using lex_time_second = repeat>; +using lex_time_secfrac = sequence, + repeat>>; + +using lex_time_numoffset = sequence, character<'-'>>, + sequence, + lex_time_minute>>; +using lex_time_offset = either, character<'z'>, + lex_time_numoffset>; + +using lex_partial_time = sequence, + lex_time_minute, character<':'>, + lex_time_second, maybe>; +using lex_full_date = sequence, + lex_date_month, character<'-'>, + lex_date_mday>; +using lex_full_time = sequence; + +using lex_offset_date_time = sequence; +using lex_local_date_time = sequence; +using lex_local_date = lex_full_date; +using lex_local_time = lex_partial_time; + +// =========================================================================== + +using lex_quotation_mark = character<'"'>; +using lex_basic_unescaped = exclude, // 0x09 (tab) is allowed + in_range<0x0A, 0x1F>, + character<0x22>, character<0x5C>, + character<0x7F>>>; + +using lex_escape = character<'\\'>; +using lex_escape_unicode_short = sequence, + repeat>>; +using lex_escape_unicode_long = sequence, + repeat>>; +using lex_escape_seq_char = either, character<'\\'>, + character<'b'>, character<'f'>, + character<'n'>, character<'r'>, + character<'t'>, + lex_escape_unicode_short, + lex_escape_unicode_long + >; +using lex_escaped = sequence; +using lex_basic_char = either; +using lex_basic_string = sequence, + lex_quotation_mark>; + +// After toml post-v0.5.0, it is explicitly clarified how quotes in ml-strings +// are allowed to be used. +// After this, the following strings are *explicitly* allowed. +// - One or two `"`s in a multi-line basic string is allowed wherever it is. +// - Three consecutive `"`s in a multi-line basic string is considered as a delimiter. +// - One or two `"`s can appear just before or after the delimiter. +// ```toml +// str4 = """Here are two quotation marks: "". Simple enough.""" +// str5 = """Here are three quotation marks: ""\".""" +// str6 = """Here are fifteen quotation marks: ""\"""\"""\"""\"""\".""" +// str7 = """"This," she said, "is just a pointless statement."""" +// ``` +// In the current implementation (v3.3.0), it is difficult to parse `str7` in +// the above example. It is difficult to recognize `"` at the end of string body +// collectly. It will be misunderstood as a `"""` delimiter and an additional, +// invalid `"`. Like this: +// ```console +// what(): [error] toml::parse_table: invalid line format +// --> hoge.toml +// | +// 13 | str7 = """"This," she said, "is just a pointless statement."""" +// | ^- expected newline, but got '"'. +// ``` +// As a quick workaround for this problem, `lex_ml_basic_string_delim` was +// split into two, `lex_ml_basic_string_open` and `lex_ml_basic_string_close`. +// `lex_ml_basic_string_open` allows only `"""`. `_close` allows 3-5 `"`s. +// In parse_ml_basic_string() function, the trailing `"`s will be attached to +// the string body. +// +using lex_ml_basic_string_delim = repeat>; +using lex_ml_basic_string_open = lex_ml_basic_string_delim; +using lex_ml_basic_string_close = sequence< + repeat>, + maybe, maybe + >; + +using lex_ml_basic_unescaped = exclude, // 0x09 is tab + in_range<0x0A, 0x1F>, + character<0x5C>, // backslash + character<0x7F>, // DEL + lex_ml_basic_string_delim>>; + +using lex_ml_basic_escaped_newline = sequence< + lex_escape, maybe, lex_newline, + repeat, unlimited>>; + +using lex_ml_basic_char = either; +using lex_ml_basic_body = repeat, + unlimited>; +using lex_ml_basic_string = sequence; + +using lex_literal_char = exclude, in_range<0x0A, 0x1F>, + character<0x7F>, character<0x27>>>; +using lex_apostrophe = character<'\''>; +using lex_literal_string = sequence, + lex_apostrophe>; + +// the same reason as above. +using lex_ml_literal_string_delim = repeat>; +using lex_ml_literal_string_open = lex_ml_literal_string_delim; +using lex_ml_literal_string_close = sequence< + repeat>, + maybe, maybe + >; + +using lex_ml_literal_char = exclude, + in_range<0x0A, 0x1F>, + character<0x7F>, + lex_ml_literal_string_delim>>; +using lex_ml_literal_body = repeat, + unlimited>; +using lex_ml_literal_string = sequence; + +using lex_string = either; + +// =========================================================================== +using lex_dot_sep = sequence, character<'.'>, maybe>; + +using lex_unquoted_key = repeat, character<'_'>>, + at_least<1>>; +using lex_quoted_key = either; +using lex_simple_key = either; +using lex_dotted_key = sequence, + at_least<1> + > + >; +using lex_key = either; + +using lex_keyval_sep = sequence, + character<'='>, + maybe>; + +using lex_std_table_open = character<'['>; +using lex_std_table_close = character<']'>; +using lex_std_table = sequence, + lex_key, + maybe, + lex_std_table_close>; + +using lex_array_table_open = sequence; +using lex_array_table_close = sequence; +using lex_array_table = sequence, + lex_key, + maybe, + lex_array_table_close>; + +using lex_utf8_1byte = in_range<0x00, 0x7F>; +using lex_utf8_2byte = sequence< + in_range(0xC2), static_cast(0xDF)>, + in_range(0x80), static_cast(0xBF)> + >; +using lex_utf8_3byte = sequence(0xE0)>, in_range(0xA0), static_cast(0xBF)>>, + sequence(0xE1), static_cast(0xEC)>, in_range(0x80), static_cast(0xBF)>>, + sequence(0xED)>, in_range(0x80), static_cast(0x9F)>>, + sequence(0xEE), static_cast(0xEF)>, in_range(0x80), static_cast(0xBF)>> + >, in_range(0x80), static_cast(0xBF)>>; +using lex_utf8_4byte = sequence(0xF0)>, in_range(0x90), static_cast(0xBF)>>, + sequence(0xF1), static_cast(0xF3)>, in_range(0x80), static_cast(0xBF)>>, + sequence(0xF4)>, in_range(0x80), static_cast(0x8F)>> + >, in_range(0x80), static_cast(0xBF)>, + in_range(0x80), static_cast(0xBF)>>; +using lex_utf8_code = either< + lex_utf8_1byte, + lex_utf8_2byte, + lex_utf8_3byte, + lex_utf8_4byte + >; + +using lex_comment_start_symbol = character<'#'>; +using lex_non_eol_ascii = either, in_range<0x20, 0x7E>>; +using lex_comment = sequence, unlimited>>; + +} // detail +} // toml +#endif // TOML_LEXER_HPP diff --git a/src/frontend/qt_sdl/toml/toml/literal.hpp b/src/frontend/qt_sdl/toml/toml/literal.hpp new file mode 100644 index 0000000000..04fbbc13e1 --- /dev/null +++ b/src/frontend/qt_sdl/toml/toml/literal.hpp @@ -0,0 +1,113 @@ +// Copyright Toru Niina 2019. +// Distributed under the MIT License. +#ifndef TOML11_LITERAL_HPP +#define TOML11_LITERAL_HPP +#include "parser.hpp" + +namespace toml +{ +inline namespace literals +{ +inline namespace toml_literals +{ + +// implementation +inline ::toml::basic_value +literal_internal_impl(::toml::detail::location loc) +{ + using value_type = ::toml::basic_value< + TOML11_DEFAULT_COMMENT_STRATEGY, std::unordered_map, std::vector>; + // if there are some comments or empty lines, skip them. + using skip_line = ::toml::detail::repeat, + ::toml::detail::maybe<::toml::detail::lex_comment>, + ::toml::detail::lex_newline + >, ::toml::detail::at_least<1>>; + skip_line::invoke(loc); + + // if there are some whitespaces before a value, skip them. + using skip_ws = ::toml::detail::repeat< + ::toml::detail::lex_ws, ::toml::detail::at_least<1>>; + skip_ws::invoke(loc); + + // to distinguish arrays and tables, first check it is a table or not. + // + // "[1,2,3]"_toml; // this is an array + // "[table]"_toml; // a table that has an empty table named "table" inside. + // "[[1,2,3]]"_toml; // this is an array of arrays + // "[[table]]"_toml; // this is a table that has an array of tables inside. + // + // "[[1]]"_toml; // this can be both... (currently it becomes a table) + // "1 = [{}]"_toml; // this is a table that has an array of table named 1. + // "[[1,]]"_toml; // this is an array of arrays. + // "[[1],]"_toml; // this also. + + const auto the_front = loc.iter(); + + const bool is_table_key = ::toml::detail::lex_std_table::invoke(loc); + loc.reset(the_front); + + const bool is_aots_key = ::toml::detail::lex_array_table::invoke(loc); + loc.reset(the_front); + + // If it is neither a table-key or a array-of-table-key, it may be a value. + if(!is_table_key && !is_aots_key) + { + if(auto data = ::toml::detail::parse_value(loc)) + { + return data.unwrap(); + } + } + + // Note that still it can be a table, because the literal might be something + // like the following. + // ```cpp + // R"( // c++11 raw string literals + // key = "value" + // int = 42 + // )"_toml; + // ``` + // It is a valid toml file. + // It should be parsed as if we parse a file with this content. + + if(auto data = ::toml::detail::parse_toml_file(loc)) + { + return data.unwrap(); + } + else // none of them. + { + throw ::toml::syntax_error(data.unwrap_err(), source_location(loc)); + } + +} + +inline ::toml::basic_value +operator"" _toml(const char* str, std::size_t len) +{ + ::toml::detail::location loc( + std::string("TOML literal encoded in a C++ code"), + std::vector(str, str + len)); + // literal length does not include the null character at the end. + return literal_internal_impl(std::move(loc)); +} + +// value of __cplusplus in C++2a/20 mode is not fixed yet along compilers. +// So here we use the feature test macro for `char8_t` itself. +#if defined(__cpp_char8_t) && __cpp_char8_t >= 201811L +// value of u8"" literal has been changed from char to char8_t and char8_t is +// NOT compatible to char +inline ::toml::basic_value +operator"" _toml(const char8_t* str, std::size_t len) +{ + ::toml::detail::location loc( + std::string("TOML literal encoded in a C++ code"), + std::vector(reinterpret_cast(str), + reinterpret_cast(str) + len)); + return literal_internal_impl(std::move(loc)); +} +#endif + +} // toml_literals +} // literals +} // toml +#endif//TOML11_LITERAL_HPP diff --git a/src/frontend/qt_sdl/toml/toml/macros.hpp b/src/frontend/qt_sdl/toml/toml/macros.hpp new file mode 100644 index 0000000000..e8f91aecd4 --- /dev/null +++ b/src/frontend/qt_sdl/toml/toml/macros.hpp @@ -0,0 +1,121 @@ +#ifndef TOML11_MACROS_HPP +#define TOML11_MACROS_HPP + +#define TOML11_STRINGIZE_AUX(x) #x +#define TOML11_STRINGIZE(x) TOML11_STRINGIZE_AUX(x) + +#define TOML11_CONCATENATE_AUX(x, y) x##y +#define TOML11_CONCATENATE(x, y) TOML11_CONCATENATE_AUX(x, y) + +// ============================================================================ +// TOML11_DEFINE_CONVERSION_NON_INTRUSIVE + +#ifndef TOML11_WITHOUT_DEFINE_NON_INTRUSIVE + +// ---------------------------------------------------------------------------- +// TOML11_ARGS_SIZE + +#define TOML11_INDEX_RSEQ() \ + 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, \ + 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 +#define TOML11_ARGS_SIZE_IMPL(\ + ARG1, ARG2, ARG3, ARG4, ARG5, ARG6, ARG7, ARG8, ARG9, ARG10, \ + ARG11, ARG12, ARG13, ARG14, ARG15, ARG16, ARG17, ARG18, ARG19, ARG20, \ + ARG21, ARG22, ARG23, ARG24, ARG25, ARG26, ARG27, ARG28, ARG29, ARG30, \ + ARG31, ARG32, N, ...) N +#define TOML11_ARGS_SIZE_AUX(...) TOML11_ARGS_SIZE_IMPL(__VA_ARGS__) +#define TOML11_ARGS_SIZE(...) TOML11_ARGS_SIZE_AUX(__VA_ARGS__, TOML11_INDEX_RSEQ()) + +// ---------------------------------------------------------------------------- +// TOML11_FOR_EACH_VA_ARGS + +#define TOML11_FOR_EACH_VA_ARGS_AUX_1( FUNCTOR, ARG1 ) FUNCTOR(ARG1) +#define TOML11_FOR_EACH_VA_ARGS_AUX_2( FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_1( FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_3( FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_2( FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_4( FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_3( FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_5( FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_4( FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_6( FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_5( FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_7( FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_6( FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_8( FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_7( FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_9( FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_8( FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_10(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_9( FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_11(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_10(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_12(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_11(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_13(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_12(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_14(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_13(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_15(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_14(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_16(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_15(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_17(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_16(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_18(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_17(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_19(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_18(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_20(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_19(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_21(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_20(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_22(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_21(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_23(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_22(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_24(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_23(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_25(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_24(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_26(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_25(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_27(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_26(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_28(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_27(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_29(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_28(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_30(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_29(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_31(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_30(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_32(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_31(FUNCTOR, __VA_ARGS__) + +#define TOML11_FOR_EACH_VA_ARGS(FUNCTOR, ...)\ + TOML11_CONCATENATE(TOML11_FOR_EACH_VA_ARGS_AUX_, TOML11_ARGS_SIZE(__VA_ARGS__))(FUNCTOR, __VA_ARGS__) + +// ---------------------------------------------------------------------------- +// TOML11_DEFINE_CONVERSION_NON_INTRUSIVE + +// use it in the following way. +// ```cpp +// namespace foo +// { +// struct Foo +// { +// std::string s; +// double d; +// int i; +// }; +// } // foo +// +// TOML11_DEFINE_CONVERSION_NON_INTRUSIVE(foo::Foo, s, d, i) +// ``` +// And then you can use `toml::find(file, "foo");` +// +#define TOML11_FIND_MEMBER_VARIABLE_FROM_VALUE(VAR_NAME)\ + obj.VAR_NAME = toml::find(v, TOML11_STRINGIZE(VAR_NAME)); + +#define TOML11_ASSIGN_MEMBER_VARIABLE_TO_VALUE(VAR_NAME)\ + v[TOML11_STRINGIZE(VAR_NAME)] = obj.VAR_NAME; + +#define TOML11_DEFINE_CONVERSION_NON_INTRUSIVE(NAME, ...)\ + namespace toml { \ + template<> \ + struct from \ + { \ + template class T, \ + template class A> \ + static NAME from_toml(const basic_value& v) \ + { \ + NAME obj; \ + TOML11_FOR_EACH_VA_ARGS(TOML11_FIND_MEMBER_VARIABLE_FROM_VALUE, __VA_ARGS__) \ + return obj; \ + } \ + }; \ + template<> \ + struct into \ + { \ + static value into_toml(const NAME& obj) \ + { \ + ::toml::value v = ::toml::table{}; \ + TOML11_FOR_EACH_VA_ARGS(TOML11_ASSIGN_MEMBER_VARIABLE_TO_VALUE, __VA_ARGS__) \ + return v; \ + } \ + }; \ + } /* toml */ + +#endif// TOML11_WITHOUT_DEFINE_NON_INTRUSIVE + +#endif// TOML11_MACROS_HPP diff --git a/src/frontend/qt_sdl/toml/toml/parser.hpp b/src/frontend/qt_sdl/toml/toml/parser.hpp new file mode 100644 index 0000000000..bfa5531f0d --- /dev/null +++ b/src/frontend/qt_sdl/toml/toml/parser.hpp @@ -0,0 +1,2416 @@ +// Copyright Toru Niina 2017. +// Distributed under the MIT License. +#ifndef TOML11_PARSER_HPP +#define TOML11_PARSER_HPP +#include +#include +#include + +#include "combinator.hpp" +#include "lexer.hpp" +#include "region.hpp" +#include "result.hpp" +#include "types.hpp" +#include "value.hpp" + +#ifndef TOML11_DISABLE_STD_FILESYSTEM +#ifdef __cpp_lib_filesystem +#if __has_include() +#define TOML11_HAS_STD_FILESYSTEM +#include +#endif // has_include() +#endif // __cpp_lib_filesystem +#endif // TOML11_DISABLE_STD_FILESYSTEM + +namespace toml +{ +namespace detail +{ + +inline result, std::string> +parse_boolean(location& loc) +{ + const auto first = loc.iter(); + if(const auto token = lex_boolean::invoke(loc)) + { + const auto reg = token.unwrap(); + if (reg.str() == "true") {return ok(std::make_pair(true, reg));} + else if(reg.str() == "false") {return ok(std::make_pair(false, reg));} + else // internal error. + { + throw internal_error(format_underline( + "toml::parse_boolean: internal error", + {{source_location(reg), "invalid token"}}), + source_location(reg)); + } + } + loc.reset(first); //rollback + return err(format_underline("toml::parse_boolean: ", + {{source_location(loc), "the next token is not a boolean"}})); +} + +inline result, std::string> +parse_binary_integer(location& loc) +{ + const auto first = loc.iter(); + if(const auto token = lex_bin_int::invoke(loc)) + { + auto str = token.unwrap().str(); + assert(str.size() > 2); // minimum -> 0b1 + integer retval(0), base(1); + for(auto i(str.rbegin()), e(str.rend() - 2); i!=e; ++i) + { + if (*i == '1'){retval += base; base *= 2;} + else if(*i == '0'){base *= 2;} + else if(*i == '_'){/* do nothing. */} + else // internal error. + { + throw internal_error(format_underline( + "toml::parse_integer: internal error", + {{source_location(token.unwrap()), "invalid token"}}), + source_location(loc)); + } + } + return ok(std::make_pair(retval, token.unwrap())); + } + loc.reset(first); + return err(format_underline("toml::parse_binary_integer:", + {{source_location(loc), "the next token is not an integer"}})); +} + +inline result, std::string> +parse_octal_integer(location& loc) +{ + const auto first = loc.iter(); + if(const auto token = lex_oct_int::invoke(loc)) + { + auto str = token.unwrap().str(); + str.erase(std::remove(str.begin(), str.end(), '_'), str.end()); + str.erase(str.begin()); str.erase(str.begin()); // remove `0o` prefix + + std::istringstream iss(str); + integer retval(0); + iss >> std::oct >> retval; + return ok(std::make_pair(retval, token.unwrap())); + } + loc.reset(first); + return err(format_underline("toml::parse_octal_integer:", + {{source_location(loc), "the next token is not an integer"}})); +} + +inline result, std::string> +parse_hexadecimal_integer(location& loc) +{ + const auto first = loc.iter(); + if(const auto token = lex_hex_int::invoke(loc)) + { + auto str = token.unwrap().str(); + str.erase(std::remove(str.begin(), str.end(), '_'), str.end()); + str.erase(str.begin()); str.erase(str.begin()); // remove `0x` prefix + + std::istringstream iss(str); + integer retval(0); + iss >> std::hex >> retval; + return ok(std::make_pair(retval, token.unwrap())); + } + loc.reset(first); + return err(format_underline("toml::parse_hexadecimal_integer", + {{source_location(loc), "the next token is not an integer"}})); +} + +inline result, std::string> +parse_integer(location& loc) +{ + const auto first = loc.iter(); + if(first != loc.end() && *first == '0') + { + const auto second = std::next(first); + if(second == loc.end()) // the token is just zero. + { + loc.advance(); + return ok(std::make_pair(0, region(loc, first, second))); + } + + if(*second == 'b') {return parse_binary_integer (loc);} // 0b1100 + if(*second == 'o') {return parse_octal_integer (loc);} // 0o775 + if(*second == 'x') {return parse_hexadecimal_integer(loc);} // 0xC0FFEE + + if(std::isdigit(*second)) + { + return err(format_underline("toml::parse_integer: " + "leading zero in an Integer is not allowed.", + {{source_location(loc), "leading zero"}})); + } + else if(std::isalpha(*second)) + { + return err(format_underline("toml::parse_integer: " + "unknown integer prefix appeared.", + {{source_location(loc), "none of 0x, 0o, 0b"}})); + } + } + + if(const auto token = lex_dec_int::invoke(loc)) + { + auto str = token.unwrap().str(); + str.erase(std::remove(str.begin(), str.end(), '_'), str.end()); + + std::istringstream iss(str); + integer retval(0); + iss >> retval; + return ok(std::make_pair(retval, token.unwrap())); + } + loc.reset(first); + return err(format_underline("toml::parse_integer: ", + {{source_location(loc), "the next token is not an integer"}})); +} + +inline result, std::string> +parse_floating(location& loc) +{ + const auto first = loc.iter(); + if(const auto token = lex_float::invoke(loc)) + { + auto str = token.unwrap().str(); + if(str == "inf" || str == "+inf") + { + if(std::numeric_limits::has_infinity) + { + return ok(std::make_pair( + std::numeric_limits::infinity(), token.unwrap())); + } + else + { + throw std::domain_error("toml::parse_floating: inf value found" + " but the current environment does not support inf. Please" + " make sure that the floating-point implementation conforms" + " IEEE 754/ISO 60559 international standard."); + } + } + else if(str == "-inf") + { + if(std::numeric_limits::has_infinity) + { + return ok(std::make_pair( + -std::numeric_limits::infinity(), token.unwrap())); + } + else + { + throw std::domain_error("toml::parse_floating: inf value found" + " but the current environment does not support inf. Please" + " make sure that the floating-point implementation conforms" + " IEEE 754/ISO 60559 international standard."); + } + } + else if(str == "nan" || str == "+nan") + { + if(std::numeric_limits::has_quiet_NaN) + { + return ok(std::make_pair( + std::numeric_limits::quiet_NaN(), token.unwrap())); + } + else if(std::numeric_limits::has_signaling_NaN) + { + return ok(std::make_pair( + std::numeric_limits::signaling_NaN(), token.unwrap())); + } + else + { + throw std::domain_error("toml::parse_floating: NaN value found" + " but the current environment does not support NaN. Please" + " make sure that the floating-point implementation conforms" + " IEEE 754/ISO 60559 international standard."); + } + } + else if(str == "-nan") + { + if(std::numeric_limits::has_quiet_NaN) + { + return ok(std::make_pair( + -std::numeric_limits::quiet_NaN(), token.unwrap())); + } + else if(std::numeric_limits::has_signaling_NaN) + { + return ok(std::make_pair( + -std::numeric_limits::signaling_NaN(), token.unwrap())); + } + else + { + throw std::domain_error("toml::parse_floating: NaN value found" + " but the current environment does not support NaN. Please" + " make sure that the floating-point implementation conforms" + " IEEE 754/ISO 60559 international standard."); + } + } + str.erase(std::remove(str.begin(), str.end(), '_'), str.end()); + std::istringstream iss(str); + floating v(0.0); + iss >> v; + return ok(std::make_pair(v, token.unwrap())); + } + loc.reset(first); + return err(format_underline("toml::parse_floating: ", + {{source_location(loc), "the next token is not a float"}})); +} + +inline std::string read_utf8_codepoint(const region& reg, const location& loc) +{ + const auto str = reg.str().substr(1); + std::uint_least32_t codepoint; + std::istringstream iss(str); + iss >> std::hex >> codepoint; + + const auto to_char = [](const std::uint_least32_t i) noexcept -> char { + const auto uc = static_cast(i); + return *reinterpret_cast(std::addressof(uc)); + }; + + std::string character; + if(codepoint < 0x80) // U+0000 ... U+0079 ; just an ASCII. + { + character += static_cast(codepoint); + } + else if(codepoint < 0x800) //U+0080 ... U+07FF + { + // 110yyyyx 10xxxxxx; 0x3f == 0b0011'1111 + character += to_char(0xC0| codepoint >> 6); + character += to_char(0x80|(codepoint & 0x3F)); + } + else if(codepoint < 0x10000) // U+0800...U+FFFF + { + if(0xD800 <= codepoint && codepoint <= 0xDFFF) + { + throw syntax_error(format_underline( + "toml::read_utf8_codepoint: codepoints in the range " + "[0xD800, 0xDFFF] are not valid UTF-8.", {{ + source_location(loc), "not a valid UTF-8 codepoint" + }}), source_location(loc)); + } + assert(codepoint < 0xD800 || 0xDFFF < codepoint); + // 1110yyyy 10yxxxxx 10xxxxxx + character += to_char(0xE0| codepoint >> 12); + character += to_char(0x80|(codepoint >> 6 & 0x3F)); + character += to_char(0x80|(codepoint & 0x3F)); + } + else if(codepoint < 0x110000) // U+010000 ... U+10FFFF + { + // 11110yyy 10yyxxxx 10xxxxxx 10xxxxxx + character += to_char(0xF0| codepoint >> 18); + character += to_char(0x80|(codepoint >> 12 & 0x3F)); + character += to_char(0x80|(codepoint >> 6 & 0x3F)); + character += to_char(0x80|(codepoint & 0x3F)); + } + else // out of UTF-8 region + { + throw syntax_error(format_underline("toml::read_utf8_codepoint:" + " input codepoint is too large.", + {{source_location(loc), "should be in [0x00..0x10FFFF]"}}), + source_location(loc)); + } + return character; +} + +inline result parse_escape_sequence(location& loc) +{ + const auto first = loc.iter(); + if(first == loc.end() || *first != '\\') + { + return err(format_underline("toml::parse_escape_sequence: ", {{ + source_location(loc), "the next token is not a backslash \"\\\""}})); + } + loc.advance(); + switch(*loc.iter()) + { + case '\\':{loc.advance(); return ok(std::string("\\"));} + case '"' :{loc.advance(); return ok(std::string("\""));} + case 'b' :{loc.advance(); return ok(std::string("\b"));} + case 't' :{loc.advance(); return ok(std::string("\t"));} + case 'n' :{loc.advance(); return ok(std::string("\n"));} + case 'f' :{loc.advance(); return ok(std::string("\f"));} + case 'r' :{loc.advance(); return ok(std::string("\r"));} + case 'u' : + { + if(const auto token = lex_escape_unicode_short::invoke(loc)) + { + return ok(read_utf8_codepoint(token.unwrap(), loc)); + } + else + { + return err(format_underline("parse_escape_sequence: " + "invalid token found in UTF-8 codepoint uXXXX.", + {{source_location(loc), "here"}})); + } + } + case 'U': + { + if(const auto token = lex_escape_unicode_long::invoke(loc)) + { + return ok(read_utf8_codepoint(token.unwrap(), loc)); + } + else + { + return err(format_underline("parse_escape_sequence: " + "invalid token found in UTF-8 codepoint Uxxxxxxxx", + {{source_location(loc), "here"}})); + } + } + } + + const auto msg = format_underline("parse_escape_sequence: " + "unknown escape sequence appeared.", {{source_location(loc), + "escape sequence is one of \\, \", b, t, n, f, r, uxxxx, Uxxxxxxxx"}}, + /* Hints = */{"if you want to write backslash as just one backslash, " + "use literal string like: regex = '<\\i\\c*\\s*>'"}); + loc.reset(first); + return err(msg); +} + +inline std::ptrdiff_t check_utf8_validity(const std::string& reg) +{ + location loc("tmp", reg); + const auto u8 = repeat::invoke(loc); + if(!u8 || loc.iter() != loc.end()) + { + const auto error_location = std::distance(loc.begin(), loc.iter()); + assert(0 <= error_location); + return error_location; + } + return -1; +} + +inline result, std::string> +parse_ml_basic_string(location& loc) +{ + const auto first = loc.iter(); + if(const auto token = lex_ml_basic_string::invoke(loc)) + { + auto inner_loc = loc; + inner_loc.reset(first); + + std::string retval; + retval.reserve(token.unwrap().size()); + + auto delim = lex_ml_basic_string_open::invoke(inner_loc); + if(!delim) + { + throw internal_error(format_underline( + "parse_ml_basic_string: invalid token", + {{source_location(inner_loc), "should be \"\"\""}}), + source_location(inner_loc)); + } + // immediate newline is ignored (if exists) + /* discard return value */ lex_newline::invoke(inner_loc); + + delim = none(); + while(!delim) + { + using lex_unescaped_seq = repeat< + either, unlimited>; + if(auto unescaped = lex_unescaped_seq::invoke(inner_loc)) + { + retval += unescaped.unwrap().str(); + } + if(auto escaped = parse_escape_sequence(inner_loc)) + { + retval += escaped.unwrap(); + } + if(auto esc_nl = lex_ml_basic_escaped_newline::invoke(inner_loc)) + { + // ignore newline after escape until next non-ws char + } + if(inner_loc.iter() == inner_loc.end()) + { + throw internal_error(format_underline( + "parse_ml_basic_string: unexpected end of region", + {{source_location(inner_loc), "not sufficient token"}}), + source_location(inner_loc)); + } + delim = lex_ml_basic_string_close::invoke(inner_loc); + } + // `lex_ml_basic_string_close` allows 3 to 5 `"`s to allow 1 or 2 `"`s + // at just before the delimiter. Here, we need to attach `"`s at the + // end of the string body, if it exists. + // For detail, see the definition of `lex_ml_basic_string_close`. + assert(std::all_of(delim.unwrap().first(), delim.unwrap().last(), + [](const char c) noexcept {return c == '\"';})); + switch(delim.unwrap().size()) + { + case 3: {break;} + case 4: {retval += "\""; break;} + case 5: {retval += "\"\""; break;} + default: + { + throw internal_error(format_underline( + "parse_ml_basic_string: closing delimiter has invalid length", + {{source_location(inner_loc), "end of this"}}), + source_location(inner_loc)); + } + } + + const auto err_loc = check_utf8_validity(token.unwrap().str()); + if(err_loc == -1) + { + return ok(std::make_pair(toml::string(retval), token.unwrap())); + } + else + { + inner_loc.reset(first); + inner_loc.advance(err_loc); + throw syntax_error(format_underline( + "parse_ml_basic_string: invalid utf8 sequence found", + {{source_location(inner_loc), "here"}}), + source_location(inner_loc)); + } + } + else + { + loc.reset(first); + return err(format_underline("toml::parse_ml_basic_string: " + "the next token is not a valid multiline string", + {{source_location(loc), "here"}})); + } +} + +inline result, std::string> +parse_basic_string(location& loc) +{ + const auto first = loc.iter(); + if(const auto token = lex_basic_string::invoke(loc)) + { + auto inner_loc = loc; + inner_loc.reset(first); + + auto quot = lex_quotation_mark::invoke(inner_loc); + if(!quot) + { + throw internal_error(format_underline("parse_basic_string: " + "invalid token", {{source_location(inner_loc), "should be \""}}), + source_location(inner_loc)); + } + + std::string retval; + retval.reserve(token.unwrap().size()); + + quot = none(); + while(!quot) + { + using lex_unescaped_seq = repeat; + if(auto unescaped = lex_unescaped_seq::invoke(inner_loc)) + { + retval += unescaped.unwrap().str(); + } + if(auto escaped = parse_escape_sequence(inner_loc)) + { + retval += escaped.unwrap(); + } + if(inner_loc.iter() == inner_loc.end()) + { + throw internal_error(format_underline( + "parse_basic_string: unexpected end of region", + {{source_location(inner_loc), "not sufficient token"}}), + source_location(inner_loc)); + } + quot = lex_quotation_mark::invoke(inner_loc); + } + + const auto err_loc = check_utf8_validity(token.unwrap().str()); + if(err_loc == -1) + { + return ok(std::make_pair(toml::string(retval), token.unwrap())); + } + else + { + inner_loc.reset(first); + inner_loc.advance(err_loc); + throw syntax_error(format_underline( + "parse_ml_basic_string: invalid utf8 sequence found", + {{source_location(inner_loc), "here"}}), + source_location(inner_loc)); + } + } + else + { + loc.reset(first); // rollback + return err(format_underline("toml::parse_basic_string: " + "the next token is not a valid string", + {{source_location(loc), "here"}})); + } +} + +inline result, std::string> +parse_ml_literal_string(location& loc) +{ + const auto first = loc.iter(); + if(const auto token = lex_ml_literal_string::invoke(loc)) + { + location inner_loc(loc.name(), token.unwrap().str()); + + const auto open = lex_ml_literal_string_open::invoke(inner_loc); + if(!open) + { + throw internal_error(format_underline( + "parse_ml_literal_string: invalid token", + {{source_location(inner_loc), "should be '''"}}), + source_location(inner_loc)); + } + // immediate newline is ignored (if exists) + /* discard return value */ lex_newline::invoke(inner_loc); + + const auto body = lex_ml_literal_body::invoke(inner_loc); + + const auto close = lex_ml_literal_string_close::invoke(inner_loc); + if(!close) + { + throw internal_error(format_underline( + "parse_ml_literal_string: invalid token", + {{source_location(inner_loc), "should be '''"}}), + source_location(inner_loc)); + } + // `lex_ml_literal_string_close` allows 3 to 5 `'`s to allow 1 or 2 `'`s + // at just before the delimiter. Here, we need to attach `'`s at the + // end of the string body, if it exists. + // For detail, see the definition of `lex_ml_basic_string_close`. + + std::string retval = body.unwrap().str(); + assert(std::all_of(close.unwrap().first(), close.unwrap().last(), + [](const char c) noexcept {return c == '\'';})); + switch(close.unwrap().size()) + { + case 3: {break;} + case 4: {retval += "'"; break;} + case 5: {retval += "''"; break;} + default: + { + throw internal_error(format_underline( + "parse_ml_literal_string: closing delimiter has invalid length", + {{source_location(inner_loc), "end of this"}}), + source_location(inner_loc)); + } + } + + const auto err_loc = check_utf8_validity(token.unwrap().str()); + if(err_loc == -1) + { + return ok(std::make_pair(toml::string(retval, toml::string_t::literal), + token.unwrap())); + } + else + { + inner_loc.reset(first); + inner_loc.advance(err_loc); + throw syntax_error(format_underline( + "parse_ml_basic_string: invalid utf8 sequence found", + {{source_location(inner_loc), "here"}}), + source_location(inner_loc)); + } + } + else + { + loc.reset(first); // rollback + return err(format_underline("toml::parse_ml_literal_string: " + "the next token is not a valid multiline literal string", + {{source_location(loc), "here"}})); + } +} + +inline result, std::string> +parse_literal_string(location& loc) +{ + const auto first = loc.iter(); + if(const auto token = lex_literal_string::invoke(loc)) + { + location inner_loc(loc.name(), token.unwrap().str()); + + const auto open = lex_apostrophe::invoke(inner_loc); + if(!open) + { + throw internal_error(format_underline( + "parse_literal_string: invalid token", + {{source_location(inner_loc), "should be '"}}), + source_location(inner_loc)); + } + + const auto body = repeat::invoke(inner_loc); + + const auto close = lex_apostrophe::invoke(inner_loc); + if(!close) + { + throw internal_error(format_underline( + "parse_literal_string: invalid token", + {{source_location(inner_loc), "should be '"}}), + source_location(inner_loc)); + } + + const auto err_loc = check_utf8_validity(token.unwrap().str()); + if(err_loc == -1) + { + return ok(std::make_pair( + toml::string(body.unwrap().str(), toml::string_t::literal), + token.unwrap())); + } + else + { + inner_loc.reset(first); + inner_loc.advance(err_loc); + throw syntax_error(format_underline( + "parse_ml_basic_string: invalid utf8 sequence found", + {{source_location(inner_loc), "here"}}), + source_location(inner_loc)); + } + } + else + { + loc.reset(first); // rollback + return err(format_underline("toml::parse_literal_string: " + "the next token is not a valid literal string", + {{source_location(loc), "here"}})); + } +} + +inline result, std::string> +parse_string(location& loc) +{ + if(loc.iter() != loc.end() && *(loc.iter()) == '"') + { + if(loc.iter() + 1 != loc.end() && *(loc.iter() + 1) == '"' && + loc.iter() + 2 != loc.end() && *(loc.iter() + 2) == '"') + { + return parse_ml_basic_string(loc); + } + else + { + return parse_basic_string(loc); + } + } + else if(loc.iter() != loc.end() && *(loc.iter()) == '\'') + { + if(loc.iter() + 1 != loc.end() && *(loc.iter() + 1) == '\'' && + loc.iter() + 2 != loc.end() && *(loc.iter() + 2) == '\'') + { + return parse_ml_literal_string(loc); + } + else + { + return parse_literal_string(loc); + } + } + return err(format_underline("toml::parse_string: ", + {{source_location(loc), "the next token is not a string"}})); +} + +inline result, std::string> +parse_local_date(location& loc) +{ + const auto first = loc.iter(); + if(const auto token = lex_local_date::invoke(loc)) + { + location inner_loc(loc.name(), token.unwrap().str()); + + const auto y = lex_date_fullyear::invoke(inner_loc); + if(!y || inner_loc.iter() == inner_loc.end() || *inner_loc.iter() != '-') + { + throw internal_error(format_underline( + "toml::parse_inner_local_date: invalid year format", + {{source_location(inner_loc), "should be `-`"}}), + source_location(inner_loc)); + } + inner_loc.advance(); + const auto m = lex_date_month::invoke(inner_loc); + if(!m || inner_loc.iter() == inner_loc.end() || *inner_loc.iter() != '-') + { + throw internal_error(format_underline( + "toml::parse_local_date: invalid month format", + {{source_location(inner_loc), "should be `-`"}}), + source_location(inner_loc)); + } + inner_loc.advance(); + const auto d = lex_date_mday::invoke(inner_loc); + if(!d) + { + throw internal_error(format_underline( + "toml::parse_local_date: invalid day format", + {{source_location(inner_loc), "here"}}), + source_location(inner_loc)); + } + + const auto year = static_cast(from_string(y.unwrap().str(), 0)); + const auto month = static_cast(from_string(m.unwrap().str(), 0)); + const auto day = static_cast(from_string(d.unwrap().str(), 0)); + + // We briefly check whether the input date is valid or not. But here, we + // only check if the RFC3339 compliance. + // Actually there are several special date that does not exist, + // because of historical reasons, such as 1582/10/5-1582/10/14 (only in + // several countries). But here, we do not care about such a complicated + // rule. It makes the code complicated and there is only low probability + // that such a specific date is needed in practice. If someone need to + // validate date accurately, that means that the one need a specialized + // library for their purpose in a different layer. + { + const bool is_leap = (year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0)); + const auto max_day = (month == 2) ? (is_leap ? 29 : 28) : + ((month == 4 || month == 6 || month == 9 || month == 11) ? 30 : 31); + + if((month < 1 || 12 < month) || (day < 1 || max_day < day)) + { + throw syntax_error(format_underline("toml::parse_date: " + "invalid date: it does not conform RFC3339.", {{ + source_location(loc), "month should be 01-12, day should be" + " 01-28,29,30,31, depending on month/year." + }}), source_location(inner_loc)); + } + } + return ok(std::make_pair(local_date(year, static_cast(month - 1), day), + token.unwrap())); + } + else + { + loc.reset(first); + return err(format_underline("toml::parse_local_date: ", + {{source_location(loc), "the next token is not a local_date"}})); + } +} + +inline result, std::string> +parse_local_time(location& loc) +{ + const auto first = loc.iter(); + if(const auto token = lex_local_time::invoke(loc)) + { + location inner_loc(loc.name(), token.unwrap().str()); + + const auto h = lex_time_hour::invoke(inner_loc); + if(!h || inner_loc.iter() == inner_loc.end() || *inner_loc.iter() != ':') + { + throw internal_error(format_underline( + "toml::parse_local_time: invalid year format", + {{source_location(inner_loc), "should be `:`"}}), + source_location(inner_loc)); + } + inner_loc.advance(); + const auto m = lex_time_minute::invoke(inner_loc); + if(!m || inner_loc.iter() == inner_loc.end() || *inner_loc.iter() != ':') + { + throw internal_error(format_underline( + "toml::parse_local_time: invalid month format", + {{source_location(inner_loc), "should be `:`"}}), + source_location(inner_loc)); + } + inner_loc.advance(); + const auto s = lex_time_second::invoke(inner_loc); + if(!s) + { + throw internal_error(format_underline( + "toml::parse_local_time: invalid second format", + {{source_location(inner_loc), "here"}}), + source_location(inner_loc)); + } + + const int hour = from_string(h.unwrap().str(), 0); + const int minute = from_string(m.unwrap().str(), 0); + const int second = from_string(s.unwrap().str(), 0); + + if((hour < 0 || 23 < hour) || (minute < 0 || 59 < minute) || + (second < 0 || 60 < second)) // it may be leap second + { + throw syntax_error(format_underline("toml::parse_time: " + "invalid time: it does not conform RFC3339.", {{ + source_location(loc), "hour should be 00-23, minute should be" + " 00-59, second should be 00-60 (depending on the leap" + " second rules.)"}}), source_location(inner_loc)); + } + + local_time time(hour, minute, second, 0, 0); + + const auto before_secfrac = inner_loc.iter(); + if(const auto secfrac = lex_time_secfrac::invoke(inner_loc)) + { + auto sf = secfrac.unwrap().str(); + sf.erase(sf.begin()); // sf.front() == '.' + switch(sf.size() % 3) + { + case 2: sf += '0'; break; + case 1: sf += "00"; break; + case 0: break; + default: break; + } + if(sf.size() >= 9) + { + time.millisecond = from_string(sf.substr(0, 3), 0u); + time.microsecond = from_string(sf.substr(3, 3), 0u); + time.nanosecond = from_string(sf.substr(6, 3), 0u); + } + else if(sf.size() >= 6) + { + time.millisecond = from_string(sf.substr(0, 3), 0u); + time.microsecond = from_string(sf.substr(3, 3), 0u); + } + else if(sf.size() >= 3) + { + time.millisecond = from_string(sf, 0u); + time.microsecond = 0u; + } + } + else + { + if(before_secfrac != inner_loc.iter()) + { + throw internal_error(format_underline( + "toml::parse_local_time: invalid subsecond format", + {{source_location(inner_loc), "here"}}), + source_location(inner_loc)); + } + } + return ok(std::make_pair(time, token.unwrap())); + } + else + { + loc.reset(first); + return err(format_underline("toml::parse_local_time: ", + {{source_location(loc), "the next token is not a local_time"}})); + } +} + +inline result, std::string> +parse_local_datetime(location& loc) +{ + const auto first = loc.iter(); + if(const auto token = lex_local_date_time::invoke(loc)) + { + location inner_loc(loc.name(), token.unwrap().str()); + const auto date = parse_local_date(inner_loc); + if(!date || inner_loc.iter() == inner_loc.end()) + { + throw internal_error(format_underline( + "toml::parse_local_datetime: invalid datetime format", + {{source_location(inner_loc), "date, not datetime"}}), + source_location(inner_loc)); + } + const char delim = *(inner_loc.iter()); + if(delim != 'T' && delim != 't' && delim != ' ') + { + throw internal_error(format_underline( + "toml::parse_local_datetime: invalid datetime format", + {{source_location(inner_loc), "should be `T` or ` ` (space)"}}), + source_location(inner_loc)); + } + inner_loc.advance(); + const auto time = parse_local_time(inner_loc); + if(!time) + { + throw internal_error(format_underline( + "toml::parse_local_datetime: invalid datetime format", + {{source_location(inner_loc), "invalid time format"}}), + source_location(inner_loc)); + } + return ok(std::make_pair( + local_datetime(date.unwrap().first, time.unwrap().first), + token.unwrap())); + } + else + { + loc.reset(first); + return err(format_underline("toml::parse_local_datetime: ", + {{source_location(loc), "the next token is not a local_datetime"}})); + } +} + +inline result, std::string> +parse_offset_datetime(location& loc) +{ + const auto first = loc.iter(); + if(const auto token = lex_offset_date_time::invoke(loc)) + { + location inner_loc(loc.name(), token.unwrap().str()); + const auto datetime = parse_local_datetime(inner_loc); + if(!datetime || inner_loc.iter() == inner_loc.end()) + { + throw internal_error(format_underline( + "toml::parse_offset_datetime: invalid datetime format", + {{source_location(inner_loc), "date, not datetime"}}), + source_location(inner_loc)); + } + time_offset offset(0, 0); + if(const auto ofs = lex_time_numoffset::invoke(inner_loc)) + { + const auto str = ofs.unwrap().str(); + + const auto hour = from_string(str.substr(1,2), 0); + const auto minute = from_string(str.substr(4,2), 0); + + if((hour < 0 || 23 < hour) || (minute < 0 || 59 < minute)) + { + throw syntax_error(format_underline("toml::parse_offset_datetime: " + "invalid offset: it does not conform RFC3339.", {{ + source_location(loc), "month should be 01-12, day should be" + " 01-28,29,30,31, depending on month/year." + }}), source_location(inner_loc)); + } + + if(str.front() == '+') + { + offset = time_offset(hour, minute); + } + else + { + offset = time_offset(-hour, -minute); + } + } + else if(*inner_loc.iter() != 'Z' && *inner_loc.iter() != 'z') + { + throw internal_error(format_underline( + "toml::parse_offset_datetime: invalid datetime format", + {{source_location(inner_loc), "should be `Z` or `+HH:MM`"}}), + source_location(inner_loc)); + } + return ok(std::make_pair(offset_datetime(datetime.unwrap().first, offset), + token.unwrap())); + } + else + { + loc.reset(first); + return err(format_underline("toml::parse_offset_datetime: ", + {{source_location(loc), "the next token is not a offset_datetime"}})); + } +} + +inline result, std::string> +parse_simple_key(location& loc) +{ + if(const auto bstr = parse_basic_string(loc)) + { + return ok(std::make_pair(bstr.unwrap().first.str, bstr.unwrap().second)); + } + if(const auto lstr = parse_literal_string(loc)) + { + return ok(std::make_pair(lstr.unwrap().first.str, lstr.unwrap().second)); + } + if(const auto bare = lex_unquoted_key::invoke(loc)) + { + const auto reg = bare.unwrap(); + return ok(std::make_pair(reg.str(), reg)); + } + return err(format_underline("toml::parse_simple_key: ", + {{source_location(loc), "the next token is not a simple key"}})); +} + +// dotted key become vector of keys +inline result, region>, std::string> +parse_key(location& loc) +{ + const auto first = loc.iter(); + // dotted key -> `foo.bar.baz` where several single keys are chained by + // dots. Whitespaces between keys and dots are allowed. + if(const auto token = lex_dotted_key::invoke(loc)) + { + const auto reg = token.unwrap(); + location inner_loc(loc.name(), reg.str()); + std::vector keys; + + while(inner_loc.iter() != inner_loc.end()) + { + lex_ws::invoke(inner_loc); + if(const auto k = parse_simple_key(inner_loc)) + { + keys.push_back(k.unwrap().first); + } + else + { + throw internal_error(format_underline( + "toml::detail::parse_key: dotted key contains invalid key", + {{source_location(inner_loc), k.unwrap_err()}}), + source_location(inner_loc)); + } + + lex_ws::invoke(inner_loc); + if(inner_loc.iter() == inner_loc.end()) + { + break; + } + else if(*inner_loc.iter() == '.') + { + inner_loc.advance(); // to skip `.` + } + else + { + throw internal_error(format_underline("toml::parse_key: " + "dotted key contains invalid key ", + {{source_location(inner_loc), "should be `.`"}}), + source_location(inner_loc)); + } + } + return ok(std::make_pair(keys, reg)); + } + loc.reset(first); + + // simple_key: a single (basic_string|literal_string|bare key) + if(const auto smpl = parse_simple_key(loc)) + { + return ok(std::make_pair(std::vector(1, smpl.unwrap().first), + smpl.unwrap().second)); + } + return err(format_underline("toml::parse_key: an invalid key appeared.", + {{source_location(loc), "is not a valid key"}}, { + "bare keys : non-empty strings composed only of [A-Za-z0-9_-].", + "quoted keys: same as \"basic strings\" or 'literal strings'.", + "dotted keys: sequence of bare or quoted keys joined with a dot." + })); +} + +// forward-decl to implement parse_array and parse_table +template +result parse_value(location&); + +template +result, std::string> +parse_array(location& loc) +{ + using value_type = Value; + using array_type = typename value_type::array_type; + + const auto first = loc.iter(); + if(loc.iter() == loc.end()) + { + return err("toml::parse_array: input is empty"); + } + if(*loc.iter() != '[') + { + return err("toml::parse_array: token is not an array"); + } + loc.advance(); + + using lex_ws_comment_newline = repeat< + either, unlimited>; + + array_type retval; + while(loc.iter() != loc.end()) + { + lex_ws_comment_newline::invoke(loc); // skip + + if(loc.iter() != loc.end() && *loc.iter() == ']') + { + loc.advance(); // skip ']' + return ok(std::make_pair(retval, + region(loc, first, loc.iter()))); + } + + if(auto val = parse_value(loc)) + { + // After TOML v1.0.0-rc.1, array becomes to be able to have values + // with different types. So here we will omit this by default. + // + // But some of the test-suite checks if the parser accepts a hetero- + // geneous arrays, so we keep this for a while. +#ifdef TOML11_DISALLOW_HETEROGENEOUS_ARRAYS + if(!retval.empty() && retval.front().type() != val.as_ok().type()) + { + auto array_start_loc = loc; + array_start_loc.reset(first); + + throw syntax_error(format_underline("toml::parse_array: " + "type of elements should be the same each other.", { + {source_location(array_start_loc), "array starts here"}, + { + retval.front().location(), + "value has type " + stringize(retval.front().type()) + }, + { + val.unwrap().location(), + "value has different type, " + stringize(val.unwrap().type()) + } + }), source_location(loc)); + } +#endif + retval.push_back(std::move(val.unwrap())); + } + else + { + auto array_start_loc = loc; + array_start_loc.reset(first); + + throw syntax_error(format_underline("toml::parse_array: " + "value having invalid format appeared in an array", { + {source_location(array_start_loc), "array starts here"}, + {source_location(loc), "it is not a valid value."} + }), source_location(loc)); + } + + using lex_array_separator = sequence, character<','>>; + const auto sp = lex_array_separator::invoke(loc); + if(!sp) + { + lex_ws_comment_newline::invoke(loc); + if(loc.iter() != loc.end() && *loc.iter() == ']') + { + loc.advance(); // skip ']' + return ok(std::make_pair(retval, + region(loc, first, loc.iter()))); + } + else + { + auto array_start_loc = loc; + array_start_loc.reset(first); + + throw syntax_error(format_underline("toml::parse_array:" + " missing array separator `,` after a value", { + {source_location(array_start_loc), "array starts here"}, + {source_location(loc), "should be `,`"} + }), source_location(loc)); + } + } + } + loc.reset(first); + throw syntax_error(format_underline("toml::parse_array: " + "array did not closed by `]`", + {{source_location(loc), "should be closed"}}), + source_location(loc)); +} + +template +result, region>, Value>, std::string> +parse_key_value_pair(location& loc) +{ + using value_type = Value; + + const auto first = loc.iter(); + auto key_reg = parse_key(loc); + if(!key_reg) + { + std::string msg = std::move(key_reg.unwrap_err()); + // if the next token is keyvalue-separator, it means that there are no + // key. then we need to show error as "empty key is not allowed". + if(const auto keyval_sep = lex_keyval_sep::invoke(loc)) + { + loc.reset(first); + msg = format_underline("toml::parse_key_value_pair: " + "empty key is not allowed.", + {{source_location(loc), "key expected before '='"}}); + } + return err(std::move(msg)); + } + + const auto kvsp = lex_keyval_sep::invoke(loc); + if(!kvsp) + { + std::string msg; + // if the line contains '=' after the invalid sequence, possibly the + // error is in the key (like, invalid character in bare key). + const auto line_end = std::find(loc.iter(), loc.end(), '\n'); + if(std::find(loc.iter(), line_end, '=') != line_end) + { + msg = format_underline("toml::parse_key_value_pair: " + "invalid format for key", + {{source_location(loc), "invalid character in key"}}, + {"Did you forget '.' to separate dotted-key?", + "Allowed characters for bare key are [0-9a-zA-Z_-]."}); + } + else // if not, the error is lack of key-value separator. + { + msg = format_underline("toml::parse_key_value_pair: " + "missing key-value separator `=`", + {{source_location(loc), "should be `=`"}}); + } + loc.reset(first); + return err(std::move(msg)); + } + + const auto after_kvsp = loc.iter(); // err msg + auto val = parse_value(loc); + if(!val) + { + std::string msg; + loc.reset(after_kvsp); + // check there is something not a comment/whitespace after `=` + if(sequence, maybe, lex_newline>::invoke(loc)) + { + loc.reset(after_kvsp); + msg = format_underline("toml::parse_key_value_pair: " + "missing value after key-value separator '='", + {{source_location(loc), "expected value, but got nothing"}}); + } + else // there is something not a comment/whitespace, so invalid format. + { + msg = std::move(val.unwrap_err()); + } + loc.reset(first); + return err(msg); + } + return ok(std::make_pair(std::move(key_reg.unwrap()), + std::move(val.unwrap()))); +} + +// for error messages. +template +std::string format_dotted_keys(InputIterator first, const InputIterator last) +{ + static_assert(std::is_same::value_type>::value,""); + + std::string retval(*first++); + for(; first != last; ++first) + { + retval += '.'; + retval += *first; + } + return retval; +} + +// forward decl for is_valid_forward_table_definition +result, region>, std::string> +parse_table_key(location& loc); +template +result, std::string> +parse_inline_table(location& loc); + +// The following toml file is allowed. +// ```toml +// [a.b.c] # here, table `a` has element `b`. +// foo = "bar" +// [a] # merge a = {baz = "qux"} to a = {b = {...}} +// baz = "qux" +// ``` +// But the following is not allowed. +// ```toml +// [a] +// b.c.foo = "bar" +// [a] # error! the same table [a] defined! +// baz = "qux" +// ``` +// The following is neither allowed. +// ```toml +// a = { b.c.foo = "bar"} +// [a] # error! the same table [a] defined! +// baz = "qux" +// ``` +// Here, it parses region of `tab->at(k)` as a table key and check the depth +// of the key. If the key region points deeper node, it would be allowed. +// Otherwise, the key points the same node. It would be rejected. +template +bool is_valid_forward_table_definition(const Value& fwd, const Value& inserting, + Iterator key_first, Iterator key_curr, Iterator key_last) +{ + // ------------------------------------------------------------------------ + // check type of the value to be inserted/merged + + std::string inserting_reg = ""; + if(const auto ptr = detail::get_region(inserting)) + { + inserting_reg = ptr->str(); + } + location inserting_def("internal", std::move(inserting_reg)); + if(const auto inlinetable = parse_inline_table(inserting_def)) + { + // check if we are overwriting existing table. + // ```toml + // # NG + // a.b = 42 + // a = {d = 3.14} + // ``` + // Inserting an inline table to a existing super-table is not allowed in + // any case. If we found it, we can reject it without further checking. + return false; + } + + // Valid and invalid cases when inserting to the [a.b] table: + // + // ## Invalid + // + // ```toml + // # invalid + // [a] + // b.c.d = "foo" + // [a.b] # a.b is already defined and closed + // d = "bar" + // ``` + // ```toml + // # invalid + // a = {b.c.d = "foo"} + // [a.b] # a is already defined and inline table is closed + // d = "bar" + // ``` + // ```toml + // # invalid + // a.b.c.d = "foo" + // [a.b] # a.b is already defined and dotted-key table is closed + // d = "bar" + // ``` + // + // ## Valid + // + // ```toml + // # OK. a.b is defined, but is *overwritable* + // [a.b.c] + // d = "foo" + // [a.b] + // d = "bar" + // ``` + // ```toml + // # OK. a.b is defined, but is *overwritable* + // [a] + // b.c.d = "foo" + // b.e = "bar" + // ``` + + // ------------------------------------------------------------------------ + // check table defined before + + std::string internal = ""; + if(const auto ptr = detail::get_region(fwd)) + { + internal = ptr->str(); + } + location def("internal", std::move(internal)); + if(const auto tabkeys = parse_table_key(def)) // [table.key] + { + // table keys always contains all the nodes from the root. + const auto& tks = tabkeys.unwrap().first; + if(std::size_t(std::distance(key_first, key_last)) == tks.size() && + std::equal(tks.begin(), tks.end(), key_first)) + { + // the keys are equivalent. it is not allowed. + return false; + } + // the keys are not equivalent. it is allowed. + return true; + } + if(const auto dotkeys = parse_key(def)) // a.b.c = "foo" + { + // consider the following case. + // [a] + // b.c = {d = 42} + // [a.b.c] + // e = 2.71 + // this defines the table [a.b.c] twice. no? + if(const auto reopening_dotkey_by_table = parse_table_key(inserting_def)) + { + // re-opening a dotkey-defined table by a table is invalid. + // only dotkey can append a key-val. Like: + // ```toml + // a.b.c = "foo" + // a.b.d = "bar" # OK. reopen `a.b` by dotkey + // [a.b] + // e = "bar" # Invalid. re-opening `a.b` by [a.b] is not allowed. + // ``` + return false; + } + + // a dotted key starts from the node representing a table in which the + // dotted key belongs to. + const auto& dks = dotkeys.unwrap().first; + if(std::size_t(std::distance(key_curr, key_last)) == dks.size() && + std::equal(dks.begin(), dks.end(), key_curr)) + { + // the keys are equivalent. it is not allowed. + return false; + } + // the keys are not equivalent. it is allowed. + return true; + } + return false; +} + +template +result +insert_nested_key(typename Value::table_type& root, const Value& v, + InputIterator iter, const InputIterator last, + region key_reg, + const bool is_array_of_table = false) +{ + static_assert(std::is_same::value_type>::value,""); + + using value_type = Value; + using table_type = typename value_type::table_type; + using array_type = typename value_type::array_type; + + const auto first = iter; + assert(iter != last); + + table_type* tab = std::addressof(root); + for(; iter != last; ++iter) // search recursively + { + const key& k = *iter; + if(std::next(iter) == last) // k is the last key + { + // XXX if the value is array-of-tables, there can be several + // tables that are in the same array. in that case, we need to + // find the last element and insert it to there. + if(is_array_of_table) + { + if(tab->count(k) == 1) // there is already an array of table + { + if(tab->at(k).is_table()) + { + // show special err msg for conflicting table + throw syntax_error(format_underline(concat_to_string( + "toml::insert_value: array of table (\"", + format_dotted_keys(first, last), + "\") cannot be defined"), { + {tab->at(k).location(), "table already defined"}, + {v.location(), "this conflicts with the previous table"} + }), v.location()); + } + else if(!(tab->at(k).is_array())) + { + throw syntax_error(format_underline(concat_to_string( + "toml::insert_value: array of table (\"", + format_dotted_keys(first, last), "\") collides with" + " existing value"), { + {tab->at(k).location(), + concat_to_string("this ", tab->at(k).type(), + " value already exists")}, + {v.location(), + "while inserting this array-of-tables"} + }), v.location()); + } + // the above if-else-if checks tab->at(k) is an array + auto& a = tab->at(k).as_array(); + // If table element is defined as [[array_of_tables]], it + // cannot be an empty array. If an array of tables is + // defined as `aot = []`, it cannot be appended. + if(a.empty() || !(a.front().is_table())) + { + throw syntax_error(format_underline(concat_to_string( + "toml::insert_value: array of table (\"", + format_dotted_keys(first, last), "\") collides with" + " existing value"), { + {tab->at(k).location(), + concat_to_string("this ", tab->at(k).type(), + " value already exists")}, + {v.location(), + "while inserting this array-of-tables"} + }), v.location()); + } + // avoid conflicting array of table like the following. + // ```toml + // a = [{b = 42}] # define a as an array of *inline* tables + // [[a]] # a is an array of *multi-line* tables + // b = 54 + // ``` + // Here, from the type information, these cannot be detected + // because inline table is also a table. + // But toml v0.5.0 explicitly says it is invalid. The above + // array-of-tables has a static size and appending to the + // array is invalid. + // In this library, multi-line table value has a region + // that points to the key of the table (e.g. [[a]]). By + // comparing the first two letters in key, we can detect + // the array-of-table is inline or multiline. + if(const auto ptr = detail::get_region(a.front())) + { + if(ptr->str().substr(0,2) != "[[") + { + throw syntax_error(format_underline(concat_to_string( + "toml::insert_value: array of table (\"", + format_dotted_keys(first, last), "\") collides " + "with existing array-of-tables"), { + {tab->at(k).location(), + concat_to_string("this ", tab->at(k).type(), + " value has static size")}, + {v.location(), + "appending it to the statically sized array"} + }), v.location()); + } + } + a.push_back(v); + return ok(true); + } + else // if not, we need to create the array of table + { + // XXX: Consider the following array of tables. + // ```toml + // # This is a comment. + // [[aot]] + // foo = "bar" + // ``` + // Here, the comment is for `aot`. But here, actually two + // values are defined. An array that contains tables, named + // `aot`, and the 0th element of the `aot`, `{foo = "bar"}`. + // Those two are different from each other. But both of them + // points to the same portion of the TOML file, `[[aot]]`, + // so `key_reg.comments()` returns `# This is a comment`. + // If it is assigned as a comment of `aot` defined here, the + // comment will be duplicated. Both the `aot` itself and + // the 0-th element will have the same comment. This causes + // "duplication of the same comments" bug when the data is + // serialized. + // Next, consider the following. + // ```toml + // # comment 1 + // aot = [ + // # comment 2 + // {foo = "bar"}, + // ] + // ``` + // In this case, we can distinguish those two comments. So + // here we need to add "comment 1" to the `aot` and + // "comment 2" to the 0th element of that. + // To distinguish those two, we check the key region. + std::vector comments{/* empty by default */}; + if(key_reg.str().substr(0, 2) != "[[") + { + comments = key_reg.comments(); + } + value_type aot(array_type(1, v), key_reg, std::move(comments)); + tab->insert(std::make_pair(k, aot)); + return ok(true); + } + } // end if(array of table) + + if(tab->count(k) == 1) + { + if(tab->at(k).is_table() && v.is_table()) + { + if(!is_valid_forward_table_definition( + tab->at(k), v, first, iter, last)) + { + throw syntax_error(format_underline(concat_to_string( + "toml::insert_value: table (\"", + format_dotted_keys(first, last), + "\") already exists."), { + {tab->at(k).location(), "table already exists here"}, + {v.location(), "table defined twice"} + }), v.location()); + } + // to allow the following toml file. + // [a.b.c] + // d = 42 + // [a] + // e = 2.71 + auto& t = tab->at(k).as_table(); + for(const auto& kv : v.as_table()) + { + if(tab->at(k).contains(kv.first)) + { + throw syntax_error(format_underline(concat_to_string( + "toml::insert_value: value (\"", + format_dotted_keys(first, last), + "\") already exists."), { + {t.at(kv.first).location(), "already exists here"}, + {v.location(), "this defined twice"} + }), v.location()); + } + t[kv.first] = kv.second; + } + detail::change_region(tab->at(k), key_reg); + return ok(true); + } + else if(v.is_table() && + tab->at(k).is_array() && + tab->at(k).as_array().size() > 0 && + tab->at(k).as_array().front().is_table()) + { + throw syntax_error(format_underline(concat_to_string( + "toml::insert_value: array of tables (\"", + format_dotted_keys(first, last), "\") already exists."), { + {tab->at(k).location(), "array of tables defined here"}, + {v.location(), "table conflicts with the previous array of table"} + }), v.location()); + } + else + { + throw syntax_error(format_underline(concat_to_string( + "toml::insert_value: value (\"", + format_dotted_keys(first, last), "\") already exists."), { + {tab->at(k).location(), "value already exists here"}, + {v.location(), "value defined twice"} + }), v.location()); + } + } + tab->insert(std::make_pair(k, v)); + return ok(true); + } + else // k is not the last one, we should insert recursively + { + // if there is no corresponding value, insert it first. + // related: you don't need to write + // # [x] + // # [x.y] + // to write + // [x.y.z] + if(tab->count(k) == 0) + { + // a table that is defined implicitly doesn't have any comments. + (*tab)[k] = value_type(table_type{}, key_reg, {/*no comment*/}); + } + + // type checking... + if(tab->at(k).is_table()) + { + // According to toml-lang/toml:36d3091b3 "Clarify that inline + // tables are immutable", check if it adds key-value pair to an + // inline table. + if(const auto* ptr = get_region(tab->at(k))) + { + // here, if the value is a (multi-line) table, the region + // should be something like `[table-name]`. + if(ptr->front() == '{') + { + throw syntax_error(format_underline(concat_to_string( + "toml::insert_value: inserting to an inline table (", + format_dotted_keys(first, std::next(iter)), + ") but inline tables are immutable"), { + {tab->at(k).location(), "inline tables are immutable"}, + {v.location(), "inserting this"} + }), v.location()); + } + } + tab = std::addressof((*tab)[k].as_table()); + } + else if(tab->at(k).is_array()) // inserting to array-of-tables? + { + auto& a = (*tab)[k].as_array(); + if(!a.back().is_table()) + { + throw syntax_error(format_underline(concat_to_string( + "toml::insert_value: target (", + format_dotted_keys(first, std::next(iter)), + ") is neither table nor an array of tables"), { + {a.back().location(), concat_to_string( + "actual type is ", a.back().type())}, + {v.location(), "inserting this"} + }), v.location()); + } + tab = std::addressof(a.back().as_table()); + } + else + { + throw syntax_error(format_underline(concat_to_string( + "toml::insert_value: target (", + format_dotted_keys(first, std::next(iter)), + ") is neither table nor an array of tables"), { + {tab->at(k).location(), concat_to_string( + "actual type is ", tab->at(k).type())}, + {v.location(), "inserting this"} + }), v.location()); + } + } + } + return err(std::string("toml::detail::insert_nested_key: never reach here")); +} + +template +result, std::string> +parse_inline_table(location& loc) +{ + using value_type = Value; + using table_type = typename value_type::table_type; + + const auto first = loc.iter(); + table_type retval; + if(!(loc.iter() != loc.end() && *loc.iter() == '{')) + { + return err(format_underline("toml::parse_inline_table: ", + {{source_location(loc), "the next token is not an inline table"}})); + } + loc.advance(); + + // check if the inline table is an empty table = { } + maybe::invoke(loc); + if(loc.iter() != loc.end() && *loc.iter() == '}') + { + loc.advance(); // skip `}` + return ok(std::make_pair(retval, region(loc, first, loc.iter()))); + } + + // it starts from "{". it should be formatted as inline-table + while(loc.iter() != loc.end()) + { + const auto kv_r = parse_key_value_pair(loc); + if(!kv_r) + { + return err(kv_r.unwrap_err()); + } + + const auto& kvpair = kv_r.unwrap(); + const std::vector& keys = kvpair.first.first; + const auto& key_reg = kvpair.first.second; + const value_type& val = kvpair.second; + + const auto inserted = + insert_nested_key(retval, val, keys.begin(), keys.end(), key_reg); + if(!inserted) + { + throw internal_error("toml::parse_inline_table: " + "failed to insert value into table: " + inserted.unwrap_err(), + source_location(loc)); + } + + using lex_table_separator = sequence, character<','>>; + const auto sp = lex_table_separator::invoke(loc); + + if(!sp) + { + maybe::invoke(loc); + + if(loc.iter() == loc.end()) + { + throw syntax_error(format_underline( + "toml::parse_inline_table: missing table separator `}` ", + {{source_location(loc), "should be `}`"}}), + source_location(loc)); + } + else if(*loc.iter() == '}') + { + loc.advance(); // skip `}` + return ok(std::make_pair( + retval, region(loc, first, loc.iter()))); + } + else if(*loc.iter() == '#' || *loc.iter() == '\r' || *loc.iter() == '\n') + { + throw syntax_error(format_underline( + "toml::parse_inline_table: missing curly brace `}`", + {{source_location(loc), "should be `}`"}}), + source_location(loc)); + } + else + { + throw syntax_error(format_underline( + "toml::parse_inline_table: missing table separator `,` ", + {{source_location(loc), "should be `,`"}}), + source_location(loc)); + } + } + else // `,` is found + { + maybe::invoke(loc); + if(loc.iter() != loc.end() && *loc.iter() == '}') + { + throw syntax_error(format_underline( + "toml::parse_inline_table: trailing comma is not allowed in" + " an inline table", + {{source_location(loc), "should be `}`"}}), + source_location(loc)); + } + } + } + loc.reset(first); + throw syntax_error(format_underline("toml::parse_inline_table: " + "inline table did not closed by `}`", + {{source_location(loc), "should be closed"}}), + source_location(loc)); +} + +inline result guess_number_type(const location& l) +{ + // This function tries to find some (common) mistakes by checking characters + // that follows the last character of a value. But it is often difficult + // because some non-newline characters can appear after a value. E.g. + // spaces, tabs, commas (in an array or inline table), closing brackets + // (of an array or inline table), comment-sign (#). Since this function + // does not parse further, those characters are always allowed to be there. + location loc = l; + + if(lex_offset_date_time::invoke(loc)) {return ok(value_t::offset_datetime);} + loc.reset(l.iter()); + + if(lex_local_date_time::invoke(loc)) + { + // bad offset may appear after this. + if(loc.iter() != loc.end() && (*loc.iter() == '+' || *loc.iter() == '-' + || *loc.iter() == 'Z' || *loc.iter() == 'z')) + { + return err(format_underline("bad offset: should be [+-]HH:MM or Z", + {{source_location(loc), "[+-]HH:MM or Z"}}, + {"pass: +09:00, -05:30", "fail: +9:00, -5:30"})); + } + return ok(value_t::local_datetime); + } + loc.reset(l.iter()); + + if(lex_local_date::invoke(loc)) + { + // bad time may appear after this. + // A space is allowed as a delimiter between local time. But there are + // both cases in which a space becomes valid or invalid. + // - invalid: 2019-06-16 7:00:00 + // - valid : 2019-06-16 07:00:00 + if(loc.iter() != loc.end()) + { + const auto c = *loc.iter(); + if(c == 'T' || c == 't') + { + return err(format_underline("bad time: should be HH:MM:SS.subsec", + {{source_location(loc), "HH:MM:SS.subsec"}}, + {"pass: 1979-05-27T07:32:00, 1979-05-27 07:32:00.999999", + "fail: 1979-05-27T7:32:00, 1979-05-27 17:32"})); + } + if('0' <= c && c <= '9') + { + return err(format_underline("bad time: missing T", + {{source_location(loc), "T or space required here"}}, + {"pass: 1979-05-27T07:32:00, 1979-05-27 07:32:00.999999", + "fail: 1979-05-27T7:32:00, 1979-05-27 7:32"})); + } + if(c == ' ' && std::next(loc.iter()) != loc.end() && + ('0' <= *std::next(loc.iter()) && *std::next(loc.iter())<= '9')) + { + loc.advance(); + return err(format_underline("bad time: should be HH:MM:SS.subsec", + {{source_location(loc), "HH:MM:SS.subsec"}}, + {"pass: 1979-05-27T07:32:00, 1979-05-27 07:32:00.999999", + "fail: 1979-05-27T7:32:00, 1979-05-27 7:32"})); + } + } + return ok(value_t::local_date); + } + loc.reset(l.iter()); + + if(lex_local_time::invoke(loc)) {return ok(value_t::local_time);} + loc.reset(l.iter()); + + if(lex_float::invoke(loc)) + { + if(loc.iter() != loc.end() && *loc.iter() == '_') + { + return err(format_underline("bad float: `_` should be surrounded by digits", + {{source_location(loc), "here"}}, + {"pass: +1.0, -2e-2, 3.141_592_653_589, inf, nan", + "fail: .0, 1., _1.0, 1.0_, 1_.0, 1.0__0"})); + } + return ok(value_t::floating); + } + loc.reset(l.iter()); + + if(lex_integer::invoke(loc)) + { + if(loc.iter() != loc.end()) + { + const auto c = *loc.iter(); + if(c == '_') + { + return err(format_underline("bad integer: `_` should be surrounded by digits", + {{source_location(loc), "here"}}, + {"pass: -42, 1_000, 1_2_3_4_5, 0xC0FFEE, 0b0010, 0o755", + "fail: 1__000, 0123"})); + } + if('0' <= c && c <= '9') + { + // leading zero. point '0' + loc.retrace(); + return err(format_underline("bad integer: leading zero", + {{source_location(loc), "here"}}, + {"pass: -42, 1_000, 1_2_3_4_5, 0xC0FFEE, 0b0010, 0o755", + "fail: 1__000, 0123"})); + } + if(c == ':' || c == '-') + { + return err(format_underline("bad datetime: invalid format", + {{source_location(loc), "here"}}, + {"pass: 1979-05-27T07:32:00-07:00, 1979-05-27 07:32:00.999999Z", + "fail: 1979-05-27T7:32:00-7:00, 1979-05-27 7:32-00:30"})); + } + if(c == '.' || c == 'e' || c == 'E') + { + return err(format_underline("bad float: invalid format", + {{source_location(loc), "here"}}, + {"pass: +1.0, -2e-2, 3.141_592_653_589, inf, nan", + "fail: .0, 1., _1.0, 1.0_, 1_.0, 1.0__0"})); + } + } + return ok(value_t::integer); + } + if(loc.iter() != loc.end() && *loc.iter() == '.') + { + return err(format_underline("bad float: invalid format", + {{source_location(loc), "integer part required before this"}}, + {"pass: +1.0, -2e-2, 3.141_592_653_589, inf, nan", + "fail: .0, 1., _1.0, 1.0_, 1_.0, 1.0__0"})); + } + if(loc.iter() != loc.end() && *loc.iter() == '_') + { + return err(format_underline("bad number: `_` should be surrounded by digits", + {{source_location(loc), "`_` is not surrounded by digits"}}, + {"pass: -42, 1_000, 1_2_3_4_5, 0xC0FFEE, 0b0010, 0o755", + "fail: 1__000, 0123"})); + } + return err(format_underline("bad format: unknown value appeared", + {{source_location(loc), "here"}})); +} + +inline result guess_value_type(const location& loc) +{ + switch(*loc.iter()) + { + case '"' : {return ok(value_t::string); } + case '\'': {return ok(value_t::string); } + case 't' : {return ok(value_t::boolean); } + case 'f' : {return ok(value_t::boolean); } + case '[' : {return ok(value_t::array); } + case '{' : {return ok(value_t::table); } + case 'i' : {return ok(value_t::floating);} // inf. + case 'n' : {return ok(value_t::floating);} // nan. + default : {return guess_number_type(loc);} + } +} + +template +result +parse_value_helper(result, std::string> rslt) +{ + if(rslt.is_ok()) + { + auto comments = rslt.as_ok().second.comments(); + return ok(Value(std::move(rslt.as_ok()), std::move(comments))); + } + else + { + return err(std::move(rslt.as_err())); + } +} + +template +result parse_value(location& loc) +{ + const auto first = loc.iter(); + if(first == loc.end()) + { + return err(format_underline("toml::parse_value: input is empty", + {{source_location(loc), ""}})); + } + + const auto type = guess_value_type(loc); + if(!type) + { + return err(type.unwrap_err()); + } + + switch(type.unwrap()) + { + case value_t::boolean : {return parse_value_helper(parse_boolean(loc) );} + case value_t::integer : {return parse_value_helper(parse_integer(loc) );} + case value_t::floating : {return parse_value_helper(parse_floating(loc) );} + case value_t::string : {return parse_value_helper(parse_string(loc) );} + case value_t::offset_datetime: {return parse_value_helper(parse_offset_datetime(loc) );} + case value_t::local_datetime : {return parse_value_helper(parse_local_datetime(loc) );} + case value_t::local_date : {return parse_value_helper(parse_local_date(loc) );} + case value_t::local_time : {return parse_value_helper(parse_local_time(loc) );} + case value_t::array : {return parse_value_helper(parse_array(loc) );} + case value_t::table : {return parse_value_helper(parse_inline_table(loc));} + default: + { + const auto msg = format_underline("toml::parse_value: " + "unknown token appeared", {{source_location(loc), "unknown"}}); + loc.reset(first); + return err(msg); + } + } +} + +inline result, region>, std::string> +parse_table_key(location& loc) +{ + if(auto token = lex_std_table::invoke(loc)) + { + location inner_loc(loc.name(), token.unwrap().str()); + + const auto open = lex_std_table_open::invoke(inner_loc); + if(!open || inner_loc.iter() == inner_loc.end()) + { + throw internal_error(format_underline( + "toml::parse_table_key: no `[`", + {{source_location(inner_loc), "should be `[`"}}), + source_location(inner_loc)); + } + // to skip [ a . b . c ] + // ^----------- this whitespace + lex_ws::invoke(inner_loc); + const auto keys = parse_key(inner_loc); + if(!keys) + { + throw internal_error(format_underline( + "toml::parse_table_key: invalid key", + {{source_location(inner_loc), "not key"}}), + source_location(inner_loc)); + } + // to skip [ a . b . c ] + // ^-- this whitespace + lex_ws::invoke(inner_loc); + const auto close = lex_std_table_close::invoke(inner_loc); + if(!close) + { + throw internal_error(format_underline( + "toml::parse_table_key: no `]`", + {{source_location(inner_loc), "should be `]`"}}), + source_location(inner_loc)); + } + + // after [table.key], newline or EOF(empty table) required. + if(loc.iter() != loc.end()) + { + using lex_newline_after_table_key = + sequence, maybe, lex_newline>; + const auto nl = lex_newline_after_table_key::invoke(loc); + if(!nl) + { + throw syntax_error(format_underline( + "toml::parse_table_key: newline required after [table.key]", + {{source_location(loc), "expected newline"}}), + source_location(loc)); + } + } + return ok(std::make_pair(keys.unwrap().first, token.unwrap())); + } + else + { + return err(format_underline("toml::parse_table_key: " + "not a valid table key", {{source_location(loc), "here"}})); + } +} + +inline result, region>, std::string> +parse_array_table_key(location& loc) +{ + if(auto token = lex_array_table::invoke(loc)) + { + location inner_loc(loc.name(), token.unwrap().str()); + + const auto open = lex_array_table_open::invoke(inner_loc); + if(!open || inner_loc.iter() == inner_loc.end()) + { + throw internal_error(format_underline( + "toml::parse_array_table_key: no `[[`", + {{source_location(inner_loc), "should be `[[`"}}), + source_location(inner_loc)); + } + lex_ws::invoke(inner_loc); + const auto keys = parse_key(inner_loc); + if(!keys) + { + throw internal_error(format_underline( + "toml::parse_array_table_key: invalid key", + {{source_location(inner_loc), "not a key"}}), + source_location(inner_loc)); + } + lex_ws::invoke(inner_loc); + const auto close = lex_array_table_close::invoke(inner_loc); + if(!close) + { + throw internal_error(format_underline( + "toml::parse_table_key: no `]]`", + {{source_location(inner_loc), "should be `]]`"}}), + source_location(inner_loc)); + } + + // after [[table.key]], newline or EOF(empty table) required. + if(loc.iter() != loc.end()) + { + using lex_newline_after_table_key = + sequence, maybe, lex_newline>; + const auto nl = lex_newline_after_table_key::invoke(loc); + if(!nl) + { + throw syntax_error(format_underline("toml::" + "parse_array_table_key: newline required after [[table.key]]", + {{source_location(loc), "expected newline"}}), + source_location(loc)); + } + } + return ok(std::make_pair(keys.unwrap().first, token.unwrap())); + } + else + { + return err(format_underline("toml::parse_array_table_key: " + "not a valid table key", {{source_location(loc), "here"}})); + } +} + +// parse table body (key-value pairs until the iter hits the next [tablekey]) +template +result +parse_ml_table(location& loc) +{ + using value_type = Value; + using table_type = typename value_type::table_type; + + const auto first = loc.iter(); + if(first == loc.end()) + { + return ok(table_type{}); + } + + // XXX at lest one newline is needed. + using skip_line = repeat< + sequence, maybe, lex_newline>, at_least<1>>; + skip_line::invoke(loc); + lex_ws::invoke(loc); + + table_type tab; + while(loc.iter() != loc.end()) + { + lex_ws::invoke(loc); + const auto before = loc.iter(); + if(const auto tmp = parse_array_table_key(loc)) // next table found + { + loc.reset(before); + return ok(tab); + } + if(const auto tmp = parse_table_key(loc)) // next table found + { + loc.reset(before); + return ok(tab); + } + + if(const auto kv = parse_key_value_pair(loc)) + { + const auto& kvpair = kv.unwrap(); + const std::vector& keys = kvpair.first.first; + const auto& key_reg = kvpair.first.second; + const value_type& val = kvpair.second; + const auto inserted = + insert_nested_key(tab, val, keys.begin(), keys.end(), key_reg); + if(!inserted) + { + return err(inserted.unwrap_err()); + } + } + else + { + return err(kv.unwrap_err()); + } + + // comment lines are skipped by the above function call. + // However, since the `skip_line` requires at least 1 newline, it fails + // if the file ends with ws and/or comment without newline. + // `skip_line` matches `ws? + comment? + newline`, not `ws` or `comment` + // itself. To skip the last ws and/or comment, call lexers. + // It does not matter if these fails, so the return value is discarded. + lex_ws::invoke(loc); + lex_comment::invoke(loc); + + // skip_line is (whitespace? comment? newline)_{1,}. multiple empty lines + // and comments after the last key-value pairs are allowed. + const auto newline = skip_line::invoke(loc); + if(!newline && loc.iter() != loc.end()) + { + const auto before2 = loc.iter(); + lex_ws::invoke(loc); // skip whitespace + const auto msg = format_underline("toml::parse_table: " + "invalid line format", {{source_location(loc), concat_to_string( + "expected newline, but got '", show_char(*loc.iter()), "'.")}}); + loc.reset(before2); + return err(msg); + } + + // the skip_lines only matches with lines that includes newline. + // to skip the last line that includes comment and/or whitespace + // but no newline, call them one more time. + lex_ws::invoke(loc); + lex_comment::invoke(loc); + } + return ok(tab); +} + +template +result parse_toml_file(location& loc) +{ + using value_type = Value; + using table_type = typename value_type::table_type; + + const auto first = loc.iter(); + if(first == loc.end()) + { + // For empty files, return an empty table with an empty region (zero-length). + // Without the region, error messages would miss the filename. + return ok(value_type(table_type{}, region(loc, first, first), {})); + } + + // put the first line as a region of a file + // Here first != loc.end(), so taking std::next is okay + const region file(loc, first, std::next(loc.iter())); + + // The first successive comments that are separated from the first value + // by an empty line are for a file itself. + // ```toml + // # this is a comment for a file. + // + // key = "the first value" + // ``` + // ```toml + // # this is a comment for "the first value". + // key = "the first value" + // ``` + std::vector comments; + using lex_first_comments = sequence< + repeat, lex_comment, lex_newline>, at_least<1>>, + sequence, lex_newline> + >; + if(const auto token = lex_first_comments::invoke(loc)) + { + location inner_loc(loc.name(), token.unwrap().str()); + while(inner_loc.iter() != inner_loc.end()) + { + maybe::invoke(inner_loc); // remove ws if exists + if(lex_newline::invoke(inner_loc)) + { + assert(inner_loc.iter() == inner_loc.end()); + break; // empty line found. + } + auto com = lex_comment::invoke(inner_loc).unwrap().str(); + com.erase(com.begin()); // remove # sign + comments.push_back(std::move(com)); + lex_newline::invoke(inner_loc); + } + } + + table_type data; + // root object is also a table, but without [tablename] + if(const auto tab = parse_ml_table(loc)) + { + data = std::move(tab.unwrap()); + } + else // failed (empty table is regarded as success in parse_ml_table) + { + return err(tab.unwrap_err()); + } + while(loc.iter() != loc.end()) + { + // here, the region of [table] is regarded as the table-key because + // the table body is normally too big and it is not so informative + // if the first key-value pair of the table is shown in the error + // message. + if(const auto tabkey = parse_array_table_key(loc)) + { + const auto tab = parse_ml_table(loc); + if(!tab){return err(tab.unwrap_err());} + + const auto& tk = tabkey.unwrap(); + const auto& keys = tk.first; + const auto& reg = tk.second; + + const auto inserted = insert_nested_key(data, + value_type(tab.unwrap(), reg, reg.comments()), + keys.begin(), keys.end(), reg, + /*is_array_of_table=*/ true); + if(!inserted) {return err(inserted.unwrap_err());} + + continue; + } + if(const auto tabkey = parse_table_key(loc)) + { + const auto tab = parse_ml_table(loc); + if(!tab){return err(tab.unwrap_err());} + + const auto& tk = tabkey.unwrap(); + const auto& keys = tk.first; + const auto& reg = tk.second; + + const auto inserted = insert_nested_key(data, + value_type(tab.unwrap(), reg, reg.comments()), + keys.begin(), keys.end(), reg); + if(!inserted) {return err(inserted.unwrap_err());} + + continue; + } + return err(format_underline("toml::parse_toml_file: " + "unknown line appeared", {{source_location(loc), "unknown format"}})); + } + + return ok(Value(std::move(data), file, comments)); +} + +} // detail + +template class Table = std::unordered_map, + template class Array = std::vector> +basic_value +parse(std::istream& is, const std::string& fname = "unknown file") +{ + using value_type = basic_value; + + const auto beg = is.tellg(); + is.seekg(0, std::ios::end); + const auto end = is.tellg(); + const auto fsize = end - beg; + is.seekg(beg); + + // read whole file as a sequence of char + assert(fsize >= 0); + std::vector letters(static_cast(fsize)); + is.read(letters.data(), fsize); + + // append LF. + // Although TOML does not require LF at the EOF, to make parsing logic + // simpler, we "normalize" the content by adding LF if it does not exist. + // It also checks if the last char is CR, to avoid changing the meaning. + // This is not the *best* way to deal with the last character, but is a + // simple and quick fix. + if(!letters.empty() && letters.back() != '\n' && letters.back() != '\r') + { + letters.push_back('\n'); + } + + detail::location loc(std::move(fname), std::move(letters)); + + // skip BOM if exists. + // XXX component of BOM (like 0xEF) exceeds the representable range of + // signed char, so on some (actually, most) of the environment, these cannot + // be compared to char. However, since we are always out of luck, we need to + // check our chars are equivalent to BOM. To do this, first we need to + // convert char to unsigned char to guarantee the comparability. + if(loc.source()->size() >= 3) + { + std::array BOM; + std::memcpy(BOM.data(), loc.source()->data(), 3); + if(BOM[0] == 0xEF && BOM[1] == 0xBB && BOM[2] == 0xBF) + { + loc.advance(3); // BOM found. skip. + } + } + + const auto data = detail::parse_toml_file(loc); + if(!data) + { + throw syntax_error(data.unwrap_err(), source_location(loc)); + } + return data.unwrap(); +} + +template class Table = std::unordered_map, + template class Array = std::vector> +basic_value parse(const std::string& fname) +{ + std::ifstream ifs(fname.c_str(), std::ios_base::binary); + if(!ifs.good()) + { + throw std::runtime_error("toml::parse: file open error -> " + fname); + } + return parse(ifs, fname); +} + +#ifdef TOML11_HAS_STD_FILESYSTEM +// This function just forwards `parse("filename.toml")` to std::string version +// to avoid the ambiguity in overload resolution. +// +// Both std::string and std::filesystem::path are convertible from const char*. +// Without this, both parse(std::string) and parse(std::filesystem::path) +// matches to parse("filename.toml"). This breaks the existing code. +// +// This function exactly matches to the invocation with c-string. +// So this function is preferred than others and the ambiguity disappears. +template class Table = std::unordered_map, + template class Array = std::vector> +basic_value parse(const char* fname) +{ + return parse(std::string(fname)); +} + +template class Table = std::unordered_map, + template class Array = std::vector> +basic_value parse(const std::filesystem::path& fpath) +{ + std::ifstream ifs(fpath, std::ios_base::binary); + if(!ifs.good()) + { + throw std::runtime_error("toml::parse: file open error -> " + + fpath.string()); + } + return parse(ifs, fpath.string()); +} +#endif // TOML11_HAS_STD_FILESYSTEM + +} // toml +#endif// TOML11_PARSER_HPP diff --git a/src/frontend/qt_sdl/toml/toml/region.hpp b/src/frontend/qt_sdl/toml/toml/region.hpp new file mode 100644 index 0000000000..2e01e51d08 --- /dev/null +++ b/src/frontend/qt_sdl/toml/toml/region.hpp @@ -0,0 +1,417 @@ +// Copyright Toru Niina 2017. +// Distributed under the MIT License. +#ifndef TOML11_REGION_HPP +#define TOML11_REGION_HPP +#include +#include +#include +#include +#include +#include +#include +#include "color.hpp" + +namespace toml +{ +namespace detail +{ + +// helper function to avoid std::string(0, 'c') or std::string(iter, iter) +template +std::string make_string(Iterator first, Iterator last) +{ + if(first == last) {return "";} + return std::string(first, last); +} +inline std::string make_string(std::size_t len, char c) +{ + if(len == 0) {return "";} + return std::string(len, c); +} + +// region_base is a base class of location and region that are defined below. +// it will be used to generate better error messages. +struct region_base +{ + region_base() = default; + virtual ~region_base() = default; + region_base(const region_base&) = default; + region_base(region_base&& ) = default; + region_base& operator=(const region_base&) = default; + region_base& operator=(region_base&& ) = default; + + virtual bool is_ok() const noexcept {return false;} + virtual char front() const noexcept {return '\0';} + + virtual std::string str() const {return std::string("unknown region");} + virtual std::string name() const {return std::string("unknown file");} + virtual std::string line() const {return std::string("unknown line");} + virtual std::string line_num() const {return std::string("?");} + + // length of the region + virtual std::size_t size() const noexcept {return 0;} + // number of characters in the line before the region + virtual std::size_t before() const noexcept {return 0;} + // number of characters in the line after the region + virtual std::size_t after() const noexcept {return 0;} + + virtual std::vector comments() const {return {};} + // ```toml + // # comment_before + // key = "value" # comment_inline + // ``` +}; + +// location represents a position in a container, which contains a file content. +// it can be considered as a region that contains only one character. +// +// it contains pointer to the file content and iterator that points the current +// location. +struct location final : public region_base +{ + using const_iterator = typename std::vector::const_iterator; + using difference_type = typename const_iterator::difference_type; + using source_ptr = std::shared_ptr>; + + location(std::string source_name, std::vector cont) + : source_(std::make_shared>(std::move(cont))), + line_number_(1), source_name_(std::move(source_name)), iter_(source_->cbegin()) + {} + location(std::string source_name, const std::string& cont) + : source_(std::make_shared>(cont.begin(), cont.end())), + line_number_(1), source_name_(std::move(source_name)), iter_(source_->cbegin()) + {} + + location(const location&) = default; + location(location&&) = default; + location& operator=(const location&) = default; + location& operator=(location&&) = default; + ~location() = default; + + bool is_ok() const noexcept override {return static_cast(source_);} + char front() const noexcept override {return *iter_;} + + // this const prohibits codes like `++(loc.iter())`. + const const_iterator iter() const noexcept {return iter_;} + + const_iterator begin() const noexcept {return source_->cbegin();} + const_iterator end() const noexcept {return source_->cend();} + + // XXX `location::line_num()` used to be implemented using `std::count` to + // count a number of '\n'. But with a long toml file (typically, 10k lines), + // it becomes intolerably slow because each time it generates error messages, + // it counts '\n' from thousands of characters. To workaround it, I decided + // to introduce `location::line_number_` member variable and synchronize it + // to the location changes the point to look. So an overload of `iter()` + // which returns mutable reference is removed and `advance()`, `retrace()` + // and `reset()` is added. + void advance(difference_type n = 1) noexcept + { + this->line_number_ += static_cast( + std::count(this->iter_, std::next(this->iter_, n), '\n')); + this->iter_ += n; + return; + } + void retrace(difference_type n = 1) noexcept + { + this->line_number_ -= static_cast( + std::count(std::prev(this->iter_, n), this->iter_, '\n')); + this->iter_ -= n; + return; + } + void reset(const_iterator rollback) noexcept + { + // since c++11, std::distance works in both ways for random-access + // iterators and returns a negative value if `first > last`. + if(0 <= std::distance(rollback, this->iter_)) // rollback < iter + { + this->line_number_ -= static_cast( + std::count(rollback, this->iter_, '\n')); + } + else // iter < rollback [[unlikely]] + { + this->line_number_ += static_cast( + std::count(this->iter_, rollback, '\n')); + } + this->iter_ = rollback; + return; + } + + std::string str() const override {return make_string(1, *this->iter());} + std::string name() const override {return source_name_;} + + std::string line_num() const override + { + return std::to_string(this->line_number_); + } + + std::string line() const override + { + return make_string(this->line_begin(), this->line_end()); + } + + const_iterator line_begin() const noexcept + { + using reverse_iterator = std::reverse_iterator; + return std::find(reverse_iterator(this->iter()), + reverse_iterator(this->begin()), '\n').base(); + } + const_iterator line_end() const noexcept + { + return std::find(this->iter(), this->end(), '\n'); + } + + // location is always points a character. so the size is 1. + std::size_t size() const noexcept override + { + return 1u; + } + std::size_t before() const noexcept override + { + const auto sz = std::distance(this->line_begin(), this->iter()); + assert(sz >= 0); + return static_cast(sz); + } + std::size_t after() const noexcept override + { + const auto sz = std::distance(this->iter(), this->line_end()); + assert(sz >= 0); + return static_cast(sz); + } + + source_ptr const& source() const& noexcept {return source_;} + source_ptr&& source() && noexcept {return std::move(source_);} + + private: + + source_ptr source_; + std::size_t line_number_; + std::string source_name_; + const_iterator iter_; +}; + +// region represents a range in a container, which contains a file content. +// +// it contains pointer to the file content and iterator that points the first +// and last location. +struct region final : public region_base +{ + using const_iterator = typename std::vector::const_iterator; + using source_ptr = std::shared_ptr>; + + // delete default constructor. source_ never be null. + region() = delete; + + explicit region(const location& loc) + : source_(loc.source()), source_name_(loc.name()), + first_(loc.iter()), last_(loc.iter()) + {} + explicit region(location&& loc) + : source_(loc.source()), source_name_(loc.name()), + first_(loc.iter()), last_(loc.iter()) + {} + + region(const location& loc, const_iterator f, const_iterator l) + : source_(loc.source()), source_name_(loc.name()), first_(f), last_(l) + {} + region(location&& loc, const_iterator f, const_iterator l) + : source_(loc.source()), source_name_(loc.name()), first_(f), last_(l) + {} + + region(const region&) = default; + region(region&&) = default; + region& operator=(const region&) = default; + region& operator=(region&&) = default; + ~region() = default; + + region& operator+=(const region& other) + { + // different regions cannot be concatenated + assert(this->begin() == other.begin() && this->end() == other.end() && + this->last_ == other.first_); + + this->last_ = other.last_; + return *this; + } + + bool is_ok() const noexcept override {return static_cast(source_);} + char front() const noexcept override {return *first_;} + + std::string str() const override {return make_string(first_, last_);} + std::string line() const override + { + if(this->contain_newline()) + { + return make_string(this->line_begin(), + std::find(this->line_begin(), this->last(), '\n')); + } + return make_string(this->line_begin(), this->line_end()); + } + std::string line_num() const override + { + return std::to_string(1 + std::count(this->begin(), this->first(), '\n')); + } + + std::size_t size() const noexcept override + { + const auto sz = std::distance(first_, last_); + assert(sz >= 0); + return static_cast(sz); + } + std::size_t before() const noexcept override + { + const auto sz = std::distance(this->line_begin(), this->first()); + assert(sz >= 0); + return static_cast(sz); + } + std::size_t after() const noexcept override + { + const auto sz = std::distance(this->last(), this->line_end()); + assert(sz >= 0); + return static_cast(sz); + } + + bool contain_newline() const noexcept + { + return std::find(this->first(), this->last(), '\n') != this->last(); + } + + const_iterator line_begin() const noexcept + { + using reverse_iterator = std::reverse_iterator; + return std::find(reverse_iterator(this->first()), + reverse_iterator(this->begin()), '\n').base(); + } + const_iterator line_end() const noexcept + { + return std::find(this->last(), this->end(), '\n'); + } + + const_iterator begin() const noexcept {return source_->cbegin();} + const_iterator end() const noexcept {return source_->cend();} + const_iterator first() const noexcept {return first_;} + const_iterator last() const noexcept {return last_;} + + source_ptr const& source() const& noexcept {return source_;} + source_ptr&& source() && noexcept {return std::move(source_);} + + std::string name() const override {return source_name_;} + + std::vector comments() const override + { + // assuming the current region (`*this`) points a value. + // ```toml + // a = "value" + // ^^^^^^^- this region + // ``` + using rev_iter = std::reverse_iterator; + + std::vector com{}; + { + // find comments just before the current region. + // ```toml + // # this should be collected. + // # this also. + // a = value # not this. + // ``` + + // # this is a comment for `a`, not array elements. + // a = [1, 2, 3, 4, 5] + if(this->first() == std::find_if(this->line_begin(), this->first(), + [](const char c) noexcept -> bool {return c == '[' || c == '{';})) + { + auto iter = this->line_begin(); // points the first character + while(iter != this->begin()) + { + iter = std::prev(iter); + + // range [line_start, iter) represents the previous line + const auto line_start = std::find( + rev_iter(iter), rev_iter(this->begin()), '\n').base(); + const auto comment_found = std::find(line_start, iter, '#'); + if(comment_found == iter) + { + break; // comment not found. + } + + // exclude the following case. + // > a = "foo" # comment // <-- this is not a comment for b but a. + // > b = "current value" + if(std::all_of(line_start, comment_found, + [](const char c) noexcept -> bool { + return c == ' ' || c == '\t'; + })) + { + // unwrap the first '#' by std::next. + auto s = make_string(std::next(comment_found), iter); + if(!s.empty() && s.back() == '\r') {s.pop_back();} + com.push_back(std::move(s)); + } + else + { + break; + } + iter = line_start; + } + } + } + + if(com.size() > 1) + { + std::reverse(com.begin(), com.end()); + } + + { + // find comments just after the current region. + // ```toml + // # not this. + // a = value # this one. + // a = [ # not this (technically difficult) + // + // ] # and this. + // ``` + // The reason why it's difficult is that it requires parsing in the + // following case. + // ```toml + // a = [ 10 # this comment is for `10`. not for `a` but `a[0]`. + // # ... + // ] # this is apparently a comment for a. + // + // b = [ + // 3.14 ] # there is no way to add a comment to `3.14` currently. + // + // c = [ + // 3.14 # do this if you need a comment here. + // ] + // ``` + const auto comment_found = + std::find(this->last(), this->line_end(), '#'); + if(comment_found != this->line_end()) // '#' found + { + // table = {key = "value"} # what is this for? + // the above comment is not for "value", but {key="value"}. + if(comment_found == std::find_if(this->last(), comment_found, + [](const char c) noexcept -> bool { + return !(c == ' ' || c == '\t' || c == ','); + })) + { + // unwrap the first '#' by std::next. + auto s = make_string(std::next(comment_found), this->line_end()); + if(!s.empty() && s.back() == '\r') {s.pop_back();} + com.push_back(std::move(s)); + } + } + } + return com; + } + + private: + + source_ptr source_; + std::string source_name_; + const_iterator first_, last_; +}; + +} // detail +} // toml +#endif// TOML11_REGION_H diff --git a/src/frontend/qt_sdl/toml/toml/result.hpp b/src/frontend/qt_sdl/toml/toml/result.hpp new file mode 100644 index 0000000000..77cd46c648 --- /dev/null +++ b/src/frontend/qt_sdl/toml/toml/result.hpp @@ -0,0 +1,717 @@ +// Copyright Toru Niina 2017. +// Distributed under the MIT License. +#ifndef TOML11_RESULT_HPP +#define TOML11_RESULT_HPP +#include "traits.hpp" +#include +#include +#include +#include +#include +#include +#include + +namespace toml +{ + +template +struct success +{ + using value_type = T; + value_type value; + + explicit success(const value_type& v) + noexcept(std::is_nothrow_copy_constructible::value) + : value(v) + {} + explicit success(value_type&& v) + noexcept(std::is_nothrow_move_constructible::value) + : value(std::move(v)) + {} + + template + explicit success(U&& v): value(std::forward(v)) {} + + template + explicit success(const success& v): value(v.value) {} + template + explicit success(success&& v): value(std::move(v.value)) {} + + ~success() = default; + success(const success&) = default; + success(success&&) = default; + success& operator=(const success&) = default; + success& operator=(success&&) = default; +}; + +template +struct failure +{ + using value_type = T; + value_type value; + + explicit failure(const value_type& v) + noexcept(std::is_nothrow_copy_constructible::value) + : value(v) + {} + explicit failure(value_type&& v) + noexcept(std::is_nothrow_move_constructible::value) + : value(std::move(v)) + {} + + template + explicit failure(U&& v): value(std::forward(v)) {} + + template + explicit failure(const failure& v): value(v.value) {} + template + explicit failure(failure&& v): value(std::move(v.value)) {} + + ~failure() = default; + failure(const failure&) = default; + failure(failure&&) = default; + failure& operator=(const failure&) = default; + failure& operator=(failure&&) = default; +}; + +template +success::type>::type> +ok(T&& v) +{ + return success< + typename std::remove_cv::type>::type + >(std::forward(v)); +} +template +failure::type>::type> +err(T&& v) +{ + return failure< + typename std::remove_cv::type>::type + >(std::forward(v)); +} + +inline success ok(const char* literal) +{ + return success(std::string(literal)); +} +inline failure err(const char* literal) +{ + return failure(std::string(literal)); +} + + +template +struct result +{ + using value_type = T; + using error_type = E; + using success_type = success; + using failure_type = failure; + + result(const success_type& s): is_ok_(true) + { + auto tmp = ::new(std::addressof(this->succ)) success_type(s); + assert(tmp == std::addressof(this->succ)); + (void)tmp; + } + result(const failure_type& f): is_ok_(false) + { + auto tmp = ::new(std::addressof(this->fail)) failure_type(f); + assert(tmp == std::addressof(this->fail)); + (void)tmp; + } + result(success_type&& s): is_ok_(true) + { + auto tmp = ::new(std::addressof(this->succ)) success_type(std::move(s)); + assert(tmp == std::addressof(this->succ)); + (void)tmp; + } + result(failure_type&& f): is_ok_(false) + { + auto tmp = ::new(std::addressof(this->fail)) failure_type(std::move(f)); + assert(tmp == std::addressof(this->fail)); + (void)tmp; + } + + template + result(const success& s): is_ok_(true) + { + auto tmp = ::new(std::addressof(this->succ)) success_type(s.value); + assert(tmp == std::addressof(this->succ)); + (void)tmp; + } + template + result(const failure& f): is_ok_(false) + { + auto tmp = ::new(std::addressof(this->fail)) failure_type(f.value); + assert(tmp == std::addressof(this->fail)); + (void)tmp; + } + template + result(success&& s): is_ok_(true) + { + auto tmp = ::new(std::addressof(this->succ)) success_type(std::move(s.value)); + assert(tmp == std::addressof(this->succ)); + (void)tmp; + } + template + result(failure&& f): is_ok_(false) + { + auto tmp = ::new(std::addressof(this->fail)) failure_type(std::move(f.value)); + assert(tmp == std::addressof(this->fail)); + (void)tmp; + } + + result& operator=(const success_type& s) + { + this->cleanup(); + this->is_ok_ = true; + auto tmp = ::new(std::addressof(this->succ)) success_type(s); + assert(tmp == std::addressof(this->succ)); + (void)tmp; + return *this; + } + result& operator=(const failure_type& f) + { + this->cleanup(); + this->is_ok_ = false; + auto tmp = ::new(std::addressof(this->fail)) failure_type(f); + assert(tmp == std::addressof(this->fail)); + (void)tmp; + return *this; + } + result& operator=(success_type&& s) + { + this->cleanup(); + this->is_ok_ = true; + auto tmp = ::new(std::addressof(this->succ)) success_type(std::move(s)); + assert(tmp == std::addressof(this->succ)); + (void)tmp; + return *this; + } + result& operator=(failure_type&& f) + { + this->cleanup(); + this->is_ok_ = false; + auto tmp = ::new(std::addressof(this->fail)) failure_type(std::move(f)); + assert(tmp == std::addressof(this->fail)); + (void)tmp; + return *this; + } + + template + result& operator=(const success& s) + { + this->cleanup(); + this->is_ok_ = true; + auto tmp = ::new(std::addressof(this->succ)) success_type(s.value); + assert(tmp == std::addressof(this->succ)); + (void)tmp; + return *this; + } + template + result& operator=(const failure& f) + { + this->cleanup(); + this->is_ok_ = false; + auto tmp = ::new(std::addressof(this->fail)) failure_type(f.value); + assert(tmp == std::addressof(this->fail)); + (void)tmp; + return *this; + } + template + result& operator=(success&& s) + { + this->cleanup(); + this->is_ok_ = true; + auto tmp = ::new(std::addressof(this->succ)) success_type(std::move(s.value)); + assert(tmp == std::addressof(this->succ)); + (void)tmp; + return *this; + } + template + result& operator=(failure&& f) + { + this->cleanup(); + this->is_ok_ = false; + auto tmp = ::new(std::addressof(this->fail)) failure_type(std::move(f.value)); + assert(tmp == std::addressof(this->fail)); + (void)tmp; + return *this; + } + + ~result() noexcept {this->cleanup();} + + result(const result& other): is_ok_(other.is_ok()) + { + if(other.is_ok()) + { + auto tmp = ::new(std::addressof(this->succ)) success_type(other.as_ok()); + assert(tmp == std::addressof(this->succ)); + (void)tmp; + } + else + { + auto tmp = ::new(std::addressof(this->fail)) failure_type(other.as_err()); + assert(tmp == std::addressof(this->fail)); + (void)tmp; + } + } + result(result&& other): is_ok_(other.is_ok()) + { + if(other.is_ok()) + { + auto tmp = ::new(std::addressof(this->succ)) success_type(std::move(other.as_ok())); + assert(tmp == std::addressof(this->succ)); + (void)tmp; + } + else + { + auto tmp = ::new(std::addressof(this->fail)) failure_type(std::move(other.as_err())); + assert(tmp == std::addressof(this->fail)); + (void)tmp; + } + } + + template + result(const result& other): is_ok_(other.is_ok()) + { + if(other.is_ok()) + { + auto tmp = ::new(std::addressof(this->succ)) success_type(other.as_ok()); + assert(tmp == std::addressof(this->succ)); + (void)tmp; + } + else + { + auto tmp = ::new(std::addressof(this->fail)) failure_type(other.as_err()); + assert(tmp == std::addressof(this->fail)); + (void)tmp; + } + } + template + result(result&& other): is_ok_(other.is_ok()) + { + if(other.is_ok()) + { + auto tmp = ::new(std::addressof(this->succ)) success_type(std::move(other.as_ok())); + assert(tmp == std::addressof(this->succ)); + (void)tmp; + } + else + { + auto tmp = ::new(std::addressof(this->fail)) failure_type(std::move(other.as_err())); + assert(tmp == std::addressof(this->fail)); + (void)tmp; + } + } + + result& operator=(const result& other) + { + this->cleanup(); + if(other.is_ok()) + { + auto tmp = ::new(std::addressof(this->succ)) success_type(other.as_ok()); + assert(tmp == std::addressof(this->succ)); + (void)tmp; + } + else + { + auto tmp = ::new(std::addressof(this->fail)) failure_type(other.as_err()); + assert(tmp == std::addressof(this->fail)); + (void)tmp; + } + is_ok_ = other.is_ok(); + return *this; + } + result& operator=(result&& other) + { + this->cleanup(); + if(other.is_ok()) + { + auto tmp = ::new(std::addressof(this->succ)) success_type(std::move(other.as_ok())); + assert(tmp == std::addressof(this->succ)); + (void)tmp; + } + else + { + auto tmp = ::new(std::addressof(this->fail)) failure_type(std::move(other.as_err())); + assert(tmp == std::addressof(this->fail)); + (void)tmp; + } + is_ok_ = other.is_ok(); + return *this; + } + + template + result& operator=(const result& other) + { + this->cleanup(); + if(other.is_ok()) + { + auto tmp = ::new(std::addressof(this->succ)) success_type(other.as_ok()); + assert(tmp == std::addressof(this->succ)); + (void)tmp; + } + else + { + auto tmp = ::new(std::addressof(this->fail)) failure_type(other.as_err()); + assert(tmp == std::addressof(this->fail)); + (void)tmp; + } + is_ok_ = other.is_ok(); + return *this; + } + template + result& operator=(result&& other) + { + this->cleanup(); + if(other.is_ok()) + { + auto tmp = ::new(std::addressof(this->succ)) success_type(std::move(other.as_ok())); + assert(tmp == std::addressof(this->succ)); + (void)tmp; + } + else + { + auto tmp = ::new(std::addressof(this->fail)) failure_type(std::move(other.as_err())); + assert(tmp == std::addressof(this->fail)); + (void)tmp; + } + is_ok_ = other.is_ok(); + return *this; + } + + bool is_ok() const noexcept {return is_ok_;} + bool is_err() const noexcept {return !is_ok_;} + + operator bool() const noexcept {return is_ok_;} + + value_type& unwrap() & + { + if(is_err()) + { + throw std::runtime_error("toml::result: bad unwrap: " + + format_error(this->as_err())); + } + return this->succ.value; + } + value_type const& unwrap() const& + { + if(is_err()) + { + throw std::runtime_error("toml::result: bad unwrap: " + + format_error(this->as_err())); + } + return this->succ.value; + } + value_type&& unwrap() && + { + if(is_err()) + { + throw std::runtime_error("toml::result: bad unwrap: " + + format_error(this->as_err())); + } + return std::move(this->succ.value); + } + + value_type& unwrap_or(value_type& opt) & + { + if(is_err()) {return opt;} + return this->succ.value; + } + value_type const& unwrap_or(value_type const& opt) const& + { + if(is_err()) {return opt;} + return this->succ.value; + } + value_type unwrap_or(value_type opt) && + { + if(is_err()) {return opt;} + return this->succ.value; + } + + error_type& unwrap_err() & + { + if(is_ok()) {throw std::runtime_error("toml::result: bad unwrap_err");} + return this->fail.value; + } + error_type const& unwrap_err() const& + { + if(is_ok()) {throw std::runtime_error("toml::result: bad unwrap_err");} + return this->fail.value; + } + error_type&& unwrap_err() && + { + if(is_ok()) {throw std::runtime_error("toml::result: bad unwrap_err");} + return std::move(this->fail.value); + } + + value_type& as_ok() & noexcept {return this->succ.value;} + value_type const& as_ok() const& noexcept {return this->succ.value;} + value_type&& as_ok() && noexcept {return std::move(this->succ.value);} + + error_type& as_err() & noexcept {return this->fail.value;} + error_type const& as_err() const& noexcept {return this->fail.value;} + error_type&& as_err() && noexcept {return std::move(this->fail.value);} + + + // prerequisities + // F: T -> U + // retval: result + template + result, error_type> + map(F&& f) & + { + if(this->is_ok()){return ok(f(this->as_ok()));} + return err(this->as_err()); + } + template + result, error_type> + map(F&& f) const& + { + if(this->is_ok()){return ok(f(this->as_ok()));} + return err(this->as_err()); + } + template + result, error_type> + map(F&& f) && + { + if(this->is_ok()){return ok(f(std::move(this->as_ok())));} + return err(std::move(this->as_err())); + } + + // prerequisities + // F: E -> F + // retval: result + template + result> + map_err(F&& f) & + { + if(this->is_err()){return err(f(this->as_err()));} + return ok(this->as_ok()); + } + template + result> + map_err(F&& f) const& + { + if(this->is_err()){return err(f(this->as_err()));} + return ok(this->as_ok()); + } + template + result> + map_err(F&& f) && + { + if(this->is_err()){return err(f(std::move(this->as_err())));} + return ok(std::move(this->as_ok())); + } + + // prerequisities + // F: T -> U + // retval: U + template + detail::return_type_of_t + map_or_else(F&& f, U&& opt) & + { + if(this->is_err()){return std::forward(opt);} + return f(this->as_ok()); + } + template + detail::return_type_of_t + map_or_else(F&& f, U&& opt) const& + { + if(this->is_err()){return std::forward(opt);} + return f(this->as_ok()); + } + template + detail::return_type_of_t + map_or_else(F&& f, U&& opt) && + { + if(this->is_err()){return std::forward(opt);} + return f(std::move(this->as_ok())); + } + + // prerequisities + // F: E -> U + // retval: U + template + detail::return_type_of_t + map_err_or_else(F&& f, U&& opt) & + { + if(this->is_ok()){return std::forward(opt);} + return f(this->as_err()); + } + template + detail::return_type_of_t + map_err_or_else(F&& f, U&& opt) const& + { + if(this->is_ok()){return std::forward(opt);} + return f(this->as_err()); + } + template + detail::return_type_of_t + map_err_or_else(F&& f, U&& opt) && + { + if(this->is_ok()){return std::forward(opt);} + return f(std::move(this->as_err())); + } + + // prerequisities: + // F: func T -> U + // toml::err(error_type) should be convertible to U. + // normally, type U is another result and E is convertible to F + template + detail::return_type_of_t + and_then(F&& f) & + { + if(this->is_ok()){return f(this->as_ok());} + return err(this->as_err()); + } + template + detail::return_type_of_t + and_then(F&& f) const& + { + if(this->is_ok()){return f(this->as_ok());} + return err(this->as_err()); + } + template + detail::return_type_of_t + and_then(F&& f) && + { + if(this->is_ok()){return f(std::move(this->as_ok()));} + return err(std::move(this->as_err())); + } + + // prerequisities: + // F: func E -> U + // toml::ok(value_type) should be convertible to U. + // normally, type U is another result and T is convertible to S + template + detail::return_type_of_t + or_else(F&& f) & + { + if(this->is_err()){return f(this->as_err());} + return ok(this->as_ok()); + } + template + detail::return_type_of_t + or_else(F&& f) const& + { + if(this->is_err()){return f(this->as_err());} + return ok(this->as_ok()); + } + template + detail::return_type_of_t + or_else(F&& f) && + { + if(this->is_err()){return f(std::move(this->as_err()));} + return ok(std::move(this->as_ok())); + } + + // if *this is error, returns *this. otherwise, returns other. + result and_other(const result& other) const& + { + return this->is_err() ? *this : other; + } + result and_other(result&& other) && + { + return this->is_err() ? std::move(*this) : std::move(other); + } + + // if *this is okay, returns *this. otherwise, returns other. + result or_other(const result& other) const& + { + return this->is_ok() ? *this : other; + } + result or_other(result&& other) && + { + return this->is_ok() ? std::move(*this) : std::move(other); + } + + void swap(result& other) + { + result tmp(std::move(*this)); + *this = std::move(other); + other = std::move(tmp); + return ; + } + + private: + + static std::string format_error(std::exception const& excpt) + { + return std::string(excpt.what()); + } + template::value, std::nullptr_t>::type = nullptr> + static std::string format_error(U const& others) + { + std::ostringstream oss; oss << others; + return oss.str(); + } + + void cleanup() noexcept + { + if(this->is_ok_) {this->succ.~success_type();} + else {this->fail.~failure_type();} + return; + } + + private: + + bool is_ok_; + union + { + success_type succ; + failure_type fail; + }; +}; + +template +void swap(result& lhs, result& rhs) +{ + lhs.swap(rhs); + return; +} + +// this might be confusing because it eagerly evaluated, while in the other +// cases operator && and || are short-circuited. +// +// template +// inline result +// operator&&(const result& lhs, const result& rhs) noexcept +// { +// return lhs.is_ok() ? rhs : lhs; +// } +// +// template +// inline result +// operator||(const result& lhs, const result& rhs) noexcept +// { +// return lhs.is_ok() ? lhs : rhs; +// } + +// ---------------------------------------------------------------------------- +// re-use result as a optional with none_t + +namespace detail +{ +struct none_t {}; +inline bool operator==(const none_t&, const none_t&) noexcept {return true;} +inline bool operator!=(const none_t&, const none_t&) noexcept {return false;} +inline bool operator< (const none_t&, const none_t&) noexcept {return false;} +inline bool operator<=(const none_t&, const none_t&) noexcept {return true;} +inline bool operator> (const none_t&, const none_t&) noexcept {return false;} +inline bool operator>=(const none_t&, const none_t&) noexcept {return true;} +template +std::basic_ostream& +operator<<(std::basic_ostream& os, const none_t&) +{ + os << "none"; + return os; +} +inline failure none() noexcept {return failure{none_t{}};} +} // detail +} // toml11 +#endif// TOML11_RESULT_H diff --git a/src/frontend/qt_sdl/toml/toml/serializer.hpp b/src/frontend/qt_sdl/toml/toml/serializer.hpp new file mode 100644 index 0000000000..88ae775a83 --- /dev/null +++ b/src/frontend/qt_sdl/toml/toml/serializer.hpp @@ -0,0 +1,922 @@ +// Copyright Toru Niina 2019. +// Distributed under the MIT License. +#ifndef TOML11_SERIALIZER_HPP +#define TOML11_SERIALIZER_HPP +#include +#include + +#include + +#include "lexer.hpp" +#include "value.hpp" + +namespace toml +{ + +// This function serialize a key. It checks a string is a bare key and +// escapes special characters if the string is not compatible to a bare key. +// ```cpp +// std::string k("non.bare.key"); // the key itself includes `.`s. +// std::string formatted = toml::format_key(k); +// assert(formatted == "\"non.bare.key\""); +// ``` +// +// This function is exposed to make it easy to write a user-defined serializer. +// Since toml restricts characters available in a bare key, generally a string +// should be escaped. But checking whether a string needs to be surrounded by +// a `"` and escaping some special character is boring. +template +std::basic_string +format_key(const std::basic_string& k) +{ + if(k.empty()) + { + return std::string("\"\""); + } + + // check the key can be a bare (unquoted) key + detail::location loc(k, std::vector(k.begin(), k.end())); + detail::lex_unquoted_key::invoke(loc); + if(loc.iter() == loc.end()) + { + return k; // all the tokens are consumed. the key is unquoted-key. + } + + //if it includes special characters, then format it in a "quoted" key. + std::basic_string serialized("\""); + for(const char c : k) + { + switch(c) + { + case '\\': {serialized += "\\\\"; break;} + case '\"': {serialized += "\\\""; break;} + case '\b': {serialized += "\\b"; break;} + case '\t': {serialized += "\\t"; break;} + case '\f': {serialized += "\\f"; break;} + case '\n': {serialized += "\\n"; break;} + case '\r': {serialized += "\\r"; break;} + default : {serialized += c; break;} + } + } + serialized += "\""; + return serialized; +} + +template +std::basic_string +format_keys(const std::vector>& keys) +{ + if(keys.empty()) + { + return std::string("\"\""); + } + + std::basic_string serialized; + for(const auto& ky : keys) + { + serialized += format_key(ky); + serialized += charT('.'); + } + serialized.pop_back(); // remove the last dot '.' + return serialized; +} + +template +struct serializer +{ + static_assert(detail::is_basic_value::value, + "toml::serializer is for toml::value and its variants, " + "toml::basic_value<...>."); + + using value_type = Value; + using key_type = typename value_type::key_type ; + using comment_type = typename value_type::comment_type ; + using boolean_type = typename value_type::boolean_type ; + using integer_type = typename value_type::integer_type ; + using floating_type = typename value_type::floating_type ; + using string_type = typename value_type::string_type ; + using local_time_type = typename value_type::local_time_type ; + using local_date_type = typename value_type::local_date_type ; + using local_datetime_type = typename value_type::local_datetime_type ; + using offset_datetime_type = typename value_type::offset_datetime_type; + using array_type = typename value_type::array_type ; + using table_type = typename value_type::table_type ; + + serializer(const std::size_t w = 80u, + const int float_prec = std::numeric_limits::max_digits10, + const bool can_be_inlined = false, + const bool no_comment = false, + std::vector ks = {}, + const bool value_has_comment = false) + : can_be_inlined_(can_be_inlined), no_comment_(no_comment), + value_has_comment_(value_has_comment && !no_comment), + float_prec_(float_prec), width_(w), keys_(std::move(ks)) + {} + ~serializer() = default; + + std::string operator()(const boolean_type& b) const + { + return b ? "true" : "false"; + } + std::string operator()(const integer_type i) const + { + return std::to_string(i); + } + std::string operator()(const floating_type f) const + { + if(std::isnan(f)) + { + if(std::signbit(f)) + { + return std::string("-nan"); + } + else + { + return std::string("nan"); + } + } + else if(!std::isfinite(f)) + { + if(std::signbit(f)) + { + return std::string("-inf"); + } + else + { + return std::string("inf"); + } + } + + const auto fmt = "%.*g"; + const auto bsz = std::snprintf(nullptr, 0, fmt, this->float_prec_, f); + // +1 for null character(\0) + std::vector buf(static_cast(bsz + 1), '\0'); + std::snprintf(buf.data(), buf.size(), fmt, this->float_prec_, f); + + std::string token(buf.begin(), std::prev(buf.end())); + if(!token.empty() && token.back() == '.') // 1. => 1.0 + { + token += '0'; + } + + const auto e = std::find_if( + token.cbegin(), token.cend(), [](const char c) noexcept -> bool { + return c == 'e' || c == 'E'; + }); + const auto has_exponent = (token.cend() != e); + const auto has_fraction = (token.cend() != std::find( + token.cbegin(), token.cend(), '.')); + + if(!has_exponent && !has_fraction) + { + // the resulting value does not have any float specific part! + token += ".0"; + } + return token; + } + std::string operator()(const string_type& s) const + { + if(s.kind == string_t::basic) + { + if((std::find(s.str.cbegin(), s.str.cend(), '\n') != s.str.cend() || + std::find(s.str.cbegin(), s.str.cend(), '\"') != s.str.cend()) && + this->width_ != (std::numeric_limits::max)()) + { + // if linefeed or double-quote is contained, + // make it multiline basic string. + const auto escaped = this->escape_ml_basic_string(s.str); + std::string open("\"\"\""); + std::string close("\"\"\""); + if(escaped.find('\n') != std::string::npos || + this->width_ < escaped.size() + 6) + { + // if the string body contains newline or is enough long, + // add newlines after and before delimiters. + open += "\n"; + close = std::string("\\\n") + close; + } + return open + escaped + close; + } + + // no linefeed. try to make it oneline-string. + std::string oneline = this->escape_basic_string(s.str); + if(oneline.size() + 2 < width_ || width_ < 2) + { + const std::string quote("\""); + return quote + oneline + quote; + } + + // the line is too long compared to the specified width. + // split it into multiple lines. + std::string token("\"\"\"\n"); + while(!oneline.empty()) + { + if(oneline.size() < width_) + { + token += oneline; + oneline.clear(); + } + else if(oneline.at(width_-2) == '\\') + { + token += oneline.substr(0, width_-2); + token += "\\\n"; + oneline.erase(0, width_-2); + } + else + { + token += oneline.substr(0, width_-1); + token += "\\\n"; + oneline.erase(0, width_-1); + } + } + return token + std::string("\\\n\"\"\""); + } + else // the string `s` is literal-string. + { + if(std::find(s.str.cbegin(), s.str.cend(), '\n') != s.str.cend() || + std::find(s.str.cbegin(), s.str.cend(), '\'') != s.str.cend() ) + { + std::string open("'''"); + if(this->width_ + 6 < s.str.size()) + { + open += '\n'; // the first newline is ignored by TOML spec + } + const std::string close("'''"); + return open + s.str + close; + } + else + { + const std::string quote("'"); + return quote + s.str + quote; + } + } + } + + std::string operator()(const local_date_type& d) const + { + std::ostringstream oss; + oss << d; + return oss.str(); + } + std::string operator()(const local_time_type& t) const + { + std::ostringstream oss; + oss << t; + return oss.str(); + } + std::string operator()(const local_datetime_type& dt) const + { + std::ostringstream oss; + oss << dt; + return oss.str(); + } + std::string operator()(const offset_datetime_type& odt) const + { + std::ostringstream oss; + oss << odt; + return oss.str(); + } + + std::string operator()(const array_type& v) const + { + if(v.empty()) + { + return std::string("[]"); + } + if(this->is_array_of_tables(v)) + { + return make_array_of_tables(v); + } + + // not an array of tables. normal array. + // first, try to make it inline if none of the elements have a comment. + if( ! this->has_comment_inside(v)) + { + const auto inl = this->make_inline_array(v); + if(inl.size() < this->width_ && + std::find(inl.cbegin(), inl.cend(), '\n') == inl.cend()) + { + return inl; + } + } + + // if the length exceeds this->width_, print multiline array. + // key = [ + // # ... + // 42, + // ... + // ] + std::string token; + std::string current_line; + token += "[\n"; + for(const auto& item : v) + { + if( ! item.comments().empty() && !no_comment_) + { + // if comment exists, the element must be the only element in the line. + // e.g. the following is not allowed. + // ```toml + // array = [ + // # comment for what? + // 1, 2, 3, 4, 5 + // ] + // ``` + if(!current_line.empty()) + { + if(current_line.back() != '\n') + { + current_line += '\n'; + } + token += current_line; + current_line.clear(); + } + for(const auto& c : item.comments()) + { + token += '#'; + token += c; + token += '\n'; + } + token += toml::visit(*this, item); + if(!token.empty() && token.back() == '\n') {token.pop_back();} + token += ",\n"; + continue; + } + std::string next_elem; + if(item.is_table()) + { + serializer ser(*this); + ser.can_be_inlined_ = true; + ser.width_ = (std::numeric_limits::max)(); + next_elem += toml::visit(ser, item); + } + else + { + next_elem += toml::visit(*this, item); + } + + // comma before newline. + if(!next_elem.empty() && next_elem.back() == '\n') {next_elem.pop_back();} + + // if current line does not exceeds the width limit, continue. + if(current_line.size() + next_elem.size() + 1 < this->width_) + { + current_line += next_elem; + current_line += ','; + } + else if(current_line.empty()) + { + // if current line was empty, force put the next_elem because + // next_elem is not splittable + token += next_elem; + token += ",\n"; + // current_line is kept empty + } + else // reset current_line + { + assert(current_line.back() == ','); + token += current_line; + token += '\n'; + current_line = next_elem; + current_line += ','; + } + } + if(!current_line.empty()) + { + if(!current_line.empty() && current_line.back() != '\n') + { + current_line += '\n'; + } + token += current_line; + } + token += "]\n"; + return token; + } + + // templatize for any table-like container + std::string operator()(const table_type& v) const + { + // if an element has a comment, then it can't be inlined. + // table = {# how can we write a comment for this? key = "value"} + if(this->can_be_inlined_ && !(this->has_comment_inside(v))) + { + std::string token; + if(!this->keys_.empty()) + { + token += format_key(this->keys_.back()); + token += " = "; + } + token += this->make_inline_table(v); + if(token.size() < this->width_ && + token.end() == std::find(token.begin(), token.end(), '\n')) + { + return token; + } + } + + std::string token; + if(!keys_.empty()) + { + token += '['; + token += format_keys(keys_); + token += "]\n"; + } + token += this->make_multiline_table(v); + return token; + } + + private: + + std::string escape_basic_string(const std::string& s) const + { + //XXX assuming `s` is a valid utf-8 sequence. + std::string retval; + for(const char c : s) + { + switch(c) + { + case '\\': {retval += "\\\\"; break;} + case '\"': {retval += "\\\""; break;} + case '\b': {retval += "\\b"; break;} + case '\t': {retval += "\\t"; break;} + case '\f': {retval += "\\f"; break;} + case '\n': {retval += "\\n"; break;} + case '\r': {retval += "\\r"; break;} + default : + { + if((0x00 <= c && c <= 0x08) || (0x0A <= c && c <= 0x1F) || c == 0x7F) + { + retval += "\\u00"; + retval += char(48 + (c / 16)); + retval += char((c % 16 < 10 ? 48 : 55) + (c % 16)); + } + else + { + retval += c; + } + } + } + } + return retval; + } + + std::string escape_ml_basic_string(const std::string& s) const + { + std::string retval; + for(auto i=s.cbegin(), e=s.cend(); i!=e; ++i) + { + switch(*i) + { + case '\\': {retval += "\\\\"; break;} + // One or two consecutive "s are allowed. + // Later we will check there are no three consecutive "s. + // case '\"': {retval += "\\\""; break;} + case '\b': {retval += "\\b"; break;} + case '\t': {retval += "\\t"; break;} + case '\f': {retval += "\\f"; break;} + case '\n': {retval += "\n"; break;} + case '\r': + { + if(std::next(i) != e && *std::next(i) == '\n') + { + retval += "\r\n"; + ++i; + } + else + { + retval += "\\r"; + } + break; + } + default : + { + const auto c = *i; + if((0x00 <= c && c <= 0x08) || (0x0A <= c && c <= 0x1F) || c == 0x7F) + { + retval += "\\u00"; + retval += char(48 + (c / 16)); + retval += char((c % 16 < 10 ? 48 : 55) + (c % 16)); + } + else + { + retval += c; + } + } + + } + } + // Only 1 or 2 consecutive `"`s are allowed in multiline basic string. + // 3 consecutive `"`s are considered as a closing delimiter. + // We need to check if there are 3 or more consecutive `"`s and insert + // backslash to break them down into several short `"`s like the `str6` + // in the following example. + // ```toml + // str4 = """Here are two quotation marks: "". Simple enough.""" + // # str5 = """Here are three quotation marks: """.""" # INVALID + // str5 = """Here are three quotation marks: ""\".""" + // str6 = """Here are fifteen quotation marks: ""\"""\"""\"""\"""\".""" + // ``` + auto found_3_quotes = retval.find("\"\"\""); + while(found_3_quotes != std::string::npos) + { + retval.replace(found_3_quotes, 3, "\"\"\\\""); + found_3_quotes = retval.find("\"\"\""); + } + return retval; + } + + // if an element of a table or an array has a comment, it cannot be inlined. + bool has_comment_inside(const array_type& a) const noexcept + { + // if no_comment is set, comments would not be written. + if(this->no_comment_) {return false;} + + for(const auto& v : a) + { + if(!v.comments().empty()) {return true;} + } + return false; + } + bool has_comment_inside(const table_type& t) const noexcept + { + // if no_comment is set, comments would not be written. + if(this->no_comment_) {return false;} + + for(const auto& kv : t) + { + if(!kv.second.comments().empty()) {return true;} + } + return false; + } + + std::string make_inline_array(const array_type& v) const + { + assert(!has_comment_inside(v)); + std::string token; + token += '['; + bool is_first = true; + for(const auto& item : v) + { + if(is_first) {is_first = false;} else {token += ',';} + token += visit(serializer( + (std::numeric_limits::max)(), this->float_prec_, + /* inlined */ true, /*no comment*/ false, /*keys*/ {}, + /*has_comment*/ !item.comments().empty()), item); + } + token += ']'; + return token; + } + + std::string make_inline_table(const table_type& v) const + { + assert(!has_comment_inside(v)); + assert(this->can_be_inlined_); + std::string token; + token += '{'; + bool is_first = true; + for(const auto& kv : v) + { + // in inline tables, trailing comma is not allowed (toml-lang #569). + if(is_first) {is_first = false;} else {token += ',';} + token += format_key(kv.first); + token += '='; + token += visit(serializer( + (std::numeric_limits::max)(), this->float_prec_, + /* inlined */ true, /*no comment*/ false, /*keys*/ {}, + /*has_comment*/ !kv.second.comments().empty()), kv.second); + } + token += '}'; + return token; + } + + std::string make_multiline_table(const table_type& v) const + { + std::string token; + + // print non-table elements first. + // ```toml + // [foo] # a table we're writing now here + // key = "value" # <- non-table element, "key" + // # ... + // [foo.bar] # <- table element, "bar" + // ``` + // because after printing [foo.bar], the remaining non-table values will + // be assigned into [foo.bar], not [foo]. Those values should be printed + // earlier. + for(const auto& kv : v) + { + if(kv.second.is_table() || is_array_of_tables(kv.second)) + { + continue; + } + + token += write_comments(kv.second); + + const auto key_and_sep = format_key(kv.first) + " = "; + const auto residual_width = (this->width_ > key_and_sep.size()) ? + this->width_ - key_and_sep.size() : 0; + token += key_and_sep; + token += visit(serializer(residual_width, this->float_prec_, + /*can be inlined*/ true, /*no comment*/ false, /*keys*/ {}, + /*has_comment*/ !kv.second.comments().empty()), kv.second); + + if(token.back() != '\n') + { + token += '\n'; + } + } + + // normal tables / array of tables + + // after multiline table appeared, the other tables cannot be inline + // because the table would be assigned into the table. + // [foo] + // ... + // bar = {...} # <- bar will be a member of [foo]. + bool multiline_table_printed = false; + for(const auto& kv : v) + { + if(!kv.second.is_table() && !is_array_of_tables(kv.second)) + { + continue; // other stuff are already serialized. skip them. + } + + std::vector ks(this->keys_); + ks.push_back(kv.first); + + auto tmp = visit(serializer(this->width_, this->float_prec_, + !multiline_table_printed, this->no_comment_, ks, + /*has_comment*/ !kv.second.comments().empty()), kv.second); + + // If it is the first time to print a multi-line table, it would be + // helpful to separate normal key-value pair and subtables by a + // newline. + // (this checks if the current key-value pair contains newlines. + // but it is not perfect because multi-line string can also contain + // a newline. in such a case, an empty line will be written) TODO + if((!multiline_table_printed) && + std::find(tmp.cbegin(), tmp.cend(), '\n') != tmp.cend()) + { + multiline_table_printed = true; + token += '\n'; // separate key-value pairs and subtables + + token += write_comments(kv.second); + token += tmp; + + // care about recursive tables (all tables in each level prints + // newline and there will be a full of newlines) + if(tmp.substr(tmp.size() - 2, 2) != "\n\n" && + tmp.substr(tmp.size() - 4, 4) != "\r\n\r\n" ) + { + token += '\n'; + } + } + else + { + token += write_comments(kv.second); + token += tmp; + token += '\n'; + } + } + return token; + } + + std::string make_array_of_tables(const array_type& v) const + { + // if it's not inlined, we need to add `[[table.key]]`. + // but if it can be inlined, we can format it as the following. + // ``` + // table.key = [ + // {...}, + // # comment + // {...}, + // ] + // ``` + // This function checks if inlinization is possible or not, and then + // format the array-of-tables in a proper way. + // + // Note about comments: + // + // If the array itself has a comment (value_has_comment_ == true), we + // should try to make it inline. + // ```toml + // # comment about array + // array = [ + // # comment about table element + // {of = "table"} + // ] + // ``` + // If it is formatted as a multiline table, the two comments becomes + // indistinguishable. + // ```toml + // # comment about array + // # comment about table element + // [[array]] + // of = "table" + // ``` + // So we need to try to make it inline, and it force-inlines regardless + // of the line width limit. + // It may fail if the element of a table has comment. In that case, + // the array-of-tables will be formatted as a multiline table. + if(this->can_be_inlined_ || this->value_has_comment_) + { + std::string token; + if(!keys_.empty()) + { + token += format_key(keys_.back()); + token += " = "; + } + + bool failed = false; + token += "[\n"; + for(const auto& item : v) + { + // if an element of the table has a comment, the table + // cannot be inlined. + if(this->has_comment_inside(item.as_table())) + { + failed = true; + break; + } + // write comments for the table itself + token += write_comments(item); + + const auto t = this->make_inline_table(item.as_table()); + + if(t.size() + 1 > width_ || // +1 for the last comma {...}, + std::find(t.cbegin(), t.cend(), '\n') != t.cend()) + { + // if the value itself has a comment, ignore the line width limit + if( ! this->value_has_comment_) + { + failed = true; + break; + } + } + token += t; + token += ",\n"; + } + + if( ! failed) + { + token += "]\n"; + return token; + } + // if failed, serialize them as [[array.of.tables]]. + } + + std::string token; + for(const auto& item : v) + { + token += write_comments(item); + token += "[["; + token += format_keys(keys_); + token += "]]\n"; + token += this->make_multiline_table(item.as_table()); + } + return token; + } + + std::string write_comments(const value_type& v) const + { + std::string retval; + if(this->no_comment_) {return retval;} + + for(const auto& c : v.comments()) + { + retval += '#'; + retval += c; + retval += '\n'; + } + return retval; + } + + bool is_array_of_tables(const value_type& v) const + { + if(!v.is_array() || v.as_array().empty()) {return false;} + return is_array_of_tables(v.as_array()); + } + bool is_array_of_tables(const array_type& v) const + { + // Since TOML v0.5.0, heterogeneous arrays are allowed. So we need to + // check all the element in an array to check if the array is an array + // of tables. + return std::all_of(v.begin(), v.end(), [](const value_type& elem) { + return elem.is_table(); + }); + } + + private: + + bool can_be_inlined_; + bool no_comment_; + bool value_has_comment_; + int float_prec_; + std::size_t width_; + std::vector keys_; +}; + +template class M, template class V> +std::string +format(const basic_value& v, std::size_t w = 80u, + int fprec = std::numeric_limits::max_digits10, + bool no_comment = false, bool force_inline = false) +{ + using value_type = basic_value; + // if value is a table, it is considered to be a root object. + // the root object can't be an inline table. + if(v.is_table()) + { + std::ostringstream oss; + if(!v.comments().empty()) + { + oss << v.comments(); + oss << '\n'; // to split the file comment from the first element + } + const auto serialized = visit(serializer(w, fprec, false, no_comment), v); + oss << serialized; + return oss.str(); + } + return visit(serializer(w, fprec, force_inline), v); +} + +namespace detail +{ +template +int comment_index(std::basic_ostream&) +{ + static const int index = std::ios_base::xalloc(); + return index; +} +} // detail + +template +std::basic_ostream& +nocomment(std::basic_ostream& os) +{ + // by default, it is zero. and by default, it shows comments. + os.iword(detail::comment_index(os)) = 1; + return os; +} + +template +std::basic_ostream& +showcomment(std::basic_ostream& os) +{ + // by default, it is zero. and by default, it shows comments. + os.iword(detail::comment_index(os)) = 0; + return os; +} + +template class M, template class V> +std::basic_ostream& +operator<<(std::basic_ostream& os, const basic_value& v) +{ + using value_type = basic_value; + + // get status of std::setw(). + const auto w = static_cast(os.width()); + const int fprec = static_cast(os.precision()); + os.width(0); + + // by default, iword is initialized by 0. And by default, toml11 outputs + // comments. So `0` means showcomment. 1 means nocommnet. + const bool no_comment = (1 == os.iword(detail::comment_index(os))); + + if(!no_comment && v.is_table() && !v.comments().empty()) + { + os << v.comments(); + os << '\n'; // to split the file comment from the first element + } + // the root object can't be an inline table. so pass `false`. + const auto serialized = visit(serializer(w, fprec, no_comment, false), v); + os << serialized; + + // if v is a non-table value, and has only one comment, then + // put a comment just after a value. in the following way. + // + // ```toml + // key = "value" # comment. + // ``` + // + // Since the top-level toml object is a table, one who want to put a + // non-table toml value must use this in a following way. + // + // ```cpp + // toml::value v; + // std::cout << "user-defined-key = " << v << std::endl; + // ``` + // + // In this case, it is impossible to put comments before key-value pair. + // The only way to preserve comments is to put all of them after a value. + if(!no_comment && !v.is_table() && !v.comments().empty()) + { + os << " #"; + for(const auto& c : v.comments()) {os << c;} + } + return os; +} + +} // toml +#endif// TOML11_SERIALIZER_HPP diff --git a/src/frontend/qt_sdl/toml/toml/source_location.hpp b/src/frontend/qt_sdl/toml/toml/source_location.hpp new file mode 100644 index 0000000000..fa175b5b48 --- /dev/null +++ b/src/frontend/qt_sdl/toml/toml/source_location.hpp @@ -0,0 +1,233 @@ +// Copyright Toru Niina 2019. +// Distributed under the MIT License. +#ifndef TOML11_SOURCE_LOCATION_HPP +#define TOML11_SOURCE_LOCATION_HPP +#include +#include + +#include "region.hpp" + +namespace toml +{ + +// A struct to contain location in a toml file. +// The interface imitates std::experimental::source_location, +// but not completely the same. +// +// It would be constructed by toml::value. It can be used to generate +// user-defined error messages. +// +// - std::uint_least32_t line() const noexcept +// - returns the line number where the region is on. +// - std::uint_least32_t column() const noexcept +// - returns the column number where the region starts. +// - std::uint_least32_t region() const noexcept +// - returns the size of the region. +// +// +-- line() +-- region of interest (region() == 9) +// v .---+---. +// 12 | value = "foo bar" +// ^ +// +-- column() +// +// - std::string const& file_name() const noexcept; +// - name of the file. +// - std::string const& line_str() const noexcept; +// - the whole line that contains the region of interest. +// +struct source_location +{ + public: + + source_location() + : line_num_(1), column_num_(1), region_size_(1), + file_name_("unknown file"), line_str_("") + {} + + explicit source_location(const detail::region_base* reg) + : line_num_(1), column_num_(1), region_size_(1), + file_name_("unknown file"), line_str_("") + { + if(reg) + { + if(reg->line_num() != detail::region_base().line_num()) + { + line_num_ = static_cast( + std::stoul(reg->line_num())); + } + column_num_ = static_cast(reg->before() + 1); + region_size_ = static_cast(reg->size()); + file_name_ = reg->name(); + line_str_ = reg->line(); + } + } + + explicit source_location(const detail::region& reg) + : line_num_(static_cast(std::stoul(reg.line_num()))), + column_num_(static_cast(reg.before() + 1)), + region_size_(static_cast(reg.size())), + file_name_(reg.name()), + line_str_ (reg.line()) + {} + explicit source_location(const detail::location& loc) + : line_num_(static_cast(std::stoul(loc.line_num()))), + column_num_(static_cast(loc.before() + 1)), + region_size_(static_cast(loc.size())), + file_name_(loc.name()), + line_str_ (loc.line()) + {} + + ~source_location() = default; + source_location(source_location const&) = default; + source_location(source_location &&) = default; + source_location& operator=(source_location const&) = default; + source_location& operator=(source_location &&) = default; + + std::uint_least32_t line() const noexcept {return line_num_;} + std::uint_least32_t column() const noexcept {return column_num_;} + std::uint_least32_t region() const noexcept {return region_size_;} + + std::string const& file_name() const noexcept {return file_name_;} + std::string const& line_str() const noexcept {return line_str_;} + + private: + + std::uint_least32_t line_num_; + std::uint_least32_t column_num_; + std::uint_least32_t region_size_; + std::string file_name_; + std::string line_str_; +}; + +namespace detail +{ + +// internal error message generation. +inline std::string format_underline(const std::string& message, + const std::vector>& loc_com, + const std::vector& helps = {}, + const bool colorize = TOML11_ERROR_MESSAGE_COLORIZED) +{ + std::size_t line_num_width = 0; + for(const auto& lc : loc_com) + { + std::uint_least32_t line = lc.first.line(); + std::size_t digit = 0; + while(line != 0) + { + line /= 10; + digit += 1; + } + line_num_width = (std::max)(line_num_width, digit); + } + // 1 is the minimum width + line_num_width = std::max(line_num_width, 1); + + std::ostringstream retval; + + if(colorize) + { + retval << color::colorize; // turn on ANSI color + } + + // XXX + // Here, before `colorize` support, it does not output `[error]` prefix + // automatically. So some user may output it manually and this change may + // duplicate the prefix. To avoid it, check the first 7 characters and + // if it is "[error]", it removes that part from the message shown. + if(message.size() > 7 && message.substr(0, 7) == "[error]") + { + retval << color::bold << color::red << "[error]" << color::reset + << color::bold << message.substr(7) << color::reset << '\n'; + } + else + { + retval << color::bold << color::red << "[error] " << color::reset + << color::bold << message << color::reset << '\n'; + } + + const auto format_one_location = [line_num_width] + (std::ostringstream& oss, + const source_location& loc, const std::string& comment) -> void + { + oss << ' ' << color::bold << color::blue + << std::setw(static_cast(line_num_width)) + << std::right << loc.line() << " | " << color::reset + << loc.line_str() << '\n'; + + oss << make_string(line_num_width + 1, ' ') + << color::bold << color::blue << " | " << color::reset + << make_string(loc.column()-1 /*1-origin*/, ' '); + + if(loc.region() == 1) + { + // invalid + // ^------ + oss << color::bold << color::red << "^---" << color::reset; + } + else + { + // invalid + // ~~~~~~~ + const auto underline_len = (std::min)( + static_cast(loc.region()), loc.line_str().size()); + oss << color::bold << color::red + << make_string(underline_len, '~') << color::reset; + } + oss << ' '; + oss << comment; + return; + }; + + assert(!loc_com.empty()); + + // --> example.toml + // | + retval << color::bold << color::blue << " --> " << color::reset + << loc_com.front().first.file_name() << '\n'; + retval << make_string(line_num_width + 1, ' ') + << color::bold << color::blue << " |\n" << color::reset; + // 1 | key value + // | ^--- missing = + format_one_location(retval, loc_com.front().first, loc_com.front().second); + + // process the rest of the locations + for(std::size_t i=1; i filename.toml" again + { + retval << color::bold << color::blue << " --> " << color::reset + << curr.first.file_name() << '\n'; + retval << make_string(line_num_width + 1, ' ') + << color::bold << color::blue << " |\n" << color::reset; + } + + format_one_location(retval, curr.first, curr.second); + } + + if(!helps.empty()) + { + retval << '\n'; + retval << make_string(line_num_width + 1, ' '); + retval << color::bold << color::blue << " |" << color::reset; + for(const auto& help : helps) + { + retval << color::bold << "\nHint: " << color::reset; + retval << help; + } + } + return retval.str(); +} + +} // detail +} // toml +#endif// TOML11_SOURCE_LOCATION_HPP diff --git a/src/frontend/qt_sdl/toml/toml/storage.hpp b/src/frontend/qt_sdl/toml/toml/storage.hpp new file mode 100644 index 0000000000..202f9035fd --- /dev/null +++ b/src/frontend/qt_sdl/toml/toml/storage.hpp @@ -0,0 +1,43 @@ +// Copyright Toru Niina 2017. +// Distributed under the MIT License. +#ifndef TOML11_STORAGE_HPP +#define TOML11_STORAGE_HPP +#include "utility.hpp" + +namespace toml +{ +namespace detail +{ + +// this contains pointer and deep-copy the content if copied. +// to avoid recursive pointer. +template +struct storage +{ + using value_type = T; + + explicit storage(value_type const& v): ptr(toml::make_unique(v)) {} + explicit storage(value_type&& v): ptr(toml::make_unique(std::move(v))) {} + ~storage() = default; + storage(const storage& rhs): ptr(toml::make_unique(*rhs.ptr)) {} + storage& operator=(const storage& rhs) + { + this->ptr = toml::make_unique(*rhs.ptr); + return *this; + } + storage(storage&&) = default; + storage& operator=(storage&&) = default; + + bool is_ok() const noexcept {return static_cast(ptr);} + + value_type& value() & noexcept {return *ptr;} + value_type const& value() const& noexcept {return *ptr;} + value_type&& value() && noexcept {return std::move(*ptr);} + + private: + std::unique_ptr ptr; +}; + +} // detail +} // toml +#endif// TOML11_STORAGE_HPP diff --git a/src/frontend/qt_sdl/toml/toml/string.hpp b/src/frontend/qt_sdl/toml/toml/string.hpp new file mode 100644 index 0000000000..def3e57c38 --- /dev/null +++ b/src/frontend/qt_sdl/toml/toml/string.hpp @@ -0,0 +1,228 @@ +// Copyright Toru Niina 2017. +// Distributed under the MIT License. +#ifndef TOML11_STRING_HPP +#define TOML11_STRING_HPP + +#include "version.hpp" + +#include + +#include +#include + +#if TOML11_CPLUSPLUS_STANDARD_VERSION >= 201703L +#if __has_include() +#define TOML11_USING_STRING_VIEW 1 +#include +#endif +#endif + +namespace toml +{ + +enum class string_t : std::uint8_t +{ + basic = 0, + literal = 1, +}; + +struct string +{ + string() = default; + ~string() = default; + string(const string& s) = default; + string(string&& s) = default; + string& operator=(const string& s) = default; + string& operator=(string&& s) = default; + + string(const std::string& s): kind(string_t::basic), str(s){} + string(const std::string& s, string_t k): kind(k), str(s){} + string(const char* s): kind(string_t::basic), str(s){} + string(const char* s, string_t k): kind(k), str(s){} + + string(std::string&& s): kind(string_t::basic), str(std::move(s)){} + string(std::string&& s, string_t k): kind(k), str(std::move(s)){} + + string& operator=(const std::string& s) + {kind = string_t::basic; str = s; return *this;} + string& operator=(std::string&& s) + {kind = string_t::basic; str = std::move(s); return *this;} + + operator std::string& () & noexcept {return str;} + operator std::string const& () const& noexcept {return str;} + operator std::string&& () && noexcept {return std::move(str);} + + string& operator+=(const char* rhs) {str += rhs; return *this;} + string& operator+=(const char rhs) {str += rhs; return *this;} + string& operator+=(const std::string& rhs) {str += rhs; return *this;} + string& operator+=(const string& rhs) {str += rhs.str; return *this;} + +#if defined(TOML11_USING_STRING_VIEW) && TOML11_USING_STRING_VIEW>0 + explicit string(std::string_view s): kind(string_t::basic), str(s){} + string(std::string_view s, string_t k): kind(k), str(s){} + + string& operator=(std::string_view s) + {kind = string_t::basic; str = s; return *this;} + + explicit operator std::string_view() const noexcept + {return std::string_view(str);} + + string& operator+=(const std::string_view& rhs) {str += rhs; return *this;} +#endif + + string_t kind; + std::string str; +}; + +inline bool operator==(const string& lhs, const string& rhs) +{ + return lhs.kind == rhs.kind && lhs.str == rhs.str; +} +inline bool operator!=(const string& lhs, const string& rhs) +{ + return !(lhs == rhs); +} +inline bool operator<(const string& lhs, const string& rhs) +{ + return (lhs.kind == rhs.kind) ? (lhs.str < rhs.str) : (lhs.kind < rhs.kind); +} +inline bool operator>(const string& lhs, const string& rhs) +{ + return rhs < lhs; +} +inline bool operator<=(const string& lhs, const string& rhs) +{ + return !(rhs < lhs); +} +inline bool operator>=(const string& lhs, const string& rhs) +{ + return !(lhs < rhs); +} + +inline bool +operator==(const string& lhs, const std::string& rhs) {return lhs.str == rhs;} +inline bool +operator!=(const string& lhs, const std::string& rhs) {return lhs.str != rhs;} +inline bool +operator< (const string& lhs, const std::string& rhs) {return lhs.str < rhs;} +inline bool +operator> (const string& lhs, const std::string& rhs) {return lhs.str > rhs;} +inline bool +operator<=(const string& lhs, const std::string& rhs) {return lhs.str <= rhs;} +inline bool +operator>=(const string& lhs, const std::string& rhs) {return lhs.str >= rhs;} + +inline bool +operator==(const std::string& lhs, const string& rhs) {return lhs == rhs.str;} +inline bool +operator!=(const std::string& lhs, const string& rhs) {return lhs != rhs.str;} +inline bool +operator< (const std::string& lhs, const string& rhs) {return lhs < rhs.str;} +inline bool +operator> (const std::string& lhs, const string& rhs) {return lhs > rhs.str;} +inline bool +operator<=(const std::string& lhs, const string& rhs) {return lhs <= rhs.str;} +inline bool +operator>=(const std::string& lhs, const string& rhs) {return lhs >= rhs.str;} + +inline bool +operator==(const string& lhs, const char* rhs) {return lhs.str == std::string(rhs);} +inline bool +operator!=(const string& lhs, const char* rhs) {return lhs.str != std::string(rhs);} +inline bool +operator< (const string& lhs, const char* rhs) {return lhs.str < std::string(rhs);} +inline bool +operator> (const string& lhs, const char* rhs) {return lhs.str > std::string(rhs);} +inline bool +operator<=(const string& lhs, const char* rhs) {return lhs.str <= std::string(rhs);} +inline bool +operator>=(const string& lhs, const char* rhs) {return lhs.str >= std::string(rhs);} + +inline bool +operator==(const char* lhs, const string& rhs) {return std::string(lhs) == rhs.str;} +inline bool +operator!=(const char* lhs, const string& rhs) {return std::string(lhs) != rhs.str;} +inline bool +operator< (const char* lhs, const string& rhs) {return std::string(lhs) < rhs.str;} +inline bool +operator> (const char* lhs, const string& rhs) {return std::string(lhs) > rhs.str;} +inline bool +operator<=(const char* lhs, const string& rhs) {return std::string(lhs) <= rhs.str;} +inline bool +operator>=(const char* lhs, const string& rhs) {return std::string(lhs) >= rhs.str;} + +template +std::basic_ostream& +operator<<(std::basic_ostream& os, const string& s) +{ + if(s.kind == string_t::basic) + { + if(std::find(s.str.cbegin(), s.str.cend(), '\n') != s.str.cend()) + { + // it contains newline. make it multiline string. + os << "\"\"\"\n"; + for(auto i=s.str.cbegin(), e=s.str.cend(); i!=e; ++i) + { + switch(*i) + { + case '\\': {os << "\\\\"; break;} + case '\"': {os << "\\\""; break;} + case '\b': {os << "\\b"; break;} + case '\t': {os << "\\t"; break;} + case '\f': {os << "\\f"; break;} + case '\n': {os << '\n'; break;} + case '\r': + { + // since it is a multiline string, + // CRLF is not needed to be escaped. + if(std::next(i) != e && *std::next(i) == '\n') + { + os << "\r\n"; + ++i; + } + else + { + os << "\\r"; + } + break; + } + default: {os << *i; break;} + } + } + os << "\\\n\"\"\""; + return os; + } + // no newline. make it inline. + os << "\""; + for(const auto c : s.str) + { + switch(c) + { + case '\\': {os << "\\\\"; break;} + case '\"': {os << "\\\""; break;} + case '\b': {os << "\\b"; break;} + case '\t': {os << "\\t"; break;} + case '\f': {os << "\\f"; break;} + case '\n': {os << "\\n"; break;} + case '\r': {os << "\\r"; break;} + default : {os << c; break;} + } + } + os << "\""; + return os; + } + // the string `s` is literal-string. + if(std::find(s.str.cbegin(), s.str.cend(), '\n') != s.str.cend() || + std::find(s.str.cbegin(), s.str.cend(), '\'') != s.str.cend() ) + { + // contains newline or single quote. make it multiline. + os << "'''\n" << s.str << "'''"; + return os; + } + // normal literal string + os << '\'' << s.str << '\''; + return os; +} + +} // toml +#endif// TOML11_STRING_H diff --git a/src/frontend/qt_sdl/toml/toml/traits.hpp b/src/frontend/qt_sdl/toml/toml/traits.hpp new file mode 100644 index 0000000000..255d9e888c --- /dev/null +++ b/src/frontend/qt_sdl/toml/toml/traits.hpp @@ -0,0 +1,328 @@ +// Copyright Toru Niina 2017. +// Distributed under the MIT License. +#ifndef TOML11_TRAITS_HPP +#define TOML11_TRAITS_HPP + +#include "from.hpp" +#include "into.hpp" +#include "version.hpp" + +#include +#include +#include +#include +#include +#include + +#if TOML11_CPLUSPLUS_STANDARD_VERSION >= 201703L +#if __has_include() +#include +#endif // has_include() +#endif // cplusplus >= C++17 + +namespace toml +{ +template class T, template class A> +class basic_value; + +namespace detail +{ +// --------------------------------------------------------------------------- +// check whether type T is a kind of container/map class + +struct has_iterator_impl +{ + template static std::true_type check(typename T::iterator*); + template static std::false_type check(...); +}; +struct has_value_type_impl +{ + template static std::true_type check(typename T::value_type*); + template static std::false_type check(...); +}; +struct has_key_type_impl +{ + template static std::true_type check(typename T::key_type*); + template static std::false_type check(...); +}; +struct has_mapped_type_impl +{ + template static std::true_type check(typename T::mapped_type*); + template static std::false_type check(...); +}; +struct has_reserve_method_impl +{ + template static std::false_type check(...); + template static std::true_type check( + decltype(std::declval().reserve(std::declval()))*); +}; +struct has_push_back_method_impl +{ + template static std::false_type check(...); + template static std::true_type check( + decltype(std::declval().push_back(std::declval()))*); +}; +struct is_comparable_impl +{ + template static std::false_type check(...); + template static std::true_type check( + decltype(std::declval() < std::declval())*); +}; + +struct has_from_toml_method_impl +{ + template class Tb, template class A> + static std::true_type check( + decltype(std::declval().from_toml( + std::declval<::toml::basic_value>()))*); + + template class Tb, template class A> + static std::false_type check(...); +}; +struct has_into_toml_method_impl +{ + template + static std::true_type check(decltype(std::declval().into_toml())*); + template + static std::false_type check(...); +}; + +struct has_specialized_from_impl +{ + template + static std::false_type check(...); + template)> + static std::true_type check(::toml::from*); +}; +struct has_specialized_into_impl +{ + template + static std::false_type check(...); + template)> + static std::true_type check(::toml::from*); +}; + + +/// Intel C++ compiler can not use decltype in parent class declaration, here +/// is a hack to work around it. https://stackoverflow.com/a/23953090/4692076 +#ifdef __INTEL_COMPILER +#define decltype(...) std::enable_if::type +#endif + +template +struct has_iterator : decltype(has_iterator_impl::check(nullptr)){}; +template +struct has_value_type : decltype(has_value_type_impl::check(nullptr)){}; +template +struct has_key_type : decltype(has_key_type_impl::check(nullptr)){}; +template +struct has_mapped_type : decltype(has_mapped_type_impl::check(nullptr)){}; +template +struct has_reserve_method : decltype(has_reserve_method_impl::check(nullptr)){}; +template +struct has_push_back_method : decltype(has_push_back_method_impl::check(nullptr)){}; +template +struct is_comparable : decltype(is_comparable_impl::check(nullptr)){}; + +template class Tb, template class A> +struct has_from_toml_method +: decltype(has_from_toml_method_impl::check(nullptr)){}; + +template +struct has_into_toml_method +: decltype(has_into_toml_method_impl::check(nullptr)){}; + +template +struct has_specialized_from : decltype(has_specialized_from_impl::check(nullptr)){}; +template +struct has_specialized_into : decltype(has_specialized_into_impl::check(nullptr)){}; + +#ifdef __INTEL_COMPILER +#undef decltype +#endif + +// --------------------------------------------------------------------------- +// C++17 and/or/not + +#if TOML11_CPLUSPLUS_STANDARD_VERSION >= 201703L + +using std::conjunction; +using std::disjunction; +using std::negation; + +#else + +template struct conjunction : std::true_type{}; +template struct conjunction : T{}; +template +struct conjunction : + std::conditional(T::value), conjunction, T>::type +{}; + +template struct disjunction : std::false_type{}; +template struct disjunction : T {}; +template +struct disjunction : + std::conditional(T::value), T, disjunction>::type +{}; + +template +struct negation : std::integral_constant(T::value)>{}; + +#endif + +// --------------------------------------------------------------------------- +// type checkers + +template struct is_std_pair : std::false_type{}; +template +struct is_std_pair> : std::true_type{}; + +template struct is_std_tuple : std::false_type{}; +template +struct is_std_tuple> : std::true_type{}; + +template struct is_std_forward_list : std::false_type{}; +template +struct is_std_forward_list> : std::true_type{}; + +template struct is_chrono_duration: std::false_type{}; +template +struct is_chrono_duration>: std::true_type{}; + +template +struct is_map : conjunction< // map satisfies all the following conditions + has_iterator, // has T::iterator + has_value_type, // has T::value_type + has_key_type, // has T::key_type + has_mapped_type // has T::mapped_type + >{}; +template struct is_map : is_map{}; +template struct is_map : is_map{}; +template struct is_map : is_map{}; +template struct is_map : is_map{}; + +template +struct is_container : conjunction< + negation>, // not a map + negation>, // not a std::string +#if TOML11_CPLUSPLUS_STANDARD_VERSION >= 201703L +#if __has_include() + negation>, // not a std::string_view +#endif // has_include() +#endif + has_iterator, // has T::iterator + has_value_type // has T::value_type + >{}; +template struct is_container : is_container{}; +template struct is_container : is_container{}; +template struct is_container : is_container{}; +template struct is_container : is_container{}; + +template +struct is_basic_value: std::false_type{}; +template struct is_basic_value : is_basic_value{}; +template struct is_basic_value : is_basic_value{}; +template struct is_basic_value : is_basic_value{}; +template struct is_basic_value : is_basic_value{}; +template class M, template class V> +struct is_basic_value<::toml::basic_value>: std::true_type{}; + +// --------------------------------------------------------------------------- +// C++14 index_sequence + +#if TOML11_CPLUSPLUS_STANDARD_VERSION >= 201402L + +using std::index_sequence; +using std::make_index_sequence; + +#else + +template struct index_sequence{}; + +template struct push_back_index_sequence{}; +template +struct push_back_index_sequence, N> +{ + typedef index_sequence type; +}; + +template +struct index_sequence_maker +{ + typedef typename push_back_index_sequence< + typename index_sequence_maker::type, N>::type type; +}; +template<> +struct index_sequence_maker<0> +{ + typedef index_sequence<0> type; +}; +template +using make_index_sequence = typename index_sequence_maker::type; + +#endif // cplusplus >= 2014 + +// --------------------------------------------------------------------------- +// C++14 enable_if_t + +#if TOML11_CPLUSPLUS_STANDARD_VERSION >= 201402L + +using std::enable_if_t; + +#else + +template +using enable_if_t = typename std::enable_if::type; + +#endif // cplusplus >= 2014 + +// --------------------------------------------------------------------------- +// return_type_of_t + +#if TOML11_CPLUSPLUS_STANDARD_VERSION >= 201703L && defined(__cpp_lib_is_invocable) && __cpp_lib_is_invocable>=201703 + +template +using return_type_of_t = std::invoke_result_t; + +#else +// result_of is deprecated after C++17 +template +using return_type_of_t = typename std::result_of::type; + +#endif + +// --------------------------------------------------------------------------- +// is_string_literal +// +// to use this, pass `typename remove_reference::type` to T. + +template +struct is_string_literal: +disjunction< + std::is_same, + conjunction< + std::is_array, + std::is_same::type> + > + >{}; + +// --------------------------------------------------------------------------- +// C++20 remove_cvref_t + +template +struct remove_cvref +{ + using type = typename std::remove_cv< + typename std::remove_reference::type>::type; +}; + +template +using remove_cvref_t = typename remove_cvref::type; + +}// detail +}//toml +#endif // TOML_TRAITS diff --git a/src/frontend/qt_sdl/toml/toml/types.hpp b/src/frontend/qt_sdl/toml/toml/types.hpp new file mode 100644 index 0000000000..1e420e7fd3 --- /dev/null +++ b/src/frontend/qt_sdl/toml/toml/types.hpp @@ -0,0 +1,173 @@ +// Copyright Toru Niina 2017. +// Distributed under the MIT License. +#ifndef TOML11_TYPES_HPP +#define TOML11_TYPES_HPP +#include +#include + +#include "comments.hpp" +#include "datetime.hpp" +#include "string.hpp" +#include "traits.hpp" + +namespace toml +{ + +template class Table, // map-like class + template class Array> // vector-like class +class basic_value; + +using character = char; +using key = std::string; + +#if !defined(__clang__) && defined(__GNUC__) && __GNUC__ <= 4 +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wshadow" +#endif + +using boolean = bool; +using integer = std::int64_t; +using floating = double; // "float" is a keyword, cannot use it here. +// the following stuffs are structs defined here, so aliases are not needed. +// - string +// - offset_datetime +// - offset_datetime +// - local_datetime +// - local_date +// - local_time + +#if defined(__GNUC__) && !defined(__clang__) +# pragma GCC diagnostic pop +#endif + +// default toml::value and default array/table. these are defined after defining +// basic_value itself. +// using value = basic_value; +// using array = typename value::array_type; +// using table = typename value::table_type; + +// to avoid warnings about `value_t::integer` is "shadowing" toml::integer in +// GCC -Wshadow=global. +#if defined(__GNUC__) && !defined(__clang__) +# pragma GCC diagnostic push +# if 7 <= __GNUC__ +# pragma GCC diagnostic ignored "-Wshadow=global" +# else // gcc-6 or older +# pragma GCC diagnostic ignored "-Wshadow" +# endif +#endif +enum class value_t : std::uint8_t +{ + empty = 0, + boolean = 1, + integer = 2, + floating = 3, + string = 4, + offset_datetime = 5, + local_datetime = 6, + local_date = 7, + local_time = 8, + array = 9, + table = 10, +}; +#if defined(__GNUC__) && !defined(__clang__) +# pragma GCC diagnostic pop +#endif + +template +inline std::basic_ostream& +operator<<(std::basic_ostream& os, value_t t) +{ + switch(t) + { + case value_t::boolean : os << "boolean"; return os; + case value_t::integer : os << "integer"; return os; + case value_t::floating : os << "floating"; return os; + case value_t::string : os << "string"; return os; + case value_t::offset_datetime : os << "offset_datetime"; return os; + case value_t::local_datetime : os << "local_datetime"; return os; + case value_t::local_date : os << "local_date"; return os; + case value_t::local_time : os << "local_time"; return os; + case value_t::array : os << "array"; return os; + case value_t::table : os << "table"; return os; + case value_t::empty : os << "empty"; return os; + default : os << "unknown"; return os; + } +} + +template, + typename alloc = std::allocator> +inline std::basic_string stringize(value_t t) +{ + std::basic_ostringstream oss; + oss << t; + return oss.str(); +} + +namespace detail +{ + +// helper to define a type that represents a value_t value. +template +using value_t_constant = std::integral_constant; + +// meta-function that convertes from value_t to the exact toml type that corresponds to. +// It takes toml::basic_value type because array and table types depend on it. +template struct enum_to_type {using type = void ;}; +template struct enum_to_type{using type = void ;}; +template struct enum_to_type{using type = boolean ;}; +template struct enum_to_type{using type = integer ;}; +template struct enum_to_type{using type = floating ;}; +template struct enum_to_type{using type = string ;}; +template struct enum_to_type{using type = offset_datetime ;}; +template struct enum_to_type{using type = local_datetime ;}; +template struct enum_to_type{using type = local_date ;}; +template struct enum_to_type{using type = local_time ;}; +template struct enum_to_type{using type = typename Value::array_type;}; +template struct enum_to_type{using type = typename Value::table_type;}; + +// meta-function that converts from an exact toml type to the enum that corresponds to. +template +struct type_to_enum : std::conditional< + std::is_same::value, // if T == array_type, + value_t_constant, // then value_t::array + typename std::conditional< // else... + std::is_same::value, // if T == table_type + value_t_constant, // then value_t::table + value_t_constant // else value_t::empty + >::type + >::type {}; +template struct type_to_enum: value_t_constant {}; +template struct type_to_enum: value_t_constant {}; +template struct type_to_enum: value_t_constant {}; +template struct type_to_enum: value_t_constant {}; +template struct type_to_enum: value_t_constant {}; +template struct type_to_enum: value_t_constant {}; +template struct type_to_enum: value_t_constant {}; +template struct type_to_enum: value_t_constant {}; + +// meta-function that checks the type T is the same as one of the toml::* types. +template +struct is_exact_toml_type : disjunction< + std::is_same, + std::is_same, + std::is_same, + std::is_same, + std::is_same, + std::is_same, + std::is_same, + std::is_same, + std::is_same, + std::is_same + >{}; +template struct is_exact_toml_type : is_exact_toml_type{}; +template struct is_exact_toml_type : is_exact_toml_type{}; +template struct is_exact_toml_type : is_exact_toml_type{}; +template struct is_exact_toml_type: is_exact_toml_type{}; + +} // detail +} // toml + +#endif// TOML11_TYPES_H diff --git a/src/frontend/qt_sdl/toml/toml/utility.hpp b/src/frontend/qt_sdl/toml/toml/utility.hpp new file mode 100644 index 0000000000..53a18b944a --- /dev/null +++ b/src/frontend/qt_sdl/toml/toml/utility.hpp @@ -0,0 +1,150 @@ +// Copyright Toru Niina 2017. +// Distributed under the MIT License. +#ifndef TOML11_UTILITY_HPP +#define TOML11_UTILITY_HPP +#include +#include +#include + +#include "traits.hpp" +#include "version.hpp" + +#if TOML11_CPLUSPLUS_STANDARD_VERSION >= 201402L +# define TOML11_MARK_AS_DEPRECATED(msg) [[deprecated(msg)]] +#elif defined(__GNUC__) +# define TOML11_MARK_AS_DEPRECATED(msg) __attribute__((deprecated(msg))) +#elif defined(_MSC_VER) +# define TOML11_MARK_AS_DEPRECATED(msg) __declspec(deprecated(msg)) +#else +# define TOML11_MARK_AS_DEPRECATED +#endif + +namespace toml +{ + +#if TOML11_CPLUSPLUS_STANDARD_VERSION >= 201402L + +using std::make_unique; + +#else + +template +inline std::unique_ptr make_unique(Ts&& ... args) +{ + return std::unique_ptr(new T(std::forward(args)...)); +} + +#endif // TOML11_CPLUSPLUS_STANDARD_VERSION >= 2014 + +namespace detail +{ +template +void try_reserve_impl(Container& container, std::size_t N, std::true_type) +{ + container.reserve(N); + return; +} +template +void try_reserve_impl(Container&, std::size_t, std::false_type) noexcept +{ + return; +} +} // detail + +template +void try_reserve(Container& container, std::size_t N) +{ + if(N <= container.size()) {return;} + detail::try_reserve_impl(container, N, detail::has_reserve_method{}); + return; +} + +namespace detail +{ +inline std::string concat_to_string_impl(std::ostringstream& oss) +{ + return oss.str(); +} +template +std::string concat_to_string_impl(std::ostringstream& oss, T&& head, Ts&& ... tail) +{ + oss << std::forward(head); + return concat_to_string_impl(oss, std::forward(tail) ... ); +} +} // detail + +template +std::string concat_to_string(Ts&& ... args) +{ + std::ostringstream oss; + oss << std::boolalpha << std::fixed; + return detail::concat_to_string_impl(oss, std::forward(args) ...); +} + +template +T from_string(const std::string& str, T opt) +{ + T v(opt); + std::istringstream iss(str); + iss >> v; + return v; +} + +namespace detail +{ +#if TOML11_CPLUSPLUS_STANDARD_VERSION >= 201402L +template +decltype(auto) last_one(T&& tail) noexcept +{ + return std::forward(tail); +} + +template +decltype(auto) last_one(T&& /*head*/, Ts&& ... tail) noexcept +{ + return last_one(std::forward(tail)...); +} +#else // C++11 +// The following code +// ```cpp +// 1 | template +// 2 | auto last_one(T&& /*head*/, Ts&& ... tail) +// 3 | -> decltype(last_one(std::forward(tail)...)) +// 4 | { +// 5 | return last_one(std::forward(tail)...); +// 6 | } +// ``` +// does not work because the function `last_one(...)` is not yet defined at +// line #3, so `decltype()` cannot deduce the type returned from `last_one`. +// So we need to determine return type in a different way, like a meta func. + +template +struct last_one_in_pack +{ + using type = typename last_one_in_pack::type; +}; +template +struct last_one_in_pack +{ + using type = T; +}; +template +using last_one_in_pack_t = typename last_one_in_pack::type; + +template +T&& last_one(T&& tail) noexcept +{ + return std::forward(tail); +} +template +enable_if_t<(sizeof...(Ts) > 0), last_one_in_pack_t> +last_one(T&& /*head*/, Ts&& ... tail) +{ + return last_one(std::forward(tail)...); +} + +#endif +} // detail + +}// toml +#endif // TOML11_UTILITY diff --git a/src/frontend/qt_sdl/toml/toml/value.hpp b/src/frontend/qt_sdl/toml/toml/value.hpp new file mode 100644 index 0000000000..1b43db8d4c --- /dev/null +++ b/src/frontend/qt_sdl/toml/toml/value.hpp @@ -0,0 +1,2035 @@ +// Copyright Toru Niina 2017. +// Distributed under the MIT License. +#ifndef TOML11_VALUE_HPP +#define TOML11_VALUE_HPP +#include + +#include "comments.hpp" +#include "exception.hpp" +#include "into.hpp" +#include "region.hpp" +#include "source_location.hpp" +#include "storage.hpp" +#include "traits.hpp" +#include "types.hpp" +#include "utility.hpp" + +namespace toml +{ + +namespace detail +{ + +// to show error messages. not recommended for users. +template +inline region_base const* get_region(const Value& v) +{ + return v.region_info_.get(); +} + +template +void change_region(Value& v, region reg) +{ + v.region_info_ = std::make_shared(std::move(reg)); + return; +} + +template +[[noreturn]] inline void +throw_bad_cast(const std::string& funcname, value_t actual, const Value& v) +{ + throw type_error(detail::format_underline( + concat_to_string(funcname, "bad_cast to ", Expected), { + {v.location(), concat_to_string("the actual type is ", actual)} + }), v.location()); +} + +// Throw `out_of_range` from `toml::value::at()` and `toml::find()` +// after generating an error message. +// +// The implementation is a bit complicated and there are many edge-cases. +// If you are not interested in the error message generation, just skip this. +template +[[noreturn]] void +throw_key_not_found_error(const Value& v, const key& ky) +{ + // The top-level table has its region at the first character of the file. + // That means that, in the case when a key is not found in the top-level + // table, the error message points to the first character. If the file has + // its first table at the first line, the error message would be like this. + // ```console + // [error] key "a" not found + // --> example.toml + // | + // 1 | [table] + // | ^------ in this table + // ``` + // It actually points to the top-level table at the first character, + // not `[table]`. But it is too confusing. To avoid the confusion, the error + // message should explicitly say "key not found in the top-level table", + // or "the parsed file is empty" if there is no content at all (0 bytes in file). + const auto loc = v.location(); + if(loc.line() == 1 && loc.region() == 0) + { + // First line with a zero-length region means "empty file". + // The region will be generated at `parse_toml_file` function + // if the file contains no bytes. + throw std::out_of_range(format_underline(concat_to_string( + "key \"", ky, "\" not found in the top-level table"), { + {loc, "the parsed file is empty"} + })); + } + else if(loc.line() == 1 && loc.region() == 1) + { + // Here it assumes that top-level table starts at the first character. + // The region corresponds to the top-level table will be generated at + // `parse_toml_file` function. + // It also assumes that the top-level table size is just one and + // the line number is `1`. It is always satisfied. And those conditions + // are satisfied only if the table is the top-level table. + // + // 1. one-character dot-key at the first line + // ```toml + // a.b = "c" + // ``` + // toml11 counts whole key as the table key. Here, `a.b` is the region + // of the table "a". It could be counter intuitive, but it works. + // The size of the region is 3, not 1. The above example is the shortest + // dot-key example. The size cannot be 1. + // + // 2. one-character inline-table at the first line + // ```toml + // a = {b = "c"} + // ``` + // toml11 considers the inline table body as the table region. Here, + // `{b = "c"}` is the region of the table "a". The size of the region + // is 9, not 1. The shotest inline table still has two characters, `{` + // and `}`. The size cannot be 1. + // + // 3. one-character table declaration at the first line + // ```toml + // [a] + // ``` + // toml11 considers the whole table key as the table region. Here, + // `[a]` is the table region. The size is 3, not 1. + // + throw std::out_of_range(format_underline(concat_to_string( + "key \"", ky, "\" not found in the top-level table"), { + {loc, "the top-level table starts here"} + })); + } + else + { + // normal table. + throw std::out_of_range(format_underline(concat_to_string( + "key \"", ky, "\" not found"), { {loc, "in this table"} })); + } +} + +// switch by `value_t` at the compile time. +template +struct switch_cast {}; +#define TOML11_GENERATE_SWITCH_CASTER(TYPE) \ + template<> \ + struct switch_cast \ + { \ + template \ + static typename Value::TYPE##_type& invoke(Value& v) \ + { \ + return v.as_##TYPE(); \ + } \ + template \ + static typename Value::TYPE##_type const& invoke(const Value& v) \ + { \ + return v.as_##TYPE(); \ + } \ + template \ + static typename Value::TYPE##_type&& invoke(Value&& v) \ + { \ + return std::move(v).as_##TYPE(); \ + } \ + }; \ + /**/ +TOML11_GENERATE_SWITCH_CASTER(boolean) +TOML11_GENERATE_SWITCH_CASTER(integer) +TOML11_GENERATE_SWITCH_CASTER(floating) +TOML11_GENERATE_SWITCH_CASTER(string) +TOML11_GENERATE_SWITCH_CASTER(offset_datetime) +TOML11_GENERATE_SWITCH_CASTER(local_datetime) +TOML11_GENERATE_SWITCH_CASTER(local_date) +TOML11_GENERATE_SWITCH_CASTER(local_time) +TOML11_GENERATE_SWITCH_CASTER(array) +TOML11_GENERATE_SWITCH_CASTER(table) + +#undef TOML11_GENERATE_SWITCH_CASTER + +}// detail + +template class Table = std::unordered_map, + template class Array = std::vector> +class basic_value +{ + template + static void assigner(T& dst, U&& v) + { + const auto tmp = ::new(std::addressof(dst)) T(std::forward(v)); + assert(tmp == std::addressof(dst)); + (void)tmp; + } + + using region_base = detail::region_base; + + template class T, + template class A> + friend class basic_value; + + public: + + using comment_type = Comment; + using key_type = ::toml::key; + using value_type = basic_value; + using boolean_type = ::toml::boolean; + using integer_type = ::toml::integer; + using floating_type = ::toml::floating; + using string_type = ::toml::string; + using local_time_type = ::toml::local_time; + using local_date_type = ::toml::local_date; + using local_datetime_type = ::toml::local_datetime; + using offset_datetime_type = ::toml::offset_datetime; + using array_type = Array; + using table_type = Table; + + public: + + basic_value() noexcept + : type_(value_t::empty), + region_info_(std::make_shared(region_base{})) + {} + ~basic_value() noexcept {this->cleanup();} + + basic_value(const basic_value& v) + : type_(v.type()), region_info_(v.region_info_), comments_(v.comments_) + { + switch(v.type()) + { + case value_t::boolean : assigner(boolean_ , v.boolean_ ); break; + case value_t::integer : assigner(integer_ , v.integer_ ); break; + case value_t::floating : assigner(floating_ , v.floating_ ); break; + case value_t::string : assigner(string_ , v.string_ ); break; + case value_t::offset_datetime: assigner(offset_datetime_, v.offset_datetime_); break; + case value_t::local_datetime : assigner(local_datetime_ , v.local_datetime_ ); break; + case value_t::local_date : assigner(local_date_ , v.local_date_ ); break; + case value_t::local_time : assigner(local_time_ , v.local_time_ ); break; + case value_t::array : assigner(array_ , v.array_ ); break; + case value_t::table : assigner(table_ , v.table_ ); break; + default: break; + } + } + basic_value(basic_value&& v) + : type_(v.type()), region_info_(std::move(v.region_info_)), + comments_(std::move(v.comments_)) + { + switch(this->type_) // here this->type_ is already initialized + { + case value_t::boolean : assigner(boolean_ , std::move(v.boolean_ )); break; + case value_t::integer : assigner(integer_ , std::move(v.integer_ )); break; + case value_t::floating : assigner(floating_ , std::move(v.floating_ )); break; + case value_t::string : assigner(string_ , std::move(v.string_ )); break; + case value_t::offset_datetime: assigner(offset_datetime_, std::move(v.offset_datetime_)); break; + case value_t::local_datetime : assigner(local_datetime_ , std::move(v.local_datetime_ )); break; + case value_t::local_date : assigner(local_date_ , std::move(v.local_date_ )); break; + case value_t::local_time : assigner(local_time_ , std::move(v.local_time_ )); break; + case value_t::array : assigner(array_ , std::move(v.array_ )); break; + case value_t::table : assigner(table_ , std::move(v.table_ )); break; + default: break; + } + } + basic_value& operator=(const basic_value& v) + { + this->cleanup(); + this->region_info_ = v.region_info_; + this->comments_ = v.comments_; + this->type_ = v.type(); + switch(this->type_) + { + case value_t::boolean : assigner(boolean_ , v.boolean_ ); break; + case value_t::integer : assigner(integer_ , v.integer_ ); break; + case value_t::floating : assigner(floating_ , v.floating_ ); break; + case value_t::string : assigner(string_ , v.string_ ); break; + case value_t::offset_datetime: assigner(offset_datetime_, v.offset_datetime_); break; + case value_t::local_datetime : assigner(local_datetime_ , v.local_datetime_ ); break; + case value_t::local_date : assigner(local_date_ , v.local_date_ ); break; + case value_t::local_time : assigner(local_time_ , v.local_time_ ); break; + case value_t::array : assigner(array_ , v.array_ ); break; + case value_t::table : assigner(table_ , v.table_ ); break; + default: break; + } + return *this; + } + basic_value& operator=(basic_value&& v) + { + this->cleanup(); + this->region_info_ = std::move(v.region_info_); + this->comments_ = std::move(v.comments_); + this->type_ = v.type(); + switch(this->type_) + { + case value_t::boolean : assigner(boolean_ , std::move(v.boolean_ )); break; + case value_t::integer : assigner(integer_ , std::move(v.integer_ )); break; + case value_t::floating : assigner(floating_ , std::move(v.floating_ )); break; + case value_t::string : assigner(string_ , std::move(v.string_ )); break; + case value_t::offset_datetime: assigner(offset_datetime_, std::move(v.offset_datetime_)); break; + case value_t::local_datetime : assigner(local_datetime_ , std::move(v.local_datetime_ )); break; + case value_t::local_date : assigner(local_date_ , std::move(v.local_date_ )); break; + case value_t::local_time : assigner(local_time_ , std::move(v.local_time_ )); break; + case value_t::array : assigner(array_ , std::move(v.array_ )); break; + case value_t::table : assigner(table_ , std::move(v.table_ )); break; + default: break; + } + return *this; + } + + // overwrite comments ---------------------------------------------------- + + basic_value(const basic_value& v, std::vector com) + : type_(v.type()), region_info_(v.region_info_), + comments_(std::move(com)) + { + switch(v.type()) + { + case value_t::boolean : assigner(boolean_ , v.boolean_ ); break; + case value_t::integer : assigner(integer_ , v.integer_ ); break; + case value_t::floating : assigner(floating_ , v.floating_ ); break; + case value_t::string : assigner(string_ , v.string_ ); break; + case value_t::offset_datetime: assigner(offset_datetime_, v.offset_datetime_); break; + case value_t::local_datetime : assigner(local_datetime_ , v.local_datetime_ ); break; + case value_t::local_date : assigner(local_date_ , v.local_date_ ); break; + case value_t::local_time : assigner(local_time_ , v.local_time_ ); break; + case value_t::array : assigner(array_ , v.array_ ); break; + case value_t::table : assigner(table_ , v.table_ ); break; + default: break; + } + } + + basic_value(basic_value&& v, std::vector com) + : type_(v.type()), region_info_(std::move(v.region_info_)), + comments_(std::move(com)) + { + switch(this->type_) // here this->type_ is already initialized + { + case value_t::boolean : assigner(boolean_ , std::move(v.boolean_ )); break; + case value_t::integer : assigner(integer_ , std::move(v.integer_ )); break; + case value_t::floating : assigner(floating_ , std::move(v.floating_ )); break; + case value_t::string : assigner(string_ , std::move(v.string_ )); break; + case value_t::offset_datetime: assigner(offset_datetime_, std::move(v.offset_datetime_)); break; + case value_t::local_datetime : assigner(local_datetime_ , std::move(v.local_datetime_ )); break; + case value_t::local_date : assigner(local_date_ , std::move(v.local_date_ )); break; + case value_t::local_time : assigner(local_time_ , std::move(v.local_time_ )); break; + case value_t::array : assigner(array_ , std::move(v.array_ )); break; + case value_t::table : assigner(table_ , std::move(v.table_ )); break; + default: break; + } + } + + // ----------------------------------------------------------------------- + // conversion between different basic_values. + template class T, + template class A> + basic_value(const basic_value& v) + : type_(v.type()), region_info_(v.region_info_), comments_(v.comments()) + { + switch(v.type()) + { + case value_t::boolean : assigner(boolean_ , v.boolean_ ); break; + case value_t::integer : assigner(integer_ , v.integer_ ); break; + case value_t::floating : assigner(floating_ , v.floating_ ); break; + case value_t::string : assigner(string_ , v.string_ ); break; + case value_t::offset_datetime: assigner(offset_datetime_, v.offset_datetime_); break; + case value_t::local_datetime : assigner(local_datetime_ , v.local_datetime_ ); break; + case value_t::local_date : assigner(local_date_ , v.local_date_ ); break; + case value_t::local_time : assigner(local_time_ , v.local_time_ ); break; + case value_t::array : + { + array_type tmp(v.as_array(std::nothrow).begin(), + v.as_array(std::nothrow).end()); + assigner(array_, std::move(tmp)); + break; + } + case value_t::table : + { + table_type tmp(v.as_table(std::nothrow).begin(), + v.as_table(std::nothrow).end()); + assigner(table_, std::move(tmp)); + break; + } + default: break; + } + } + template class T, + template class A> + basic_value(const basic_value& v, std::vector com) + : type_(v.type()), region_info_(v.region_info_), + comments_(std::move(com)) + { + switch(v.type()) + { + case value_t::boolean : assigner(boolean_ , v.boolean_ ); break; + case value_t::integer : assigner(integer_ , v.integer_ ); break; + case value_t::floating : assigner(floating_ , v.floating_ ); break; + case value_t::string : assigner(string_ , v.string_ ); break; + case value_t::offset_datetime: assigner(offset_datetime_, v.offset_datetime_); break; + case value_t::local_datetime : assigner(local_datetime_ , v.local_datetime_ ); break; + case value_t::local_date : assigner(local_date_ , v.local_date_ ); break; + case value_t::local_time : assigner(local_time_ , v.local_time_ ); break; + case value_t::array : + { + array_type tmp(v.as_array(std::nothrow).begin(), + v.as_array(std::nothrow).end()); + assigner(array_, std::move(tmp)); + break; + } + case value_t::table : + { + table_type tmp(v.as_table(std::nothrow).begin(), + v.as_table(std::nothrow).end()); + assigner(table_, std::move(tmp)); + break; + } + default: break; + } + } + template class T, + template class A> + basic_value& operator=(const basic_value& v) + { + this->region_info_ = v.region_info_; + this->comments_ = comment_type(v.comments()); + this->type_ = v.type(); + switch(v.type()) + { + case value_t::boolean : assigner(boolean_ , v.boolean_ ); break; + case value_t::integer : assigner(integer_ , v.integer_ ); break; + case value_t::floating : assigner(floating_ , v.floating_ ); break; + case value_t::string : assigner(string_ , v.string_ ); break; + case value_t::offset_datetime: assigner(offset_datetime_, v.offset_datetime_); break; + case value_t::local_datetime : assigner(local_datetime_ , v.local_datetime_ ); break; + case value_t::local_date : assigner(local_date_ , v.local_date_ ); break; + case value_t::local_time : assigner(local_time_ , v.local_time_ ); break; + case value_t::array : + { + array_type tmp(v.as_array(std::nothrow).begin(), + v.as_array(std::nothrow).end()); + assigner(array_, std::move(tmp)); + break; + } + case value_t::table : + { + table_type tmp(v.as_table(std::nothrow).begin(), + v.as_table(std::nothrow).end()); + assigner(table_, std::move(tmp)); + break; + } + default: break; + } + return *this; + } + + // boolean ============================================================== + + basic_value(boolean b) + : type_(value_t::boolean), + region_info_(std::make_shared(region_base{})) + { + assigner(this->boolean_, b); + } + basic_value& operator=(boolean b) + { + this->cleanup(); + this->type_ = value_t::boolean; + this->region_info_ = std::make_shared(region_base{}); + assigner(this->boolean_, b); + return *this; + } + basic_value(boolean b, std::vector com) + : type_(value_t::boolean), + region_info_(std::make_shared(region_base{})), + comments_(std::move(com)) + { + assigner(this->boolean_, b); + } + + // integer ============================================================== + + template, detail::negation>>::value, + std::nullptr_t>::type = nullptr> + basic_value(T i) + : type_(value_t::integer), + region_info_(std::make_shared(region_base{})) + { + assigner(this->integer_, static_cast(i)); + } + + template, detail::negation>>::value, + std::nullptr_t>::type = nullptr> + basic_value& operator=(T i) + { + this->cleanup(); + this->type_ = value_t::integer; + this->region_info_ = std::make_shared(region_base{}); + assigner(this->integer_, static_cast(i)); + return *this; + } + + template, detail::negation>>::value, + std::nullptr_t>::type = nullptr> + basic_value(T i, std::vector com) + : type_(value_t::integer), + region_info_(std::make_shared(region_base{})), + comments_(std::move(com)) + { + assigner(this->integer_, static_cast(i)); + } + + // floating ============================================================= + + template::value, std::nullptr_t>::type = nullptr> + basic_value(T f) + : type_(value_t::floating), + region_info_(std::make_shared(region_base{})) + { + assigner(this->floating_, static_cast(f)); + } + + + template::value, std::nullptr_t>::type = nullptr> + basic_value& operator=(T f) + { + this->cleanup(); + this->type_ = value_t::floating; + this->region_info_ = std::make_shared(region_base{}); + assigner(this->floating_, static_cast(f)); + return *this; + } + + template::value, std::nullptr_t>::type = nullptr> + basic_value(T f, std::vector com) + : type_(value_t::floating), + region_info_(std::make_shared(region_base{})), + comments_(std::move(com)) + { + assigner(this->floating_, f); + } + + // string =============================================================== + + basic_value(toml::string s) + : type_(value_t::string), + region_info_(std::make_shared(region_base{})) + { + assigner(this->string_, std::move(s)); + } + basic_value& operator=(toml::string s) + { + this->cleanup(); + this->type_ = value_t::string ; + this->region_info_ = std::make_shared(region_base{}); + assigner(this->string_, s); + return *this; + } + basic_value(toml::string s, std::vector com) + : type_(value_t::string), + region_info_(std::make_shared(region_base{})), + comments_(std::move(com)) + { + assigner(this->string_, std::move(s)); + } + + basic_value(std::string s) + : type_(value_t::string), + region_info_(std::make_shared(region_base{})) + { + assigner(this->string_, toml::string(std::move(s))); + } + basic_value& operator=(std::string s) + { + this->cleanup(); + this->type_ = value_t::string ; + this->region_info_ = std::make_shared(region_base{}); + assigner(this->string_, toml::string(std::move(s))); + return *this; + } + basic_value(std::string s, string_t kind) + : type_(value_t::string), + region_info_(std::make_shared(region_base{})) + { + assigner(this->string_, toml::string(std::move(s), kind)); + } + basic_value(std::string s, std::vector com) + : type_(value_t::string), + region_info_(std::make_shared(region_base{})), + comments_(std::move(com)) + { + assigner(this->string_, toml::string(std::move(s))); + } + basic_value(std::string s, string_t kind, std::vector com) + : type_(value_t::string), + region_info_(std::make_shared(region_base{})), + comments_(std::move(com)) + { + assigner(this->string_, toml::string(std::move(s), kind)); + } + + basic_value(const char* s) + : type_(value_t::string), + region_info_(std::make_shared(region_base{})) + { + assigner(this->string_, toml::string(std::string(s))); + } + basic_value& operator=(const char* s) + { + this->cleanup(); + this->type_ = value_t::string ; + this->region_info_ = std::make_shared(region_base{}); + assigner(this->string_, toml::string(std::string(s))); + return *this; + } + basic_value(const char* s, string_t kind) + : type_(value_t::string), + region_info_(std::make_shared(region_base{})) + { + assigner(this->string_, toml::string(std::string(s), kind)); + } + basic_value(const char* s, std::vector com) + : type_(value_t::string), + region_info_(std::make_shared(region_base{})), + comments_(std::move(com)) + { + assigner(this->string_, toml::string(std::string(s))); + } + basic_value(const char* s, string_t kind, std::vector com) + : type_(value_t::string), + region_info_(std::make_shared(region_base{})), + comments_(std::move(com)) + { + assigner(this->string_, toml::string(std::string(s), kind)); + } + +#if defined(TOML11_USING_STRING_VIEW) && TOML11_USING_STRING_VIEW>0 + basic_value(std::string_view s) + : type_(value_t::string), + region_info_(std::make_shared(region_base{})) + { + assigner(this->string_, toml::string(s)); + } + basic_value& operator=(std::string_view s) + { + this->cleanup(); + this->type_ = value_t::string ; + this->region_info_ = std::make_shared(region_base{}); + assigner(this->string_, toml::string(s)); + return *this; + } + basic_value(std::string_view s, std::vector com) + : type_(value_t::string), + region_info_(std::make_shared(region_base{})), + comments_(std::move(com)) + { + assigner(this->string_, toml::string(s)); + } + basic_value(std::string_view s, string_t kind) + : type_(value_t::string), + region_info_(std::make_shared(region_base{})) + { + assigner(this->string_, toml::string(s, kind)); + } + basic_value(std::string_view s, string_t kind, std::vector com) + : type_(value_t::string), + region_info_(std::make_shared(region_base{})), + comments_(std::move(com)) + { + assigner(this->string_, toml::string(s, kind)); + } +#endif + + // local date =========================================================== + + basic_value(const local_date& ld) + : type_(value_t::local_date), + region_info_(std::make_shared(region_base{})) + { + assigner(this->local_date_, ld); + } + basic_value& operator=(const local_date& ld) + { + this->cleanup(); + this->type_ = value_t::local_date; + this->region_info_ = std::make_shared(region_base{}); + assigner(this->local_date_, ld); + return *this; + } + basic_value(const local_date& ld, std::vector com) + : type_(value_t::local_date), + region_info_(std::make_shared(region_base{})), + comments_(std::move(com)) + { + assigner(this->local_date_, ld); + } + + // local time =========================================================== + + basic_value(const local_time& lt) + : type_(value_t::local_time), + region_info_(std::make_shared(region_base{})) + { + assigner(this->local_time_, lt); + } + basic_value(const local_time& lt, std::vector com) + : type_(value_t::local_time), + region_info_(std::make_shared(region_base{})), + comments_(std::move(com)) + { + assigner(this->local_time_, lt); + } + basic_value& operator=(const local_time& lt) + { + this->cleanup(); + this->type_ = value_t::local_time; + this->region_info_ = std::make_shared(region_base{}); + assigner(this->local_time_, lt); + return *this; + } + + template + basic_value(const std::chrono::duration& dur) + : type_(value_t::local_time), + region_info_(std::make_shared(region_base{})) + { + assigner(this->local_time_, local_time(dur)); + } + template + basic_value(const std::chrono::duration& dur, + std::vector com) + : type_(value_t::local_time), + region_info_(std::make_shared(region_base{})), + comments_(std::move(com)) + { + assigner(this->local_time_, local_time(dur)); + } + template + basic_value& operator=(const std::chrono::duration& dur) + { + this->cleanup(); + this->type_ = value_t::local_time; + this->region_info_ = std::make_shared(region_base{}); + assigner(this->local_time_, local_time(dur)); + return *this; + } + + // local datetime ======================================================= + + basic_value(const local_datetime& ldt) + : type_(value_t::local_datetime), + region_info_(std::make_shared(region_base{})) + { + assigner(this->local_datetime_, ldt); + } + basic_value(const local_datetime& ldt, std::vector com) + : type_(value_t::local_datetime), + region_info_(std::make_shared(region_base{})), + comments_(std::move(com)) + { + assigner(this->local_datetime_, ldt); + } + basic_value& operator=(const local_datetime& ldt) + { + this->cleanup(); + this->type_ = value_t::local_datetime; + this->region_info_ = std::make_shared(region_base{}); + assigner(this->local_datetime_, ldt); + return *this; + } + + // offset datetime ====================================================== + + basic_value(const offset_datetime& odt) + : type_(value_t::offset_datetime), + region_info_(std::make_shared(region_base{})) + { + assigner(this->offset_datetime_, odt); + } + basic_value(const offset_datetime& odt, std::vector com) + : type_(value_t::offset_datetime), + region_info_(std::make_shared(region_base{})), + comments_(std::move(com)) + { + assigner(this->offset_datetime_, odt); + } + basic_value& operator=(const offset_datetime& odt) + { + this->cleanup(); + this->type_ = value_t::offset_datetime; + this->region_info_ = std::make_shared(region_base{}); + assigner(this->offset_datetime_, odt); + return *this; + } + basic_value(const std::chrono::system_clock::time_point& tp) + : type_(value_t::offset_datetime), + region_info_(std::make_shared(region_base{})) + { + assigner(this->offset_datetime_, offset_datetime(tp)); + } + basic_value(const std::chrono::system_clock::time_point& tp, + std::vector com) + : type_(value_t::offset_datetime), + region_info_(std::make_shared(region_base{})), + comments_(std::move(com)) + { + assigner(this->offset_datetime_, offset_datetime(tp)); + } + basic_value& operator=(const std::chrono::system_clock::time_point& tp) + { + this->cleanup(); + this->type_ = value_t::offset_datetime; + this->region_info_ = std::make_shared(region_base{}); + assigner(this->offset_datetime_, offset_datetime(tp)); + return *this; + } + + // array ================================================================ + + basic_value(const array_type& ary) + : type_(value_t::array), + region_info_(std::make_shared(region_base{})) + { + assigner(this->array_, ary); + } + basic_value(const array_type& ary, std::vector com) + : type_(value_t::array), + region_info_(std::make_shared(region_base{})), + comments_(std::move(com)) + { + assigner(this->array_, ary); + } + basic_value& operator=(const array_type& ary) + { + this->cleanup(); + this->type_ = value_t::array ; + this->region_info_ = std::make_shared(region_base{}); + assigner(this->array_, ary); + return *this; + } + + // array (initializer_list) ---------------------------------------------- + + template::value, + std::nullptr_t>::type = nullptr> + basic_value(std::initializer_list list) + : type_(value_t::array), + region_info_(std::make_shared(region_base{})) + { + array_type ary(list.begin(), list.end()); + assigner(this->array_, std::move(ary)); + } + template::value, + std::nullptr_t>::type = nullptr> + basic_value(std::initializer_list list, std::vector com) + : type_(value_t::array), + region_info_(std::make_shared(region_base{})), + comments_(std::move(com)) + { + array_type ary(list.begin(), list.end()); + assigner(this->array_, std::move(ary)); + } + template::value, + std::nullptr_t>::type = nullptr> + basic_value& operator=(std::initializer_list list) + { + this->cleanup(); + this->type_ = value_t::array; + this->region_info_ = std::make_shared(region_base{}); + + array_type ary(list.begin(), list.end()); + assigner(this->array_, std::move(ary)); + return *this; + } + + // array (STL Containers) ------------------------------------------------ + + template>, + detail::is_container + >::value, std::nullptr_t>::type = nullptr> + basic_value(const T& list) + : type_(value_t::array), + region_info_(std::make_shared(region_base{})) + { + static_assert(std::is_convertible::value, + "elements of a container should be convertible to toml::value"); + + array_type ary(list.size()); + std::copy(list.begin(), list.end(), ary.begin()); + assigner(this->array_, std::move(ary)); + } + template>, + detail::is_container + >::value, std::nullptr_t>::type = nullptr> + basic_value(const T& list, std::vector com) + : type_(value_t::array), + region_info_(std::make_shared(region_base{})), + comments_(std::move(com)) + { + static_assert(std::is_convertible::value, + "elements of a container should be convertible to toml::value"); + + array_type ary(list.size()); + std::copy(list.begin(), list.end(), ary.begin()); + assigner(this->array_, std::move(ary)); + } + template>, + detail::is_container + >::value, std::nullptr_t>::type = nullptr> + basic_value& operator=(const T& list) + { + static_assert(std::is_convertible::value, + "elements of a container should be convertible to toml::value"); + + this->cleanup(); + this->type_ = value_t::array; + this->region_info_ = std::make_shared(region_base{}); + + array_type ary(list.size()); + std::copy(list.begin(), list.end(), ary.begin()); + assigner(this->array_, std::move(ary)); + return *this; + } + + // table ================================================================ + + basic_value(const table_type& tab) + : type_(value_t::table), + region_info_(std::make_shared(region_base{})) + { + assigner(this->table_, tab); + } + basic_value(const table_type& tab, std::vector com) + : type_(value_t::table), + region_info_(std::make_shared(region_base{})), + comments_(std::move(com)) + { + assigner(this->table_, tab); + } + basic_value& operator=(const table_type& tab) + { + this->cleanup(); + this->type_ = value_t::table; + this->region_info_ = std::make_shared(region_base{}); + assigner(this->table_, tab); + return *this; + } + + // initializer-list ------------------------------------------------------ + + basic_value(std::initializer_list> list) + : type_(value_t::table), + region_info_(std::make_shared(region_base{})) + { + table_type tab; + for(const auto& elem : list) {tab[elem.first] = elem.second;} + assigner(this->table_, std::move(tab)); + } + + basic_value(std::initializer_list> list, + std::vector com) + : type_(value_t::table), + region_info_(std::make_shared(region_base{})), + comments_(std::move(com)) + { + table_type tab; + for(const auto& elem : list) {tab[elem.first] = elem.second;} + assigner(this->table_, std::move(tab)); + } + basic_value& operator=(std::initializer_list> list) + { + this->cleanup(); + this->type_ = value_t::table; + this->region_info_ = std::make_shared(region_base{}); + + table_type tab; + for(const auto& elem : list) {tab[elem.first] = elem.second;} + assigner(this->table_, std::move(tab)); + return *this; + } + + // other table-like ----------------------------------------------------- + + template>, + detail::is_map + >::value, std::nullptr_t>::type = nullptr> + basic_value(const Map& mp) + : type_(value_t::table), + region_info_(std::make_shared(region_base{})) + { + table_type tab; + for(const auto& elem : mp) {tab[elem.first] = elem.second;} + assigner(this->table_, std::move(tab)); + } + template>, + detail::is_map + >::value, std::nullptr_t>::type = nullptr> + basic_value(const Map& mp, std::vector com) + : type_(value_t::table), + region_info_(std::make_shared(region_base{})), + comments_(std::move(com)) + { + table_type tab; + for(const auto& elem : mp) {tab[elem.first] = elem.second;} + assigner(this->table_, std::move(tab)); + } + template>, + detail::is_map + >::value, std::nullptr_t>::type = nullptr> + basic_value& operator=(const Map& mp) + { + this->cleanup(); + this->type_ = value_t::table; + this->region_info_ = std::make_shared(region_base{}); + + table_type tab; + for(const auto& elem : mp) {tab[elem.first] = elem.second;} + assigner(this->table_, std::move(tab)); + return *this; + } + + // user-defined ========================================================= + + // convert using into_toml() method ------------------------------------- + + template::value, std::nullptr_t>::type = nullptr> + basic_value(const T& ud): basic_value(ud.into_toml()) {} + + template::value, std::nullptr_t>::type = nullptr> + basic_value(const T& ud, std::vector com) + : basic_value(ud.into_toml(), std::move(com)) + {} + template::value, std::nullptr_t>::type = nullptr> + basic_value& operator=(const T& ud) + { + *this = ud.into_toml(); + return *this; + } + + // convert using into struct ----------------------------------------- + + template)> + basic_value(const T& ud): basic_value(::toml::into::into_toml(ud)) {} + template)> + basic_value(const T& ud, std::vector com) + : basic_value(::toml::into::into_toml(ud), std::move(com)) + {} + template)> + basic_value& operator=(const T& ud) + { + *this = ::toml::into::into_toml(ud); + return *this; + } + + // for internal use ------------------------------------------------------ + // + // Those constructors take detail::region that contains parse result. + + basic_value(boolean b, detail::region reg, std::vector cm) + : type_(value_t::boolean), + region_info_(std::make_shared(std::move(reg))), + comments_(std::move(cm)) + { + assigner(this->boolean_, b); + } + template, detail::negation> + >::value, std::nullptr_t>::type = nullptr> + basic_value(T i, detail::region reg, std::vector cm) + : type_(value_t::integer), + region_info_(std::make_shared(std::move(reg))), + comments_(std::move(cm)) + { + assigner(this->integer_, static_cast(i)); + } + template::value, std::nullptr_t>::type = nullptr> + basic_value(T f, detail::region reg, std::vector cm) + : type_(value_t::floating), + region_info_(std::make_shared(std::move(reg))), + comments_(std::move(cm)) + { + assigner(this->floating_, static_cast(f)); + } + basic_value(toml::string s, detail::region reg, + std::vector cm) + : type_(value_t::string), + region_info_(std::make_shared(std::move(reg))), + comments_(std::move(cm)) + { + assigner(this->string_, std::move(s)); + } + basic_value(const local_date& ld, detail::region reg, + std::vector cm) + : type_(value_t::local_date), + region_info_(std::make_shared(std::move(reg))), + comments_(std::move(cm)) + { + assigner(this->local_date_, ld); + } + basic_value(const local_time& lt, detail::region reg, + std::vector cm) + : type_(value_t::local_time), + region_info_(std::make_shared(std::move(reg))), + comments_(std::move(cm)) + { + assigner(this->local_time_, lt); + } + basic_value(const local_datetime& ldt, detail::region reg, + std::vector cm) + : type_(value_t::local_datetime), + region_info_(std::make_shared(std::move(reg))), + comments_(std::move(cm)) + { + assigner(this->local_datetime_, ldt); + } + basic_value(const offset_datetime& odt, detail::region reg, + std::vector cm) + : type_(value_t::offset_datetime), + region_info_(std::make_shared(std::move(reg))), + comments_(std::move(cm)) + { + assigner(this->offset_datetime_, odt); + } + basic_value(const array_type& ary, detail::region reg, + std::vector cm) + : type_(value_t::array), + region_info_(std::make_shared(std::move(reg))), + comments_(std::move(cm)) + { + assigner(this->array_, ary); + } + basic_value(const table_type& tab, detail::region reg, + std::vector cm) + : type_(value_t::table), + region_info_(std::make_shared(std::move(reg))), + comments_(std::move(cm)) + { + assigner(this->table_, tab); + } + + template::value, + std::nullptr_t>::type = nullptr> + basic_value(std::pair parse_result, std::vector com) + : basic_value(std::move(parse_result.first), + std::move(parse_result.second), + std::move(com)) + {} + + // type checking and casting ============================================ + + template::value, + std::nullptr_t>::type = nullptr> + bool is() const noexcept + { + return detail::type_to_enum::value == this->type_; + } + bool is(value_t t) const noexcept {return t == this->type_;} + + bool is_uninitialized() const noexcept {return this->is(value_t::empty );} + bool is_boolean() const noexcept {return this->is(value_t::boolean );} + bool is_integer() const noexcept {return this->is(value_t::integer );} + bool is_floating() const noexcept {return this->is(value_t::floating );} + bool is_string() const noexcept {return this->is(value_t::string );} + bool is_offset_datetime() const noexcept {return this->is(value_t::offset_datetime);} + bool is_local_datetime() const noexcept {return this->is(value_t::local_datetime );} + bool is_local_date() const noexcept {return this->is(value_t::local_date );} + bool is_local_time() const noexcept {return this->is(value_t::local_time );} + bool is_array() const noexcept {return this->is(value_t::array );} + bool is_table() const noexcept {return this->is(value_t::table );} + + value_t type() const noexcept {return type_;} + + template + typename detail::enum_to_type::type& cast() & + { + if(this->type_ != T) + { + detail::throw_bad_cast("toml::value::cast: ", this->type_, *this); + } + return detail::switch_cast::invoke(*this); + } + template + typename detail::enum_to_type::type const& cast() const& + { + if(this->type_ != T) + { + detail::throw_bad_cast("toml::value::cast: ", this->type_, *this); + } + return detail::switch_cast::invoke(*this); + } + template + typename detail::enum_to_type::type&& cast() && + { + if(this->type_ != T) + { + detail::throw_bad_cast("toml::value::cast: ", this->type_, *this); + } + return detail::switch_cast::invoke(std::move(*this)); + } + + // ------------------------------------------------------------------------ + // nothrow version + + boolean const& as_boolean (const std::nothrow_t&) const& noexcept {return this->boolean_;} + integer const& as_integer (const std::nothrow_t&) const& noexcept {return this->integer_;} + floating const& as_floating (const std::nothrow_t&) const& noexcept {return this->floating_;} + string const& as_string (const std::nothrow_t&) const& noexcept {return this->string_;} + offset_datetime const& as_offset_datetime(const std::nothrow_t&) const& noexcept {return this->offset_datetime_;} + local_datetime const& as_local_datetime (const std::nothrow_t&) const& noexcept {return this->local_datetime_;} + local_date const& as_local_date (const std::nothrow_t&) const& noexcept {return this->local_date_;} + local_time const& as_local_time (const std::nothrow_t&) const& noexcept {return this->local_time_;} + array_type const& as_array (const std::nothrow_t&) const& noexcept {return this->array_.value();} + table_type const& as_table (const std::nothrow_t&) const& noexcept {return this->table_.value();} + + boolean & as_boolean (const std::nothrow_t&) & noexcept {return this->boolean_;} + integer & as_integer (const std::nothrow_t&) & noexcept {return this->integer_;} + floating & as_floating (const std::nothrow_t&) & noexcept {return this->floating_;} + string & as_string (const std::nothrow_t&) & noexcept {return this->string_;} + offset_datetime& as_offset_datetime(const std::nothrow_t&) & noexcept {return this->offset_datetime_;} + local_datetime & as_local_datetime (const std::nothrow_t&) & noexcept {return this->local_datetime_;} + local_date & as_local_date (const std::nothrow_t&) & noexcept {return this->local_date_;} + local_time & as_local_time (const std::nothrow_t&) & noexcept {return this->local_time_;} + array_type & as_array (const std::nothrow_t&) & noexcept {return this->array_.value();} + table_type & as_table (const std::nothrow_t&) & noexcept {return this->table_.value();} + + boolean && as_boolean (const std::nothrow_t&) && noexcept {return std::move(this->boolean_);} + integer && as_integer (const std::nothrow_t&) && noexcept {return std::move(this->integer_);} + floating && as_floating (const std::nothrow_t&) && noexcept {return std::move(this->floating_);} + string && as_string (const std::nothrow_t&) && noexcept {return std::move(this->string_);} + offset_datetime&& as_offset_datetime(const std::nothrow_t&) && noexcept {return std::move(this->offset_datetime_);} + local_datetime && as_local_datetime (const std::nothrow_t&) && noexcept {return std::move(this->local_datetime_);} + local_date && as_local_date (const std::nothrow_t&) && noexcept {return std::move(this->local_date_);} + local_time && as_local_time (const std::nothrow_t&) && noexcept {return std::move(this->local_time_);} + array_type && as_array (const std::nothrow_t&) && noexcept {return std::move(this->array_.value());} + table_type && as_table (const std::nothrow_t&) && noexcept {return std::move(this->table_.value());} + + // ======================================================================== + // throw version + // ------------------------------------------------------------------------ + // const reference {{{ + + boolean const& as_boolean() const& + { + if(this->type_ != value_t::boolean) + { + detail::throw_bad_cast( + "toml::value::as_boolean(): ", this->type_, *this); + } + return this->boolean_; + } + integer const& as_integer() const& + { + if(this->type_ != value_t::integer) + { + detail::throw_bad_cast( + "toml::value::as_integer(): ", this->type_, *this); + } + return this->integer_; + } + floating const& as_floating() const& + { + if(this->type_ != value_t::floating) + { + detail::throw_bad_cast( + "toml::value::as_floating(): ", this->type_, *this); + } + return this->floating_; + } + string const& as_string() const& + { + if(this->type_ != value_t::string) + { + detail::throw_bad_cast( + "toml::value::as_string(): ", this->type_, *this); + } + return this->string_; + } + offset_datetime const& as_offset_datetime() const& + { + if(this->type_ != value_t::offset_datetime) + { + detail::throw_bad_cast( + "toml::value::as_offset_datetime(): ", this->type_, *this); + } + return this->offset_datetime_; + } + local_datetime const& as_local_datetime() const& + { + if(this->type_ != value_t::local_datetime) + { + detail::throw_bad_cast( + "toml::value::as_local_datetime(): ", this->type_, *this); + } + return this->local_datetime_; + } + local_date const& as_local_date() const& + { + if(this->type_ != value_t::local_date) + { + detail::throw_bad_cast( + "toml::value::as_local_date(): ", this->type_, *this); + } + return this->local_date_; + } + local_time const& as_local_time() const& + { + if(this->type_ != value_t::local_time) + { + detail::throw_bad_cast( + "toml::value::as_local_time(): ", this->type_, *this); + } + return this->local_time_; + } + array_type const& as_array() const& + { + if(this->type_ != value_t::array) + { + detail::throw_bad_cast( + "toml::value::as_array(): ", this->type_, *this); + } + return this->array_.value(); + } + table_type const& as_table() const& + { + if(this->type_ != value_t::table) + { + detail::throw_bad_cast( + "toml::value::as_table(): ", this->type_, *this); + } + return this->table_.value(); + } + // }}} + // ------------------------------------------------------------------------ + // nonconst reference {{{ + + boolean & as_boolean() & + { + if(this->type_ != value_t::boolean) + { + detail::throw_bad_cast( + "toml::value::as_boolean(): ", this->type_, *this); + } + return this->boolean_; + } + integer & as_integer() & + { + if(this->type_ != value_t::integer) + { + detail::throw_bad_cast( + "toml::value::as_integer(): ", this->type_, *this); + } + return this->integer_; + } + floating & as_floating() & + { + if(this->type_ != value_t::floating) + { + detail::throw_bad_cast( + "toml::value::as_floating(): ", this->type_, *this); + } + return this->floating_; + } + string & as_string() & + { + if(this->type_ != value_t::string) + { + detail::throw_bad_cast( + "toml::value::as_string(): ", this->type_, *this); + } + return this->string_; + } + offset_datetime & as_offset_datetime() & + { + if(this->type_ != value_t::offset_datetime) + { + detail::throw_bad_cast( + "toml::value::as_offset_datetime(): ", this->type_, *this); + } + return this->offset_datetime_; + } + local_datetime & as_local_datetime() & + { + if(this->type_ != value_t::local_datetime) + { + detail::throw_bad_cast( + "toml::value::as_local_datetime(): ", this->type_, *this); + } + return this->local_datetime_; + } + local_date & as_local_date() & + { + if(this->type_ != value_t::local_date) + { + detail::throw_bad_cast( + "toml::value::as_local_date(): ", this->type_, *this); + } + return this->local_date_; + } + local_time & as_local_time() & + { + if(this->type_ != value_t::local_time) + { + detail::throw_bad_cast( + "toml::value::as_local_time(): ", this->type_, *this); + } + return this->local_time_; + } + array_type & as_array() & + { + if(this->type_ != value_t::array) + { + detail::throw_bad_cast( + "toml::value::as_array(): ", this->type_, *this); + } + return this->array_.value(); + } + table_type & as_table() & + { + if(this->type_ != value_t::table) + { + detail::throw_bad_cast( + "toml::value::as_table(): ", this->type_, *this); + } + return this->table_.value(); + } + + // }}} + // ------------------------------------------------------------------------ + // rvalue reference {{{ + + boolean && as_boolean() && + { + if(this->type_ != value_t::boolean) + { + detail::throw_bad_cast( + "toml::value::as_boolean(): ", this->type_, *this); + } + return std::move(this->boolean_); + } + integer && as_integer() && + { + if(this->type_ != value_t::integer) + { + detail::throw_bad_cast( + "toml::value::as_integer(): ", this->type_, *this); + } + return std::move(this->integer_); + } + floating && as_floating() && + { + if(this->type_ != value_t::floating) + { + detail::throw_bad_cast( + "toml::value::as_floating(): ", this->type_, *this); + } + return std::move(this->floating_); + } + string && as_string() && + { + if(this->type_ != value_t::string) + { + detail::throw_bad_cast( + "toml::value::as_string(): ", this->type_, *this); + } + return std::move(this->string_); + } + offset_datetime && as_offset_datetime() && + { + if(this->type_ != value_t::offset_datetime) + { + detail::throw_bad_cast( + "toml::value::as_offset_datetime(): ", this->type_, *this); + } + return std::move(this->offset_datetime_); + } + local_datetime && as_local_datetime() && + { + if(this->type_ != value_t::local_datetime) + { + detail::throw_bad_cast( + "toml::value::as_local_datetime(): ", this->type_, *this); + } + return std::move(this->local_datetime_); + } + local_date && as_local_date() && + { + if(this->type_ != value_t::local_date) + { + detail::throw_bad_cast( + "toml::value::as_local_date(): ", this->type_, *this); + } + return std::move(this->local_date_); + } + local_time && as_local_time() && + { + if(this->type_ != value_t::local_time) + { + detail::throw_bad_cast( + "toml::value::as_local_time(): ", this->type_, *this); + } + return std::move(this->local_time_); + } + array_type && as_array() && + { + if(this->type_ != value_t::array) + { + detail::throw_bad_cast( + "toml::value::as_array(): ", this->type_, *this); + } + return std::move(this->array_.value()); + } + table_type && as_table() && + { + if(this->type_ != value_t::table) + { + detail::throw_bad_cast( + "toml::value::as_table(): ", this->type_, *this); + } + return std::move(this->table_.value()); + } + // }}} + + // accessors ============================================================= + // + // may throw type_error or out_of_range + // + value_type& at(const key& k) + { + if(!this->is_table()) + { + detail::throw_bad_cast( + "toml::value::at(key): ", this->type_, *this); + } + if(this->as_table(std::nothrow).count(k) == 0) + { + detail::throw_key_not_found_error(*this, k); + } + return this->as_table(std::nothrow).at(k); + } + value_type const& at(const key& k) const + { + if(!this->is_table()) + { + detail::throw_bad_cast( + "toml::value::at(key): ", this->type_, *this); + } + if(this->as_table(std::nothrow).count(k) == 0) + { + detail::throw_key_not_found_error(*this, k); + } + return this->as_table(std::nothrow).at(k); + } + value_type& operator[](const key& k) + { + if(this->is_uninitialized()) + { + *this = table_type{}; + } + else if(!this->is_table()) // initialized, but not a table + { + detail::throw_bad_cast( + "toml::value::operator[](key): ", this->type_, *this); + } + return this->as_table(std::nothrow)[k]; + } + + value_type& at(const std::size_t idx) + { + if(!this->is_array()) + { + detail::throw_bad_cast( + "toml::value::at(idx): ", this->type_, *this); + } + if(this->as_array(std::nothrow).size() <= idx) + { + throw std::out_of_range(detail::format_underline( + "toml::value::at(idx): no element corresponding to the index", { + {this->location(), concat_to_string("the length is ", + this->as_array(std::nothrow).size(), + ", and the specified index is ", idx)} + })); + } + return this->as_array().at(idx); + } + value_type const& at(const std::size_t idx) const + { + if(!this->is_array()) + { + detail::throw_bad_cast( + "toml::value::at(idx): ", this->type_, *this); + } + if(this->as_array(std::nothrow).size() <= idx) + { + throw std::out_of_range(detail::format_underline( + "toml::value::at(idx): no element corresponding to the index", { + {this->location(), concat_to_string("the length is ", + this->as_array(std::nothrow).size(), + ", and the specified index is ", idx)} + })); + } + return this->as_array(std::nothrow).at(idx); + } + + value_type& operator[](const std::size_t idx) noexcept + { + // no check... + return this->as_array(std::nothrow)[idx]; + } + value_type const& operator[](const std::size_t idx) const noexcept + { + // no check... + return this->as_array(std::nothrow)[idx]; + } + + void push_back(const value_type& x) + { + if(!this->is_array()) + { + detail::throw_bad_cast( + "toml::value::push_back(value): ", this->type_, *this); + } + this->as_array(std::nothrow).push_back(x); + return; + } + void push_back(value_type&& x) + { + if(!this->is_array()) + { + detail::throw_bad_cast( + "toml::value::push_back(value): ", this->type_, *this); + } + this->as_array(std::nothrow).push_back(std::move(x)); + return; + } + + template + value_type& emplace_back(Ts&& ... args) + { + if(!this->is_array()) + { + detail::throw_bad_cast( + "toml::value::emplace_back(...): ", this->type_, *this); + } + this->as_array(std::nothrow).emplace_back(std::forward(args) ...); + return this->as_array(std::nothrow).back(); + } + + std::size_t size() const + { + switch(this->type_) + { + case value_t::array: + { + return this->as_array(std::nothrow).size(); + } + case value_t::table: + { + return this->as_table(std::nothrow).size(); + } + case value_t::string: + { + return this->as_string(std::nothrow).str.size(); + } + default: + { + throw type_error(detail::format_underline( + "toml::value::size(): bad_cast to container types", { + {this->location(), + concat_to_string("the actual type is ", this->type_)} + }), this->location()); + } + } + } + + std::size_t count(const key_type& k) const + { + if(!this->is_table()) + { + detail::throw_bad_cast( + "toml::value::count(key): ", this->type_, *this); + } + return this->as_table(std::nothrow).count(k); + } + + bool contains(const key_type& k) const + { + if(!this->is_table()) + { + detail::throw_bad_cast( + "toml::value::contains(key): ", this->type_, *this); + } + return (this->as_table(std::nothrow).count(k) != 0); + } + + source_location location() const + { + return source_location(this->region_info_.get()); + } + + comment_type const& comments() const noexcept {return this->comments_;} + comment_type& comments() noexcept {return this->comments_;} + + private: + + void cleanup() noexcept + { + switch(this->type_) + { + case value_t::string : {string_.~string(); return;} + case value_t::array : {array_.~array_storage(); return;} + case value_t::table : {table_.~table_storage(); return;} + default : return; + } + } + + // for error messages + template + friend region_base const* detail::get_region(const Value& v); + + template + friend void detail::change_region(Value& v, detail::region reg); + + private: + + using array_storage = detail::storage; + using table_storage = detail::storage; + + value_t type_; + union + { + boolean boolean_; + integer integer_; + floating floating_; + string string_; + offset_datetime offset_datetime_; + local_datetime local_datetime_; + local_date local_date_; + local_time local_time_; + array_storage array_; + table_storage table_; + }; + std::shared_ptr region_info_; + comment_type comments_; +}; + +// default toml::value and default array/table. +// TOML11_DEFAULT_COMMENT_STRATEGY is defined in comments.hpp +using value = basic_value; +using array = typename value::array_type; +using table = typename value::table_type; + +template class T, template class A> +inline bool +operator==(const basic_value& lhs, const basic_value& rhs) +{ + if(lhs.type() != rhs.type()) {return false;} + if(lhs.comments() != rhs.comments()) {return false;} + + switch(lhs.type()) + { + case value_t::boolean : + { + return lhs.as_boolean() == rhs.as_boolean(); + } + case value_t::integer : + { + return lhs.as_integer() == rhs.as_integer(); + } + case value_t::floating : + { + return lhs.as_floating() == rhs.as_floating(); + } + case value_t::string : + { + return lhs.as_string() == rhs.as_string(); + } + case value_t::offset_datetime: + { + return lhs.as_offset_datetime() == rhs.as_offset_datetime(); + } + case value_t::local_datetime: + { + return lhs.as_local_datetime() == rhs.as_local_datetime(); + } + case value_t::local_date: + { + return lhs.as_local_date() == rhs.as_local_date(); + } + case value_t::local_time: + { + return lhs.as_local_time() == rhs.as_local_time(); + } + case value_t::array : + { + return lhs.as_array() == rhs.as_array(); + } + case value_t::table : + { + return lhs.as_table() == rhs.as_table(); + } + case value_t::empty : {return true; } + default: {return false;} + } +} + +template class T, template class A> +inline bool operator!=(const basic_value& lhs, const basic_value& rhs) +{ + return !(lhs == rhs); +} + +template class T, template class A> +typename std::enable_if::array_type>, + detail::is_comparable::table_type> + >::value, bool>::type +operator<(const basic_value& lhs, const basic_value& rhs) +{ + if(lhs.type() != rhs.type()){return (lhs.type() < rhs.type());} + switch(lhs.type()) + { + case value_t::boolean : + { + return lhs.as_boolean() < rhs.as_boolean() || + (lhs.as_boolean() == rhs.as_boolean() && + lhs.comments() < rhs.comments()); + } + case value_t::integer : + { + return lhs.as_integer() < rhs.as_integer() || + (lhs.as_integer() == rhs.as_integer() && + lhs.comments() < rhs.comments()); + } + case value_t::floating : + { + return lhs.as_floating() < rhs.as_floating() || + (lhs.as_floating() == rhs.as_floating() && + lhs.comments() < rhs.comments()); + } + case value_t::string : + { + return lhs.as_string() < rhs.as_string() || + (lhs.as_string() == rhs.as_string() && + lhs.comments() < rhs.comments()); + } + case value_t::offset_datetime: + { + return lhs.as_offset_datetime() < rhs.as_offset_datetime() || + (lhs.as_offset_datetime() == rhs.as_offset_datetime() && + lhs.comments() < rhs.comments()); + } + case value_t::local_datetime: + { + return lhs.as_local_datetime() < rhs.as_local_datetime() || + (lhs.as_local_datetime() == rhs.as_local_datetime() && + lhs.comments() < rhs.comments()); + } + case value_t::local_date: + { + return lhs.as_local_date() < rhs.as_local_date() || + (lhs.as_local_date() == rhs.as_local_date() && + lhs.comments() < rhs.comments()); + } + case value_t::local_time: + { + return lhs.as_local_time() < rhs.as_local_time() || + (lhs.as_local_time() == rhs.as_local_time() && + lhs.comments() < rhs.comments()); + } + case value_t::array : + { + return lhs.as_array() < rhs.as_array() || + (lhs.as_array() == rhs.as_array() && + lhs.comments() < rhs.comments()); + } + case value_t::table : + { + return lhs.as_table() < rhs.as_table() || + (lhs.as_table() == rhs.as_table() && + lhs.comments() < rhs.comments()); + } + case value_t::empty : + { + return lhs.comments() < rhs.comments(); + } + default: + { + return lhs.comments() < rhs.comments(); + } + } +} + +template class T, template class A> +typename std::enable_if::array_type>, + detail::is_comparable::table_type> + >::value, bool>::type +operator<=(const basic_value& lhs, const basic_value& rhs) +{ + return (lhs < rhs) || (lhs == rhs); +} +template class T, template class A> +typename std::enable_if::array_type>, + detail::is_comparable::table_type> + >::value, bool>::type +operator>(const basic_value& lhs, const basic_value& rhs) +{ + return !(lhs <= rhs); +} +template class T, template class A> +typename std::enable_if::array_type>, + detail::is_comparable::table_type> + >::value, bool>::type +operator>=(const basic_value& lhs, const basic_value& rhs) +{ + return !(lhs < rhs); +} + +template class T, template class A> +inline std::string format_error(const std::string& err_msg, + const basic_value& v, const std::string& comment, + std::vector hints = {}, + const bool colorize = TOML11_ERROR_MESSAGE_COLORIZED) +{ + return detail::format_underline(err_msg, {{v.location(), comment}}, + std::move(hints), colorize); +} + +template class T, template class A> +inline std::string format_error(const std::string& err_msg, + const toml::basic_value& v1, const std::string& comment1, + const toml::basic_value& v2, const std::string& comment2, + std::vector hints = {}, + const bool colorize = TOML11_ERROR_MESSAGE_COLORIZED) +{ + return detail::format_underline(err_msg, { + {v1.location(), comment1}, {v2.location(), comment2} + }, std::move(hints), colorize); +} + +template class T, template class A> +inline std::string format_error(const std::string& err_msg, + const toml::basic_value& v1, const std::string& comment1, + const toml::basic_value& v2, const std::string& comment2, + const toml::basic_value& v3, const std::string& comment3, + std::vector hints = {}, + const bool colorize = TOML11_ERROR_MESSAGE_COLORIZED) +{ + return detail::format_underline(err_msg, {{v1.location(), comment1}, + {v2.location(), comment2}, {v3.location(), comment3} + }, std::move(hints), colorize); +} + +template class T, template class A> +detail::return_type_of_t +visit(Visitor&& visitor, const toml::basic_value& v) +{ + switch(v.type()) + { + case value_t::boolean : {return visitor(v.as_boolean ());} + case value_t::integer : {return visitor(v.as_integer ());} + case value_t::floating : {return visitor(v.as_floating ());} + case value_t::string : {return visitor(v.as_string ());} + case value_t::offset_datetime: {return visitor(v.as_offset_datetime());} + case value_t::local_datetime : {return visitor(v.as_local_datetime ());} + case value_t::local_date : {return visitor(v.as_local_date ());} + case value_t::local_time : {return visitor(v.as_local_time ());} + case value_t::array : {return visitor(v.as_array ());} + case value_t::table : {return visitor(v.as_table ());} + case value_t::empty : break; + default: break; + } + throw std::runtime_error(format_error("[error] toml::visit: toml::basic_value " + "does not have any valid basic_value.", v, "here")); +} + +template class T, template class A> +detail::return_type_of_t +visit(Visitor&& visitor, toml::basic_value& v) +{ + switch(v.type()) + { + case value_t::boolean : {return visitor(v.as_boolean ());} + case value_t::integer : {return visitor(v.as_integer ());} + case value_t::floating : {return visitor(v.as_floating ());} + case value_t::string : {return visitor(v.as_string ());} + case value_t::offset_datetime: {return visitor(v.as_offset_datetime());} + case value_t::local_datetime : {return visitor(v.as_local_datetime ());} + case value_t::local_date : {return visitor(v.as_local_date ());} + case value_t::local_time : {return visitor(v.as_local_time ());} + case value_t::array : {return visitor(v.as_array ());} + case value_t::table : {return visitor(v.as_table ());} + case value_t::empty : break; + default: break; + } + throw std::runtime_error(format_error("[error] toml::visit: toml::basic_value " + "does not have any valid basic_value.", v, "here")); +} + +template class T, template class A> +detail::return_type_of_t +visit(Visitor&& visitor, toml::basic_value&& v) +{ + switch(v.type()) + { + case value_t::boolean : {return visitor(std::move(v.as_boolean ()));} + case value_t::integer : {return visitor(std::move(v.as_integer ()));} + case value_t::floating : {return visitor(std::move(v.as_floating ()));} + case value_t::string : {return visitor(std::move(v.as_string ()));} + case value_t::offset_datetime: {return visitor(std::move(v.as_offset_datetime()));} + case value_t::local_datetime : {return visitor(std::move(v.as_local_datetime ()));} + case value_t::local_date : {return visitor(std::move(v.as_local_date ()));} + case value_t::local_time : {return visitor(std::move(v.as_local_time ()));} + case value_t::array : {return visitor(std::move(v.as_array ()));} + case value_t::table : {return visitor(std::move(v.as_table ()));} + case value_t::empty : break; + default: break; + } + throw std::runtime_error(format_error("[error] toml::visit: toml::basic_value " + "does not have any valid basic_value.", v, "here")); +} + +}// toml +#endif// TOML11_VALUE diff --git a/src/frontend/qt_sdl/toml/toml/version.hpp b/src/frontend/qt_sdl/toml/toml/version.hpp new file mode 100644 index 0000000000..9cbfa39be0 --- /dev/null +++ b/src/frontend/qt_sdl/toml/toml/version.hpp @@ -0,0 +1,42 @@ +#ifndef TOML11_VERSION_HPP +#define TOML11_VERSION_HPP + +// This file checks C++ version. + +#ifndef __cplusplus +# error "__cplusplus is not defined" +#endif + +// Since MSVC does not define `__cplusplus` correctly unless you pass +// `/Zc:__cplusplus` when compiling, the workaround macros are added. +// Those enables you to define version manually or to use MSVC specific +// version macro automatically. +// +// The value of `__cplusplus` macro is defined in the C++ standard spec, but +// MSVC ignores the value, maybe because of backward compatibility. Instead, +// MSVC defines _MSVC_LANG that has the same value as __cplusplus defined in +// the C++ standard. First we check the manual version definition, and then +// we check if _MSVC_LANG is defined. If neither, use normal `__cplusplus`. +// +// FYI: https://docs.microsoft.com/en-us/cpp/build/reference/zc-cplusplus?view=msvc-170 +// https://docs.microsoft.com/en-us/cpp/preprocessor/predefined-macros?view=msvc-170 +// +#if defined(TOML11_ENFORCE_CXX11) +# define TOML11_CPLUSPLUS_STANDARD_VERSION 201103L +#elif defined(TOML11_ENFORCE_CXX14) +# define TOML11_CPLUSPLUS_STANDARD_VERSION 201402L +#elif defined(TOML11_ENFORCE_CXX17) +# define TOML11_CPLUSPLUS_STANDARD_VERSION 201703L +#elif defined(TOML11_ENFORCE_CXX20) +# define TOML11_CPLUSPLUS_STANDARD_VERSION 202002L +#elif defined(_MSVC_LANG) && defined(_MSC_VER) && 1910 <= _MSC_VER +# define TOML11_CPLUSPLUS_STANDARD_VERSION _MSVC_LANG +#else +# define TOML11_CPLUSPLUS_STANDARD_VERSION __cplusplus +#endif + +#if TOML11_CPLUSPLUS_STANDARD_VERSION < 201103L && _MSC_VER < 1900 +# error "toml11 requires C++11 or later." +#endif + +#endif// TOML11_VERSION_HPP