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
This commit is contained in:
Arisotura 2024-06-15 13:52:47 +02:00 committed by GitHub
parent 8e9b88d01d
commit 25a7b1ca1d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
111 changed files with 16802 additions and 5042 deletions

View File

@ -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),

View File

@ -130,7 +130,8 @@ public:
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;

View File

@ -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;

View File

@ -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

View File

@ -24,6 +24,7 @@
namespace melonDS
{
template<typename T, u32 NumEntries>
class FIFO
{
@ -191,5 +192,121 @@ private:
u32 ReadPos, WritePos;
};
template<u32 Size>
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

View File

@ -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<u8[]>&& rom, u32 len, std::unique_ptr<u8[]>&& sram, u32 sramlen, GBACart::CartType type) :
CartGame::CartGame(std::unique_ptr<u8[]>&& rom, u32 len, std::unique_ptr<u8[]>&& 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<u8[]>&& rom, u32 len, std::unique_ptr<u8[]>&& sram, u32 sramlen) :
CartGame(std::move(rom), len, std::move(sram), sramlen, CartType::GameSolarSensor)
CartGameSolarSensor::CartGameSolarSensor(std::unique_ptr<u8[]>&& rom, u32 len, std::unique_ptr<u8[]>&& 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<CartCommon>&& cart) noexcept : Cart(std::move(cart))
GBACartSlot::GBACartSlot(melonDS::NDS& nds, std::unique_ptr<CartCommon>&& 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<CartCommon> ParseROM(std::unique_ptr<u8[]>&& romdata, u32 romlen)
std::unique_ptr<CartCommon> ParseROM(std::unique_ptr<u8[]>&& 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<CartCommon> ParseROM(const u8* romdata, u32 romlen, const u8* sramdata, u32 sramlen)
std::unique_ptr<CartCommon> 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<CartCommon> ParseROM(const u8* romdata, u32 romlen)
std::unique_ptr<CartCommon> ParseROM(const u8* romdata, u32 romlen, void* userdata)
{
return ParseROM(romdata, romlen, nullptr, 0);
return ParseROM(romdata, romlen, nullptr, 0, userdata);
}
std::unique_ptr<CartCommon> ParseROM(std::unique_ptr<u8[]>&& romdata, u32 romlen, std::unique_ptr<u8[]>&& sramdata, u32 sramlen)
std::unique_ptr<CartCommon> ParseROM(std::unique_ptr<u8[]>&& romdata, u32 romlen, std::unique_ptr<u8[]>&& sramdata, u32 sramlen, void* userdata)
{
if (romdata == nullptr)
{
@ -773,9 +774,9 @@ std::unique_ptr<CartCommon> ParseROM(std::unique_ptr<u8[]>&& romdata, u32 romlen
std::unique_ptr<CartCommon> cart;
if (solarsensor)
cart = std::make_unique<CartGameSolarSensor>(std::move(cartrom), cartromsize, std::move(sramdata), sramlen);
cart = std::make_unique<CartGameSolarSensor>(std::move(cartrom), cartromsize, std::move(sramdata), sramlen, userdata);
else
cart = std::make_unique<CartGame>(std::move(cartrom), cartromsize, std::move(sramdata), sramlen);
cart = std::make_unique<CartGame>(std::move(cartrom), cartromsize, std::move(sramdata), sramlen, userdata);
cart->Reset();

View File

@ -72,8 +72,8 @@ private:
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<u8[]>&& rom, u32 len, std::unique_ptr<u8[]>&& 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<u8[]>&& rom, u32 len, std::unique_ptr<u8[]>&& sram, u32 sramlen, void* userdata, GBACart::CartType type = GBACart::CartType::Game);
~CartGame() override;
u32 Checksum() const override;
@ -104,6 +104,8 @@ protected:
u8 SRAMRead_SRAM(u32 addr);
void SRAMWrite_SRAM(u32 addr, u8 val);
void* UserData;
std::unique_ptr<u8[]> ROM;
u32 ROMLength;
@ -147,8 +149,8 @@ private:
class CartGameSolarSensor : public CartGame
{
public:
CartGameSolarSensor(const u8* rom, u32 len, const u8* sram, u32 sramlen);
CartGameSolarSensor(std::unique_ptr<u8[]>&& rom, u32 len, std::unique_ptr<u8[]>&& sram, u32 sramlen);
CartGameSolarSensor(const u8* rom, u32 len, const u8* sram, u32 sramlen, void* userdata);
CartGameSolarSensor(std::unique_ptr<u8[]>&& rom, u32 len, std::unique_ptr<u8[]>&& sram, u32 sramlen, void* userdata);
void Reset() override;
@ -197,7 +199,7 @@ enum
class GBACartSlot
{
public:
GBACartSlot(std::unique_ptr<CartCommon>&& cart = nullptr) noexcept;
GBACartSlot(melonDS::NDS& nds, std::unique_ptr<CartCommon>&& cart = nullptr) noexcept;
~GBACartSlot() noexcept = default;
void Reset() noexcept;
void DoSavestate(Savestate* file) noexcept;
@ -258,6 +260,7 @@ public:
/// 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<CartCommon> Cart = nullptr;
u16 OpenBusDecay = 0;
};
@ -270,9 +273,9 @@ private:
/// @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<CartCommon> ParseROM(const u8* romdata, u32 romlen);
std::unique_ptr<CartCommon> ParseROM(std::unique_ptr<u8[]>&& romdata, u32 romlen);
std::unique_ptr<CartCommon> ParseROM(const u8* romdata, u32 romlen, const u8* sramdata, u32 sramlen);
std::unique_ptr<CartCommon> ParseROM(const u8* romdata, u32 romlen, void* userdata = nullptr);
std::unique_ptr<CartCommon> ParseROM(std::unique_ptr<u8[]>&& romdata, u32 romlen, void* userdata = nullptr);
std::unique_ptr<CartCommon> 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<CartCommon> 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<CartCommon> ParseROM(std::unique_ptr<u8[]>&& romdata, u32 romlen, std::unique_ptr<u8[]>&& sramdata, u32 sramlen);
std::unique_ptr<CartCommon> ParseROM(std::unique_ptr<u8[]>&& romdata, u32 romlen, std::unique_ptr<u8[]>&& sramdata, u32 sramlen, void* userdata = nullptr);
}

View File

@ -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();
}

View File

@ -229,6 +229,8 @@ private:
#endif
public: // TODO: Encapsulate the rest of these members
void* UserData;
int ConsoleType;
int CurCPU;
@ -522,7 +524,7 @@ private:
template <bool EnableJIT>
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 @@ public:
// 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) {}
};

View File

@ -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<u8[]>&& rom, u32 len, u32 chipid, bool badDSiDump, ROMListEntry romparams, melonDS::NDSCart::CartType type) :
CartCommon::CartCommon(std::unique_ptr<u8[]>&& 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<u8[]>&& 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<u8[]>&& 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<u8[]>&& rom, u32 len, u32 chipid, bool badDSiDump, ROMListEntry romparams, std::unique_ptr<u8[]>&& sram, u32 sramlen, melonDS::NDSCart::CartType type) :
CartCommon(std::move(rom), len, chipid, badDSiDump, romparams, type)
CartRetail::CartRetail(std::unique_ptr<u8[]>&& rom, u32 len, u32 chipid, bool badDSiDump, ROMListEntry romparams, std::unique_ptr<u8[]>&& 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<u8[]>&& 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<u8[]>&& sram, u32 sramlen, void* userdata) :
CartRetailNAND(CopyToUnique(rom, len), len, chipid, romparams, std::move(sram), sramlen, userdata)
{
}
CartRetailNAND::CartRetailNAND(std::unique_ptr<u8[]>&& rom, u32 len, u32 chipid, ROMListEntry romparams, std::unique_ptr<u8[]>&& sram, u32 sramlen) :
CartRetail(std::move(rom), len, chipid, false, romparams, std::move(sram), sramlen, CartType::RetailNAND)
CartRetailNAND::CartRetailNAND(std::unique_ptr<u8[]>&& rom, u32 len, u32 chipid, ROMListEntry romparams, std::unique_ptr<u8[]>&& 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<u8[]>&& 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<u8[]>&& 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<u8[]>&& 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<u8[]>&& 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<u8[]>&& sram, u32 sramlen, void* userdata) :
CartRetailBT(CopyToUnique(rom, len), len, chipid, romparams, std::move(sram), sramlen, userdata)
{
}
CartRetailBT::CartRetailBT(std::unique_ptr<u8[]>&& rom, u32 len, u32 chipid, ROMListEntry romparams, std::unique_ptr<u8[]>&& sram, u32 sramlen) :
CartRetail(std::move(rom), len, chipid, false, romparams, std::move(sram), sramlen, CartType::RetailBT)
CartRetailBT::CartRetailBT(std::unique_ptr<u8[]>&& rom, u32 len, u32 chipid, ROMListEntry romparams, std::unique_ptr<u8[]>&& 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<FATStorage>&& 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<FATStorage>&& sdcard) :
CartSD(CopyToUnique(rom, len), len, chipid, romparams, userdata, std::move(sdcard))
{}
CartSD::CartSD(std::unique_ptr<u8[]>&& rom, u32 len, u32 chipid, ROMListEntry romparams, std::optional<FATStorage>&& sdcard) :
CartCommon(std::move(rom), len, chipid, false, romparams, CartType::Homebrew),
CartSD::CartSD(std::unique_ptr<u8[]>&& rom, u32 len, u32 chipid, ROMListEntry romparams, void* userdata, std::optional<FATStorage>&& 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<FATStorage>&& sdcard) :
CartSD(rom, len, chipid, romparams, std::move(sdcard))
CartHomebrew::CartHomebrew(const u8* rom, u32 len, u32 chipid, ROMListEntry romparams, void* userdata, std::optional<FATStorage>&& sdcard) :
CartSD(rom, len, chipid, romparams, userdata, std::move(sdcard))
{}
CartHomebrew::CartHomebrew(std::unique_ptr<u8[]>&& rom, u32 len, u32 chipid, ROMListEntry romparams, std::optional<FATStorage>&& sdcard) :
CartSD(std::move(rom), len, chipid, romparams, std::move(sdcard))
CartHomebrew::CartHomebrew(std::unique_ptr<u8[]>&& rom, u32 len, u32 chipid, ROMListEntry romparams, void* userdata, std::optional<FATStorage>&& 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<CartCommon> ParseROM(const u8* romdata, u32 romlen, std::optional<NDSCartArgs>&& args)
std::unique_ptr<CartCommon> ParseROM(const u8* romdata, u32 romlen, void* userdata, std::optional<NDSCartArgs>&& args)
{
return ParseROM(CopyToUnique(romdata, romlen), romlen, std::move(args));
return ParseROM(CopyToUnique(romdata, romlen), romlen, userdata, std::move(args));
}
std::unique_ptr<CartCommon> ParseROM(std::unique_ptr<u8[]>&& romdata, u32 romlen, std::optional<NDSCartArgs>&& args)
std::unique_ptr<CartCommon> ParseROM(std::unique_ptr<u8[]>&& romdata, u32 romlen, void* userdata, std::optional<NDSCartArgs>&& args)
{
if (romdata == nullptr)
{
@ -1659,21 +1667,21 @@ std::unique_ptr<CartCommon> ParseROM(std::unique_ptr<u8[]>&& romdata, u32 romlen
if (homebrew)
{
std::optional<FATStorage> sdcard = args && args->SDCard ? std::make_optional<FATStorage>(std::move(*args->SDCard)) : std::nullopt;
cart = std::make_unique<CartHomebrew>(std::move(cartrom), cartromsize, cartid, romparams, std::move(sdcard));
cart = std::make_unique<CartHomebrew>(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<FATStorage> sdcard = args && args->SDCard ? std::make_optional<FATStorage>(std::move(*args->SDCard)) : std::nullopt;
cart = std::make_unique<CartR4>(std::move(cartrom), cartromsize, cartid, romparams, CartR4TypeR4, CartR4LanguageEnglish, std::move(sdcard));
cart = std::make_unique<CartR4>(std::move(cartrom), cartromsize, cartid, romparams, CartR4TypeR4, CartR4LanguageEnglish, userdata, std::move(sdcard));
}
else if (cartid & 0x08000000)
cart = std::make_unique<CartRetailNAND>(std::move(cartrom), cartromsize, cartid, romparams, std::move(sram), sramlen);
cart = std::make_unique<CartRetailNAND>(std::move(cartrom), cartromsize, cartid, romparams, std::move(sram), sramlen, userdata);
else if (irversion != 0)
cart = std::make_unique<CartRetailIR>(std::move(cartrom), cartromsize, cartid, irversion, badDSiDump, romparams, std::move(sram), sramlen);
cart = std::make_unique<CartRetailIR>(std::move(cartrom), cartromsize, cartid, irversion, badDSiDump, romparams, std::move(sram), sramlen, userdata);
else if ((gamecode & 0xFFFFFF) == 0x505A55) // UZPx
cart = std::make_unique<CartRetailBT>(std::move(cartrom), cartromsize, cartid, romparams, std::move(sram), sramlen);
cart = std::make_unique<CartRetailBT>(std::move(cartrom), cartromsize, cartid, romparams, std::move(sram), sramlen, userdata);
else
cart = std::make_unique<CartRetail>(std::move(cartrom), cartromsize, cartid, badDSiDump, romparams, std::move(sram), sramlen);
cart = std::make_unique<CartRetail>(std::move(cartrom), cartromsize, cartid, badDSiDump, romparams, std::move(sram), sramlen, userdata);
args = std::nullopt;
return cart;

View File

@ -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<u8[]>&& 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<u8[]>&& 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 @@ public:
protected:
void ReadROM(u32 addr, u32 len, u8* data, u32 offset) const;
void* UserData;
std::unique_ptr<u8[]> ROM = nullptr;
u32 ROMLength = 0;
u32 ChipID = 0;
@ -139,6 +141,7 @@ public:
ROMListEntry romparams,
std::unique_ptr<u8[]>&& sram,
u32 sramlen,
void* userdata,
melonDS::NDSCart::CartType type = CartType::Retail
);
CartRetail(
@ -148,6 +151,7 @@ public:
ROMListEntry romparams,
std::unique_ptr<u8[]>&& sram,
u32 sramlen,
void* userdata,
melonDS::NDSCart::CartType type = CartType::Retail
);
~CartRetail() override;
@ -187,8 +191,8 @@ protected:
class CartRetailNAND : public CartRetail
{
public:
CartRetailNAND(const u8* rom, u32 len, u32 chipid, ROMListEntry romparams, std::unique_ptr<u8[]>&& sram, u32 sramlen);
CartRetailNAND(std::unique_ptr<u8[]>&& rom, u32 len, u32 chipid, ROMListEntry romparams, std::unique_ptr<u8[]>&& sram, u32 sramlen);
CartRetailNAND(const u8* rom, u32 len, u32 chipid, ROMListEntry romparams, std::unique_ptr<u8[]>&& sram, u32 sramlen, void* userdata);
CartRetailNAND(std::unique_ptr<u8[]>&& rom, u32 len, u32 chipid, ROMListEntry romparams, std::unique_ptr<u8[]>&& sram, u32 sramlen, void* userdata);
~CartRetailNAND() override;
void Reset() override;
@ -216,8 +220,8 @@ private:
class CartRetailIR : public CartRetail
{
public:
CartRetailIR(const u8* rom, u32 len, u32 chipid, u32 irversion, bool badDSiDump, ROMListEntry romparams, std::unique_ptr<u8[]>&& sram, u32 sramlen);
CartRetailIR(std::unique_ptr<u8[]>&& rom, u32 len, u32 chipid, u32 irversion, bool badDSiDump, ROMListEntry romparams, std::unique_ptr<u8[]>&& sram, u32 sramlen);
CartRetailIR(const u8* rom, u32 len, u32 chipid, u32 irversion, bool badDSiDump, ROMListEntry romparams, std::unique_ptr<u8[]>&& sram, u32 sramlen, void* userdata);
CartRetailIR(std::unique_ptr<u8[]>&& rom, u32 len, u32 chipid, u32 irversion, bool badDSiDump, ROMListEntry romparams, std::unique_ptr<u8[]>&& sram, u32 sramlen, void* userdata);
~CartRetailIR() override;
void Reset() override;
@ -235,8 +239,8 @@ private:
class CartRetailBT : public CartRetail
{
public:
CartRetailBT(const u8* rom, u32 len, u32 chipid, ROMListEntry romparams, std::unique_ptr<u8[]>&& sram, u32 sramlen);
CartRetailBT(std::unique_ptr<u8[]>&& rom, u32 len, u32 chipid, ROMListEntry romparams, std::unique_ptr<u8[]>&& sram, u32 sramlen);
CartRetailBT(const u8* rom, u32 len, u32 chipid, ROMListEntry romparams, std::unique_ptr<u8[]>&& sram, u32 sramlen, void* userdata);
CartRetailBT(std::unique_ptr<u8[]>&& rom, u32 len, u32 chipid, ROMListEntry romparams, std::unique_ptr<u8[]>&& sram, u32 sramlen, void* userdata);
~CartRetailBT() override;
u8 SPIWrite(u8 val, u32 pos, bool last) override;
@ -246,8 +250,8 @@ public:
class CartSD : public CartCommon
{
public:
CartSD(const u8* rom, u32 len, u32 chipid, ROMListEntry romparams, std::optional<FATStorage>&& sdcard = std::nullopt);
CartSD(std::unique_ptr<u8[]>&& rom, u32 len, u32 chipid, ROMListEntry romparams, std::optional<FATStorage>&& sdcard = std::nullopt);
CartSD(const u8* rom, u32 len, u32 chipid, ROMListEntry romparams, void* userdata, std::optional<FATStorage>&& sdcard = std::nullopt);
CartSD(std::unique_ptr<u8[]>&& rom, u32 len, u32 chipid, ROMListEntry romparams, void* userdata, std::optional<FATStorage>&& sdcard = std::nullopt);
~CartSD() override;
[[nodiscard]] const std::optional<FATStorage>& GetSDCard() const noexcept { return SD; }
@ -288,8 +292,8 @@ protected:
class CartHomebrew : public CartSD
{
public:
CartHomebrew(const u8* rom, u32 len, u32 chipid, ROMListEntry romparams, std::optional<FATStorage>&& sdcard = std::nullopt);
CartHomebrew(std::unique_ptr<u8[]>&& rom, u32 len, u32 chipid, ROMListEntry romparams, std::optional<FATStorage>&& sdcard = std::nullopt);
CartHomebrew(const u8* rom, u32 len, u32 chipid, ROMListEntry romparams, void* userdata, std::optional<FATStorage>&& sdcard = std::nullopt);
CartHomebrew(std::unique_ptr<u8[]>&& rom, u32 len, u32 chipid, ROMListEntry romparams, void* userdata, std::optional<FATStorage>&& sdcard = std::nullopt);
~CartHomebrew() override;
void Reset() override;
@ -322,7 +326,7 @@ enum CartR4Language
class CartR4 : public CartSD
{
public:
CartR4(std::unique_ptr<u8[]>&& rom, u32 len, u32 chipid, ROMListEntry romparams, CartR4Type ctype, CartR4Language clanguage,
CartR4(std::unique_ptr<u8[]>&& rom, u32 len, u32 chipid, ROMListEntry romparams, CartR4Type ctype, CartR4Language clanguage, void* userdata,
std::optional<FATStorage>&& sdcard = std::nullopt);
~CartR4() override;
@ -461,8 +465,8 @@ private:
/// 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<CartCommon> ParseROM(const u8* romdata, u32 romlen, std::optional<NDSCartArgs>&& args = std::nullopt);
std::unique_ptr<CartCommon> ParseROM(std::unique_ptr<u8[]>&& romdata, u32 romlen, std::optional<NDSCartArgs>&& args = std::nullopt);
std::unique_ptr<CartCommon> ParseROM(const u8* romdata, u32 romlen, void* userdata = nullptr, std::optional<NDSCartArgs>&& args = std::nullopt);
std::unique_ptr<CartCommon> ParseROM(std::unique_ptr<u8[]>&& romdata, u32 romlen, void* userdata = nullptr, std::optional<NDSCartArgs>&& args = std::nullopt);
}
#endif

View File

@ -67,9 +67,9 @@ static void DecryptR4Sector(u8* dest, u8* src, u16 key1)
}
}
CartR4::CartR4(std::unique_ptr<u8[]>&& rom, u32 len, u32 chipid, ROMListEntry romparams, CartR4Type ctype, CartR4Language clanguage,
CartR4::CartR4(std::unique_ptr<u8[]>&& rom, u32 len, u32 chipid, ROMListEntry romparams, CartR4Type ctype, CartR4Language clanguage, void* userdata,
std::optional<FATStorage>&& 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;

View File

@ -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);
void SignalStop(StopReason reason, void* userdata);
/**
* @returns The ID of the running melonDS instance if running in local multiplayer mode,
* or 0 if not.
*/
int InstanceID();
/**
* @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);
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);
// LAN comm interface
// 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;

View File

@ -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()

View File

@ -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();

View File

@ -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, &timestamp);
rxlen = Platform::MP_RecvPacket(RXBuffer, &timestamp, NDS.UserData);
if ((rxlen <= 0) && (!IsMP))
rxlen = WifiAP->RecvPacket(RXBuffer);
}
else
{
rxlen = Platform::MP_RecvHostPacket(RXBuffer, &timestamp);
rxlen = Platform::MP_RecvHostPacket(RXBuffer, &timestamp, NDS.UserData);
if (rxlen < 0)
{
// host is gone

View File

@ -241,9 +241,6 @@ private:
u16 MPLastSeqno;
bool MPInited;
bool LANInited;
int USUntilPowerOn;
// MULTIPLAYER SYNC APPARATUS

View File

@ -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

View File

@ -28,7 +28,7 @@ class Wifi;
class WifiAP
{
public:
WifiAP(Wifi* client);
WifiAP(Wifi* client, void* userdata);
~WifiAP();
void Reset();
@ -44,6 +44,7 @@ public:
private:
Wifi* Client;
void* UserData;
u64 USCounter;

View File

@ -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 <string>
#include <vector>
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

File diff suppressed because it is too large Load Diff

104
src/frontend/ScreenLayout.h Normal file
View File

@ -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

View File

@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#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;
}
}

View File

@ -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 <SDL2/SDL.h>
#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<AudioInterpolation>(Config::AudioInterp));
SetupMicInputData();
MicOpen();
}
void Enable()
{
if (audioDevice) SDL_PauseAudioDevice(audioDevice, 0);
MicOpen();
}
void Disable()
{
if (audioDevice) SDL_PauseAudioDevice(audioDevice, 1);
MicClose();
}
}

View File

@ -16,7 +16,6 @@
with melonDS. If not, see http://www.gnu.org/licenses/.
*/
#include <stdio.h>
#include <SDL2/SDL.h>
#include <QFileDialog>
@ -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<DSi&>(*emuThread->NDS);
auto dsi = static_cast<DSi*>(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<DSi&>(*emuThread->NDS);
dsi.I2C.GetBPTWL()->SetVolumeLevel(val);
auto dsi = static_cast<DSi*>(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<DSi&>(*emuThread->NDS);
auto dsi = static_cast<DSi*>(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;

View File

@ -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 @@ public:
return currentDlg;
}
currentDlg = new AudioSettingsDialog(parent, emuActive, emuThread);
currentDlg = new AudioSettingsDialog(parent);
currentDlg->show();
return currentDlg;
}
@ -56,6 +57,7 @@ public:
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

View File

@ -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

View File

@ -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<QVideoFrame::PixelFormat> 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;

View File

@ -33,6 +33,7 @@
#include <QMutex>
#include "types.h"
#include "Config.h"
class CameraManager;
@ -80,6 +81,8 @@ public:
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;

View File

@ -22,6 +22,7 @@
#include <QPainter>
#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);
}

View File

@ -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);
};

View File

@ -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);

View File

@ -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;
};

File diff suppressed because it is too large Load Diff

View File

@ -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 <variant>
#include <string>
#include <QString>
#include <unordered_map>
#include <tuple>
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<int, bool, std::string, int64_t> Default;
char TOMLPath[64];
bool InstanceUnique; // whether the setting can exist individually for each instance in multiplayer
};
struct CameraConfig
template<typename T>
using DefaultList = std::unordered_map<std::string, T>;
using RangeList = std::unordered_map<std::string, std::tuple<int, int>>;
class Table;
class Array
{
int InputType; // 0=blank 1=image 2=camera
std::string ImagePath;
std::string CamDeviceName;
bool XFlip;
public:
Array(toml::value& data);
~Array() {}
size_t Size();
void Clear();
Array GetArray(const int id);
int GetInt(const int id);
int64_t GetInt64(const int id);
bool GetBool(const int id);
std::string GetString(const int id);
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);
// convenience
QString GetQString(const int id)
{
return QString::fromStdString(GetString(id));
}
void SetQString(const int id, const QString& val)
{
return SetString(id, val.toStdString());
}
private:
toml::value& Data;
};
class Table
{
public:
//Table();
Table(toml::value& data, const std::string& path);
~Table() {}
extern int KeyMapping[12];
extern int JoyMapping[12];
Table& operator=(const Table& b);
extern int HKKeyMapping[HK_MAX];
extern int HKJoyMapping[HK_MAX];
Array GetArray(const std::string& path);
Table GetTable(const std::string& path, const std::string& defpath = "");
extern int JoystickID;
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 int WindowWidth;
extern int WindowHeight;
extern bool WindowMaximized;
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 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;
// convenience
extern bool ScreenUseGL;
extern bool ScreenVSync;
extern int ScreenVSyncInterval;
QString GetQString(const std::string& path)
{
return QString::fromStdString(GetString(path));
}
extern int _3DRenderer;
extern bool Threaded3D;
void SetQString(const std::string& path, const QString& val)
{
return SetString(path, val.toStdString());
}
extern int GL_ScaleFactor;
extern bool GL_BetterPolygons;
extern bool GL_HiresCoordinates;
private:
toml::value& Data;
std::string PathPrefix;
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;
extern std::string BIOS9Path;
extern std::string BIOS7Path;
extern std::string FirmwarePath;
extern std::string DSiBIOS9Path;
extern std::string DSiBIOS7Path;
extern std::string DSiFirmwarePath;
extern std::string DSiNANDPath;
extern bool DLDIEnable;
extern std::string DLDISDPath;
extern int DLDISize;
extern bool DLDIReadOnly;
extern bool DLDIFolderSync;
extern std::string DLDIFolderPath;
extern bool DSiSDEnable;
extern std::string DSiSDPath;
extern int DSiSDSize;
extern bool DSiSDReadOnly;
extern bool DSiSDFolderSync;
extern std::string DSiSDFolderPath;
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;
extern int MPAudioMode;
extern int MPRecvTimeout;
extern std::string LANDevice;
extern bool DirectLAN;
extern bool SavestateRelocSRAM;
extern int AudioInterp;
extern int AudioBitDepth;
extern int AudioVolume;
extern bool DSiVolumeSync;
extern int MicInputType;
extern std::string MicDevice;
extern std::string MicWavPath;
extern std::string LastROMFolder;
extern std::string LastBIOSFolder;
extern std::string RecentROMList[10];
extern std::string SaveFilePath;
extern std::string SavestatePath;
extern std::string CheatFilePath;
extern bool EnableCheats;
extern bool MouseHide;
extern int MouseHideSeconds;
extern bool PauseLostFocus;
extern std::string UITheme;
extern int64_t RTCOffset;
extern bool DSBatteryLevelOkay;
extern int DSiBatteryLevel;
extern bool DSiBatteryCharging;
extern bool DSiFullBIOSBoot;
extern CameraConfig Camera[2];
extern bool GdbEnabled;
extern int GdbPortARM7;
extern int GdbPortARM9;
extern bool GdbARM7BreakOnStartup;
extern bool GdbARM9BreakOnStartup;
toml::value& ResolvePath(const std::string& path);
template<typename T> T FindDefault(const std::string& path, T def, DefaultList<T> list);
};
bool Load();
void Save();
Table GetLocalTable(int instance);
inline Table GetGlobalTable() { return GetLocalTable(-1); }
}
#endif // PLATFORMCONFIG_H
#endif // CONFIG_H

View File

@ -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();
}

View File

@ -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;
};

View File

@ -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 <SDL2/SDL.h>
#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<int> &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<melonDS::ARM9BIOSImage> loadARM9BIOS() noexcept;
std::unique_ptr<melonDS::ARM7BIOSImage> loadARM7BIOS() noexcept;
std::unique_ptr<melonDS::DSiBIOSImage> loadDSiARM9BIOS() noexcept;
std::unique_ptr<melonDS::DSiBIOSImage> loadDSiARM7BIOS() noexcept;
melonDS::Firmware generateFirmware(int type) noexcept;
std::optional<melonDS::Firmware> loadFirmware(int type) noexcept;
std::optional<melonDS::DSi_NAND::NANDImage> loadNAND(const std::array<melonDS::u8, melonDS::DSiBIOSSize>& arm7ibios) noexcept;
std::optional<melonDS::FATStorageArgs> getSDCardArgs(const std::string& key) noexcept;
std::optional<melonDS::FATStorage> 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<melonDS::u8[]>& outContent);
void clearBackupState();
std::pair<std::unique_ptr<melonDS::Firmware>, std::string> generateDefaultFirmware();
bool parseMacAddress(void* data);
void customizeFirmware(melonDS::Firmware& firmware, bool overridesettings) noexcept;
bool loadROMData(const QStringList& filepath, std::unique_ptr<melonDS::u8[]>& 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<<id); }
bool hotkeyPressed(int id) { return hotkeyPress & (1<<id); }
bool hotkeyReleased(int id) { return hotkeyRelease & (1<<id); }
int instanceID;
EmuThread* emuThread;
MainWindow* mainWindow;
MainWindow* windowList[kMaxWindows];
int numWindows;
Config::Table globalCfg;
Config::Table localCfg;
melonDS::NDS* nds;
int cartType;
std::string baseROMDir;
std::string baseROMName;
std::string baseAssetName;
int gbaCartType;
std::string baseGBAROMDir;
std::string baseGBAROMName;
std::string baseGBAAssetName;
// HACK
public:
std::unique_ptr<SaveManager> ndsSave;
std::unique_ptr<SaveManager> gbaSave;
std::unique_ptr<SaveManager> firmwareSave;
bool doLimitFPS;
int maxFPS;
bool doAudioSync;
private:
std::unique_ptr<melonDS::Savestate> 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

View File

@ -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<AudioInterpolation>(audiointerp));
setupMicInputData();
micOpen();
}
void EmuInstance::audioEnable()
{
if (audioDevice) SDL_PauseAudioDevice(audioDevice, 0);
micOpen();
}
void EmuInstance::audioDisable()
{
if (audioDevice) SDL_PauseAudioDevice(audioDevice, 1);
micClose();
}

View File

@ -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 <QKeyEvent>
#include <SDL2/SDL.h>
#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<<i);
for (int i = 0; i < HK_MAX; i++)
if (keyHK == hkKeyMapping[i])
keyHotkeyMask |= (1<<i);
}
void EmuInstance::onKeyRelease(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<<i);
for (int i = 0; i < HK_MAX; i++)
if (keyHK == hkKeyMapping[i])
keyHotkeyMask &= ~(1<<i);
}
void EmuInstance::keyReleaseAll()
{
keyInputMask = 0xFFF;
keyHotkeyMask = 0;
}
bool EmuInstance::joystickButtonDown(int val)
{
if (val == -1) return false;
bool hasbtn = ((val & 0xFFFF) != 0xFFFF);
if (hasbtn)
{
if (val & 0x100)
{
int hatnum = (val >> 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;
}

View File

@ -16,11 +16,8 @@
with melonDS. If not, see http://www.gnu.org/licenses/.
*/
#include <stdio.h>
#include <QFileDialog>
#include <QMessageBox>
#include <QList>
#include <QDateEdit>
#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();
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));
lastBIOSFolder = cfg.GetQString("LastBIOSFolder");
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<type*>(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();
bool modified = false;
int consoleType = ui->cbxConsoleType->currentIndex();
bool directBoot = ui->chkDirectBoot->isChecked();
#define CHECK_ORIGVAL(type, val) \
if (!modified) for (type* w : findChildren<type*>(nullptr)) \
{ \
QVariant v = w->val(); \
if (v != w->property("user_originalValue")) \
{ \
modified = true; \
break; \
}\
}
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();
CHECK_ORIGVAL(QLineEdit, text);
CHECK_ORIGVAL(QSpinBox, value);
CHECK_ORIGVAL(QComboBox, currentIndex);
CHECK_ORIGVAL(QCheckBox, 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();
#undef CHECK_ORIGVAL
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();
if (QVariant(ui->txtFirmwarePath->text()) != ui->txtFirmwarePath->property("user_originalValue"))
verifyFirmware();
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)
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;
auto& cfg = emuInstance->getGlobalConfig();
auto& instcfg = emuInstance->getLocalConfig();
Config::DLDIEnable = dldiEnable;
Config::DLDISDPath = dldiSDPath;
Config::DLDISize = dldiSize;
Config::DLDIReadOnly = dldiReadOnly;
Config::DLDIFolderSync = dldiFolderSync;
Config::DLDIFolderPath = dldiFolderPath;
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());
Config::DSiBIOS9Path = dsiBios9Path;
Config::DSiBIOS7Path = dsiBios7Path;
Config::DSiFirmwarePath = dsiFirmwarePath;
Config::DSiNANDPath = dsiNANDPath;
Config::DSiFullBIOSBoot = dsiFullBiosBoot;
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());
Config::DSiSDEnable = dsiSDEnable;
Config::DSiSDPath = dsiSDPath;
Config::DSiSDSize = dsiSDSize;
Config::DSiSDReadOnly = dsiSDReadOnly;
Config::DSiSDFolderSync = dsiSDFolderSync;
Config::DSiSDFolderPath = dsiSDFolderPath;
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;

View File

@ -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

View File

@ -29,13 +29,11 @@
#include <SDL2/SDL.h>
#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;
emuInstance = inst;
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()));
emuStatus = emuStatus_Paused;
emuPauseStack = emuPauseStackRunning;
emuActive = false;
}
std::unique_ptr<NDS> EmuThread::CreateConsole(
std::unique_ptr<melonDS::NDSCart::CartCommon> &&ndscart,
std::unique_ptr<melonDS::GBACart::CartCommon> &&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<unsigned>(Config::JIT_MaxBlockSize),
Config::JIT_LiteralOptimisations,
Config::JIT_BranchOptimisations,
Config::JIT_FastMemory,
};
#endif
#ifdef GDBSTUB_ENABLED
GDBArgs gdbargs {
static_cast<u16>(Config::GdbPortARM7),
static_cast<u16>(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<AudioBitDepth>(Config::AudioBitDepth),
static_cast<AudioInterpolation>(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<melonDS::DSi>(std::move(args));
}
return std::make_unique<melonDS::NDS>(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<NDSCart::CartCommon> nextndscart;
if (std::holds_alternative<Keep>(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<std::unique_ptr<NDSCart::CartCommon>>(&ndsargs))
{
nextndscart = std::move(*ptr);
ndsargs = {};
}
if (auto* cartsd = dynamic_cast<NDSCart::CartSD*>(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<GBACart::CartCommon> nextgbacart;
if (std::holds_alternative<Keep>(gbaargs))
{
nextgbacart = NDS ? NDS->EjectGBACart() : nullptr;
}
else if (const auto ptr = std::get_if<std::unique_ptr<GBACart::CartCommon>>(&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<DSi&>(*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<unsigned>(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<AudioInterpolation>(Config::AudioInterp));
NDS->SPU.SetDegrade10Bit(static_cast<AudioBitDepth>(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<ScreenPanelGL*>(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<DSi&>(*NDS);
DSi* dsi = static_cast<DSi*>(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<DSi&>(*NDS);
u8 volumeLevel = dsi.I2C.GetBPTWL()->GetVolumeLevel();
DSi* dsi = static_cast<DSi*>(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,38 +431,140 @@ void EmuThread::run()
SDL_Delay(75);
if (screenGL)
screenGL->drawScreenGL();
ContextRequestKind contextRequest = ContextRequest;
if (contextRequest == contextRequest_InitGL)
if (useOpenGL)
{
screenGL = static_cast<ScreenPanelGL*>(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::sendMessage(Message msg)
{
msgMutex.lock();
msgQueue.enqueue(msg);
msgMutex.unlock();
}
void EmuThread::waitMessage(int num)
{
if (QThread::currentThread() == this) return;
msgSemaphore.acquire(num);
}
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)
@ -670,75 +572,78 @@ void EmuThread::changeWindowTitle(char* title)
emit windowTitleChange(QString(title));
}
void EmuThread::emuRun()
{
EmuRunning = emuStatus_Running;
EmuPauseStack = EmuPauseStackRunning;
RunningSomething = true;
// checkme
emit windowEmuStart();
AudioInOut::Enable();
}
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::emuRun()
{
sendMessage(msg_EmuRun);
waitMessage();
}
void EmuThread::emuPause()
{
EmuPauseStack++;
if (EmuPauseStack > EmuPauseStackPauseThreshold) return;
PrevEmuStatus = EmuRunning;
EmuRunning = emuStatus_Paused;
while (EmuStatus != emuStatus_Paused);
AudioInOut::Disable();
sendMessage(msg_EmuPause);
waitMessage();
}
void EmuThread::emuUnpause()
{
if (EmuPauseStack < EmuPauseStackPauseThreshold) return;
EmuPauseStack--;
if (EmuPauseStack >= EmuPauseStackPauseThreshold) return;
EmuRunning = PrevEmuStatus;
AudioInOut::Enable();
sendMessage(msg_EmuUnpause);
waitMessage();
}
void EmuThread::emuStop()
void EmuThread::emuTogglePause()
{
EmuRunning = emuStatus_Exit;
EmuPauseStack = EmuPauseStackRunning;
if (emuStatus == emuStatus_Paused)
emuUnpause();
else
emuPause();
}
AudioInOut::Disable();
void EmuThread::emuStop(bool external)
{
sendMessage({.type = msg_EmuStop, .stopExternal = external});
waitMessage();
}
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:
NDS->GPU.SetRenderer3D(std::make_unique<SoftRenderer>());
break;
case renderer3D_OpenGL:
NDS->GPU.SetRenderer3D(GLRenderer::New());
break;
case renderer3D_OpenGLCompute:
NDS->GPU.SetRenderer3D(ComputeRenderer::New());
break;
default: __builtin_unreachable();
case renderer3D_Software:
emuInstance->nds->GPU.SetRenderer3D(std::make_unique<SoftRenderer>());
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:
static_cast<SoftRenderer&>(NDS->GPU.GetRenderer3D()).SetThreaded(Config::Threaded3D, NDS->GPU);
break;
case renderer3D_OpenGL:
static_cast<GLRenderer&>(NDS->GPU.GetRenderer3D()).SetRenderSettings(Config::GL_BetterPolygons, Config::GL_ScaleFactor);
break;
case renderer3D_OpenGLCompute:
static_cast<ComputeRenderer&>(NDS->GPU.GetRenderer3D()).SetRenderSettings(Config::GL_ScaleFactor, Config::GL_HiresCoordinates);
break;
default: __builtin_unreachable();
case renderer3D_Software:
static_cast<SoftRenderer&>(emuInstance->nds->GPU.GetRenderer3D()).SetThreaded(
cfg.GetBool("3D.Soft.Threaded"),
emuInstance->nds->GPU);
break;
case renderer3D_OpenGL:
static_cast<GLRenderer&>(emuInstance->nds->GPU.GetRenderer3D()).SetRenderSettings(
cfg.GetBool("3D.GL.BetterPolygons"),
cfg.GetInt("3D.GL.ScaleFactor"));
break;
case renderer3D_OpenGLCompute:
static_cast<ComputeRenderer&>(emuInstance->nds->GPU.GetRenderer3D()).SetRenderSettings(
cfg.GetInt("3D.GL.ScaleFactor"),
cfg.GetBool("3D.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);
}

View File

@ -21,10 +21,13 @@
#include <QThread>
#include <QMutex>
#include <QSemaphore>
#include <QQueue>
#include <atomic>
#include <variant>
#include <optional>
#include <list>
#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 @@ public:
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<melonDS::NDS> 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 @@ signals:
void syncVolumeLevel();
private:
void handleMessages();
void updateRenderer();
void compileShaders();
std::unique_ptr<melonDS::NDS> CreateConsole(
std::unique_ptr<melonDS::NDSCart::CartCommon>&& ndscart,
std::unique_ptr<melonDS::GBACart::CartCommon>&& gbacart
) noexcept;
enum EmuStatusKind
{
emuStatus_Exit,
@ -109,30 +143,30 @@ private:
emuStatus_Paused,
emuStatus_FrameStep,
};
std::atomic<EmuStatusKind> 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<ContextRequestKind> ContextRequest = contextRequest_None;
QMutex msgMutex;
QSemaphore msgSemaphore;
QQueue<Message> msgQueue;
ScreenPanelGL* screenGL;
EmuInstance* emuInstance;
int autoScreenSizing;
int lastVideoRenderer = -1;
double perfCountsSec;
bool useOpenGL;
int videoRenderer;
bool videoSettingsDirty;
};
#endif // EMUTHREAD_H

View File

@ -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<type*>(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();
bool modified = false;
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();
#define CHECK_ORIGVAL(type, val) \
if (!modified) for (type* w : findChildren<type*>(nullptr)) \
{ \
QVariant v = w->val(); \
if (v != w->property("user_originalValue")) \
{ \
modified = true; \
break; \
}\
}
std::string newMAC = ui->txtMAC->text().toStdString();
CHECK_ORIGVAL(QLineEdit, text);
CHECK_ORIGVAL(QComboBox, currentIndex);
CHECK_ORIGVAL(QCheckBox, isChecked);
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)
#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");
Config::FirmwareUsername = newName;
Config::FirmwareLanguage = newLanguage;
Config::FirmwareFavouriteColour = newFavColor;
Config::FirmwareBirthdayDay = newBirthdayDay;
Config::FirmwareBirthdayMonth = newBirthdayMonth;
Config::FirmwareMessage = newMessage;
firmcfg.SetBool("OverrideSettings", ui->overrideFirmwareBox->isChecked());
Config::FirmwareMAC = newMAC;
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());
firmcfg.SetQString("MAC", ui->txtMAC->text());
Config::Save();

View File

@ -25,6 +25,8 @@
namespace Ui { class FirmwareSettingsDialog; }
class FirmwareSettingsDialog;
class EmuInstance;
class FirmwareSettingsDialog : public QDialog
{
Q_OBJECT
@ -129,6 +131,7 @@ private:
bool verifyMAC();
Ui::FirmwareSettingsDialog* ui;
EmuInstance* emuInstance;
};
#endif // FIRMWARESETTINGSDIALOG_H

View File

@ -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 <QKeyEvent>
#include <SDL2/SDL.h>
#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<<i);
for (int i = 0; i < HK_MAX; i++)
if (keyHK == Config::HKKeyMapping[i])
KeyHotkeyMask |= (1<<i);
}
void KeyRelease(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<<i);
for (int i = 0; i < HK_MAX; i++)
if (keyHK == Config::HKKeyMapping[i])
KeyHotkeyMask &= ~(1<<i);
}
void KeyReleaseAll()
{
KeyInputMask = 0xFFF;
KeyHotkeyMask = 0;
}
bool JoystickButtonDown(int val)
{
if (val == -1) return false;
bool hasbtn = ((val & 0xFFFF) != 0xFFFF);
if (hasbtn)
{
if (val & 0x100)
{
int hatnum = (val >> 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<<id); }
bool HotkeyPressed(int id) { return HotkeyPress & (1<<id); }
bool HotkeyReleased(int id) { return HotkeyRelease & (1<<id); }
// 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)
#ifdef __WIN32__
bool IsRightModKey(QKeyEvent* event)
{
quint32 scan = event->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
}

View File

@ -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();
}

View File

@ -24,6 +24,7 @@
#include <initializer_list>
#include "Config.h"
#include "EmuInstance.h"
static constexpr int keypad_num = 12;
@ -89,6 +90,8 @@ public:
explicit InputConfigDialog(QWidget* parent);
~InputConfigDialog();
SDL_Joystick* getJoystick();
static InputConfigDialog* currentDlg;
static InputConfigDialog* openDlg(QWidget* parent)
{
@ -123,9 +126,12 @@ private:
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;
};

View File

@ -23,8 +23,10 @@
#include <SDL2/SDL.h>
#include "Input.h"
#include "Platform.h"
#include "EmuInstance.h"
class InputConfigDialog;
class KeyMapButton : public QPushButton
{
@ -76,7 +78,7 @@ protected:
if (!ismod)
key |= mod;
else if (Input::IsRightModKey(event))
else if (isRightModKey(event))
key |= (1<<31);
*mapping = key;
@ -162,6 +164,9 @@ public:
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 @@ public:
}
protected:
void showEvent(QShowEvent* event) override
{
if (event->spontaneous()) return;
QWidget* w = parentWidget();
for (;;)
{
parentDialog = qobject_cast<InputConfigDialog*>(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 @@ protected:
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:
return str;
}
InputConfigDialog* parentDialog;
int* mapping;
bool isHotkey;

View File

@ -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<QString> 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);

View File

@ -24,6 +24,8 @@
namespace Ui { class InterfaceSettingsDialog; }
class InterfaceSettingsDialog;
class EmuInstance;
class InterfaceSettingsDialog : public QDialog
{
Q_OBJECT
@ -51,7 +53,7 @@ public:
}
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

View File

@ -16,25 +16,10 @@
with melonDS. If not, see http://www.gnu.org/licenses/.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <cstring>
#include <QMutex>
#include <QSemaphore>
#ifdef __WIN32__
#include <windows.h>
#else
#include <fcntl.h>
#include <semaphore.h>
#include <time.h>
#ifdef __APPLE__
#include "sem_timedwait.h"
#endif
#endif
#include <string>
#include <QSharedMemory>
#include "Config.h"
#include "LocalMP.h"
#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;
};
const u32 kPacketQueueSize = 0x10000;
const u32 kReplyQueueSize = 0x10000;
const u32 kMaxFrameSize = 0x948;
QSharedMemory* MPQueue;
int InstanceID;
u32 PacketReadOffset;
u32 ReplyReadOffset;
const u32 kQueueSize = 0x20000;
const u32 kMaxFrameSize = 0x800;
const u32 kPacketStart = sizeof(MPQueueHeader);
const u32 kReplyStart = kQueueSize / 2;
const u32 kPacketEnd = kReplyStart;
const u32 kReplyEnd = kQueueSize;
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;
return SemPool[num].tryAcquire(1);
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
ts.tv_nsec += timeout * 1000000;
long sec = ts.tv_nsec / 1000000000;
ts.tv_nsec -= sec * 1000000000;
ts.tv_sec += sec;
return sem_timedwait(SemPool[num], &ts) == 0;
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");
MPQueueLock.lock();
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;
}
memset(MPPacketQueue, 0, kPacketQueueSize);
memset(MPReplyQueue, 0, kReplyQueueSize);
memset(&MPStatus, 0, sizeof(MPStatus));
memset(PacketReadOffset, 0, sizeof(PacketReadOffset));
memset(ReplyReadOffset, 0, sizeof(ReplyReadOffset));
MPQueue->lock();
memset(MPQueue->data(), 0, MPQueue->size());
MPQueueHeader* header = (MPQueueHeader*)MPQueue->data();
header->PacketWriteOffset = kPacketStart;
header->ReplyWriteOffset = kReplyStart;
MPQueue->unlock();
}
MPQueue->lock();
MPQueueHeader* header = (MPQueueHeader*)MPQueue->data();
u16 mask = header->InstanceBitmask;
for (int i = 0; i < 16; i++)
{
if (!(mask & (1<<i)))
{
InstanceID = i;
header->InstanceBitmask |= (1<<i);
//header->ConnectedBitmask |= (1 << i);
break;
}
}
header->NumInstances++;
PacketReadOffset = header->PacketWriteOffset;
ReplyReadOffset = header->ReplyWriteOffset;
MPQueue->unlock();
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();
}
}

View File

@ -30,16 +30,16 @@ void DeInit();
void SetRecvTimeout(int timeout);
void Begin();
void End();
void Begin(int inst);
void End(int inst);
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);
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);
}

View File

@ -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();
}
//

View File

@ -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;
};

112
src/frontend/qt_sdl/Net.cpp Normal file
View File

@ -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 <stdio.h>
#include <string.h>
#include <QMutex>
#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;
}
}

View File

@ -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 <QMainWindow>
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 RegisterInstance(int inst);
void UnregisterInstance(int inst);
void AudioSync(melonDS::NDS& nds);
void RXEnqueue(const void* buf, int len);
void UpdateSettings(melonDS::NDS& nds);
void Enable();
void Disable();
int SendPacket(u8* data, int len, int inst);
int RecvPacket(u8* data, int inst);
}
#endif
#endif // NET_H

View File

@ -16,16 +16,12 @@
with melonDS. If not, see http://www.gnu.org/licenses/.
*/
// direct LAN interface. Currently powered by libpcap, may change.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pcap/pcap.h>
#include "Wifi.h"
#include "LAN_PCap.h"
#include "Net.h"
#include "Config.h"
#include "Platform.h"
#include "main.h"
#ifdef __WIN32__
#include <iphlpapi.h>
@ -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;
if (PCapAdapter == nullptr)
return;
int ret = 0;
if (RXNum > 0)
{
memcpy(data, PacketBuffer, PacketLen);
ret = PacketLen;
RXNum = 0;
}
pcap_dispatch(PCapAdapter, 1, RXCallback, NULL);
return ret;
pcap_dispatch(PCapAdapter, 1, RXCallback, nullptr);
}
}

View File

@ -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

View File

@ -16,13 +16,9 @@
with melonDS. If not, see http://www.gnu.org/licenses/.
*/
// indirect LAN interface, powered by BSD sockets.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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;
}
}

View File

@ -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

View File

@ -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;
}

View File

@ -16,40 +16,33 @@
with melonDS. If not, see http://www.gnu.org/licenses/.
*/
#ifndef INPUT_H
#define INPUT_H
#include <SDL2/SDL.h>
#ifndef PACKETDISPATCHER_H
#define PACKETDISPATCHER_H
#include <QMutex>
#include "types.h"
#include "FIFO.h"
namespace Input
using PacketQueue = melonDS::RingBuffer<0x8000>;
class PacketDispatcher
{
public:
PacketDispatcher();
~PacketDispatcher();
using namespace melonDS;
extern int JoystickID;
extern SDL_Joystick* Joystick;
void registerInstance(int inst);
void unregisterInstance(int inst);
extern u32 InputMask;
void clear();
void Init();
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);
// set joystickID before calling openJoystick()
void OpenJoystick();
void CloseJoystick();
private:
QMutex mutex;
melonDS::u16 instanceMask;
PacketQueue* packetQueues[16];
};
void KeyPress(QKeyEvent* event);
void KeyRelease(QKeyEvent* event);
void KeyReleaseAll();
void Process();
bool HotkeyDown(int id);
bool HotkeyPressed(int id);
bool HotkeyReleased(int id);
bool IsRightModKey(QKeyEvent* event);
}
#endif // INPUT_H
#endif // PACKETDISPATCHER_H

View File

@ -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();
int inst = Platform::InstanceID();
auto& cfg = emuInstance->getGlobalConfig();
ui->txtSaveFilePath->setText(cfg.GetQString("SaveFilePath"));
ui->txtSavestatePath->setText(cfg.GetQString("SavestatePath"));
ui->txtCheatFilePath->setText(cfg.GetQString("CheatFilePath"));
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<type*>(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;
if ( saveFilePath != Config::SaveFilePath
|| savestatePath != Config::SavestatePath
|| cheatFilePath != Config::CheatFilePath)
#define CHECK_ORIGVAL(type, val) \
if (!modified) for (type* w : findChildren<type*>(nullptr)) \
{ \
QVariant v = w->val(); \
if (v != w->property("user_originalValue")) \
{ \
modified = true; \
break; \
}\
}
CHECK_ORIGVAL(QLineEdit, text);
#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::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;

View File

@ -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

View File

@ -29,17 +29,15 @@
#include <QThread>
#include <QSemaphore>
#include <QMutex>
#include <QOpenGLContext>
#include <QSharedMemory>
#include <QTemporaryFile>
#include <SDL_loadso.h>
#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<<i)))
{
IPCInstanceID = i;
*(u16*)&data[0] |= (1<<i);
break;
}
}
IPCBuffer->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<<IPCInstanceID);
IPCBuffer->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()
void MP_Begin(void* userdata)
{
return LocalMP::Init();
int inst = ((EmuInstance*)userdata)->getInstanceID();
LocalMP::Begin(inst);
}
void MP_DeInit()
void MP_End(void* userdata)
{
return LocalMP::DeInit();
int inst = ((EmuInstance*)userdata)->getInstanceID();
LocalMP::End(inst);
}
void MP_Begin()
int MP_SendPacket(u8* data, int len, u64 timestamp, void* userdata)
{
return LocalMP::Begin();
int inst = ((EmuInstance*)userdata)->getInstanceID();
return LocalMP::SendPacket(inst, data, len, timestamp);
}
void MP_End()
int MP_RecvPacket(u8* data, u64* timestamp, void* userdata)
{
return LocalMP::End();
int inst = ((EmuInstance*)userdata)->getInstanceID();
return LocalMP::RecvPacket(inst, data, timestamp);
}
int MP_SendPacket(u8* data, int len, u64 timestamp)
int MP_SendCmd(u8* data, int len, u64 timestamp, void* userdata)
{
return LocalMP::SendPacket(data, len, timestamp);
int inst = ((EmuInstance*)userdata)->getInstanceID();
return LocalMP::SendCmd(inst, data, len, timestamp);
}
int MP_RecvPacket(u8* data, u64* timestamp)
int MP_SendReply(u8* data, int len, u64 timestamp, u16 aid, void* userdata)
{
return LocalMP::RecvPacket(data, timestamp);
int inst = ((EmuInstance*)userdata)->getInstanceID();
return LocalMP::SendReply(inst, data, len, timestamp, aid);
}
int MP_SendCmd(u8* data, int len, u64 timestamp)
int MP_SendAck(u8* data, int len, u64 timestamp, void* userdata)
{
return LocalMP::SendCmd(data, len, timestamp);
int inst = ((EmuInstance*)userdata)->getInstanceID();
return LocalMP::SendAck(inst, data, len, timestamp);
}
int MP_SendReply(u8* data, int len, u64 timestamp, u16 aid)
int MP_RecvHostPacket(u8* data, u64* timestamp, void* userdata)
{
return LocalMP::SendReply(data, len, timestamp, aid);
int inst = ((EmuInstance*)userdata)->getInstanceID();
return LocalMP::RecvHostPacket(inst, data, timestamp);
}
int MP_SendAck(u8* data, int len, u64 timestamp)
u16 MP_RecvReplies(u8* data, u64 timestamp, u16 aidmask, void* userdata)
{
return LocalMP::SendAck(data, len, timestamp);
}
int MP_RecvHostPacket(u8* data, u64* timestamp)
{
return LocalMP::RecvHostPacket(data, timestamp);
}
u16 MP_RecvReplies(u8* data, u64 timestamp, u16 aidmask)
{
return LocalMP::RecvReplies(data, timestamp, aidmask);
}
bool LAN_Init()
{
if (Config::DirectLAN)
{
if (!LAN_PCap::Init(true))
return false;
}
else
{
if (!LAN_Socket::Init())
return false;
}
return true;
}
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)
{
if (Config::DirectLAN)
return LAN_PCap::SendPacket(data, len);
else
return LAN_Socket::SendPacket(data, len);
}
int LAN_RecvPacket(u8* data)
{
if (Config::DirectLAN)
return LAN_PCap::RecvPacket(data);
else
return LAN_Socket::RecvPacket(data);
int inst = ((EmuInstance*)userdata)->getInstanceID();
return LocalMP::RecvReplies(inst, data, timestamp, aidmask);
}
void Camera_Start(int num)
int Net_SendPacket(u8* data, int len, void* userdata)
{
int inst = ((EmuInstance*)userdata)->getInstanceID();
Net::SendPacket(data, len, inst);
return 0;
}
int Net_RecvPacket(u8* data, void* userdata)
{
int inst = ((EmuInstance*)userdata)->getInstanceID();
return Net::RecvPacket(data, inst);
}
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);
}

View File

@ -30,43 +30,47 @@
#include <QtDebug>
#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<DSi&>(*emuThread->NDS);
oldDSiBatteryLevel = dsi.I2C.GetBPTWL()->GetBatteryLevel();
oldDSiBatteryCharging = dsi.I2C.GetBPTWL()->GetBatteryCharging();
auto dsi = static_cast<DSi*>(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<DSi&>(*emuThread->NDS);
ui->cbDSiBatteryCharging->setChecked(dsi.I2C.GetBPTWL()->GetBatteryCharging());
auto dsi = static_cast<DSi*>(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<DSi&>(*emuThread->NDS);
Config::DSiBatteryLevel = dsi.I2C.GetBPTWL()->GetBatteryLevel();
Config::DSiBatteryCharging = dsi.I2C.GetBPTWL()->GetBatteryCharging();
auto dsi = static_cast<DSi*>(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<DSi&>(*emuThread->NDS);
dsi.I2C.GetBPTWL()->SetBatteryLevel(oldDSiBatteryLevel);
dsi.I2C.GetBPTWL()->SetBatteryCharging(oldDSiBatteryCharging);
auto dsi = static_cast<DSi*>(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<DSi&>(*emuThread->NDS);
dsi.I2C.GetBPTWL()->SetBatteryCharging(ui->cbDSiBatteryCharging->isChecked());
auto dsi = static_cast<DSi*>(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<DSi&>(*emuThread->NDS);
auto dsi = static_cast<DSi*>(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();

View File

@ -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 @@ public:
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;

View File

@ -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<int>>("QVector<int>");
qRegisterMetaType<u32>("u32");
qRegisterMetaType<s32>("s32");
@ -91,7 +92,7 @@ void RAMInfoDialog::ShowRowsInTable()
for (u32 row = scrollValue; row < std::min<u32>(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;
}

View File

@ -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 @@ public:
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

View File

@ -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<u8*>(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;

View File

@ -25,21 +25,22 @@
#include <QImage>
#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 @@ public:
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;

View File

@ -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 <QMainWindow>
#include "MemConstants.h"
#include <Args.h>
#include <optional>
#include <string>
#include <memory>
#include <vector>
namespace melonDS
{
class NDS;
class DSi;
class FATStorage;
class FATStorageArgs;
}
class EmuThread;
namespace ROMManager
{
using namespace melonDS;
extern std::unique_ptr<SaveManager> NDSSave;
extern std::unique_ptr<SaveManager> GBASave;
extern std::unique_ptr<SaveManager> 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<ARM9BIOSImage> LoadARM9BIOS() noexcept;
std::unique_ptr<ARM7BIOSImage> LoadARM7BIOS() noexcept;
std::unique_ptr<DSiBIOSImage> LoadDSiARM9BIOS() noexcept;
std::unique_ptr<DSiBIOSImage> LoadDSiARM7BIOS() noexcept;
std::optional<FATStorageArgs> GetDSiSDCardArgs() noexcept;
std::optional<FATStorage> LoadDSiSDCard() noexcept;
std::optional<FATStorageArgs> GetDLDISDCardArgs() noexcept;
std::optional<FATStorage> 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<Firmware> LoadFirmware(int type) noexcept;
/// Loads and customizes a NAND image based on the values in Config
std::optional<DSi_NAND::NANDImage> LoadNAND(const std::array<u8, DSiBIOSSize>& 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<int> &animatedSequenceRef);
}
#endif // ROMMANAGER_H

View File

@ -16,20 +16,13 @@
with melonDS. If not, see http://www.gnu.org/licenses/.
*/
#include <stdlib.h>
#include <time.h>
#include <stdio.h>
#include <string.h>
#include <optional>
#include <vector>
#include <string>
#include <algorithm>
#include <cmath>
#include <QPaintEvent>
#include <QPainter>
#include <QDebug>
#ifndef _WIN32
#ifndef APPLE
#include <qpa/qplatformnativeinterface.h>
@ -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<MainWindow*>(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<Frontend::ScreenLayout>(Config::ScreenLayout),
static_cast<Frontend::ScreenRotation>(Config::ScreenRotation),
static_cast<Frontend::ScreenSizing>(sizing),
Config::ScreenGap,
Config::IntegerScaling != 0,
Config::ScreenSwap != 0,
aspectTop,
aspectBot);
layout.Setup(w, h,
static_cast<ScreenLayoutType>(screenLayout),
static_cast<ScreenRotation>(screenRotation),
static_cast<ScreenSizing>(sizing),
screenGap,
integerScaling != 0,
screenSwap != 0,
aspectTop,
aspectBot);
numScreens = Frontend::GetScreenTransforms(screenMatrix[0], screenKind);
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> windowInfo = getWindowInfo();
std::array<GL::Context::Version, 2> 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> 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<GL::Context::Version, 2> 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<WindowInfo> 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();

View File

@ -31,11 +31,12 @@
#include <QTimer>
#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 @@ public:
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 @@ public:
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 @@ protected:
unsigned int osdID;
std::deque<OSDItem> osdItems;
void loadConfig();
virtual void setupScreenLayout();
void resizeEvent(QResizeEvent* event) override;
@ -98,12 +131,6 @@ protected:
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 @@ private:
void setupScreenLayout() override;
QImage screen[2];
QTransform screenTrans[Frontend::MaxScreenTransforms];
QTransform screenTrans[kMaxScreenTransforms];
};
@ -152,6 +179,7 @@ public:
void initOpenGL();
void deinitOpenGL();
void makeCurrentGL();
void drawScreenGL();
GL::Context* getContext() { return glContext.get(); }
@ -177,7 +205,6 @@ private:
QMutex screenSettingsLock;
WindowInfo windowInfo;
bool filter;
int lastScreenWidth = -1, lastScreenHeight = -1;

View File

@ -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<DSi_NAND::NANDImage> 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;

View File

@ -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;

View File

@ -16,7 +16,6 @@
with melonDS. If not, see http://www.gnu.org/licenses/.
*/
#include <stdio.h>
#include <QFileDialog>
#include <QtGlobal>
@ -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);
}

View File

@ -24,6 +24,7 @@
namespace Ui { class VideoSettingsDialog; }
class VideoSettingsDialog;
class EmuInstance;
class VideoSettingsDialog : public QDialog
{
@ -33,6 +34,8 @@ public:
explicit VideoSettingsDialog(QWidget* parent);
~VideoSettingsDialog();
bool UsesGL();
static VideoSettingsDialog* currentDlg;
static VideoSettingsDialog* openDlg(QWidget* parent)
{
@ -73,6 +76,7 @@ private:
void setEnabled();
Ui::VideoSettingsDialog* ui;
EmuInstance* emuInstance;
QButtonGroup* grp3DRenderer;

View File

@ -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",

View File

@ -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;

File diff suppressed because it is too large Load Diff

View File

@ -20,7 +20,7 @@
#define WINDOW_H
#include "glad/glad.h"
#include "FrontendUtil.h"
#include "ScreenLayout.h"
#include "duckstation/gl/context.h"
#include <QWidget>
@ -34,10 +34,14 @@
#include <QCloseEvent>
#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:
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 @@ public:
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;

View File

@ -39,6 +39,7 @@
#include <QMimeData>
#include <QVector>
#include <QCommandLineParser>
#include <QStandardPaths>
#ifndef _WIN32
#include <QGuiApplication>
#include <QSocketNotifier>
@ -56,206 +57,117 @@
#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;
QString emuDirectory;
MainWindow* mainWindow;
EmuThread* emuThread;
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)
bool createEmuInstance()
{
return std::any_of(extensions.cbegin(), extensions.cend(), [&](const auto& ext) {
return filename.endsWith(ext, cs);
});
int id = -1;
for (int i = 0; i < kMaxEmuInstances; i++)
{
if (!emuInstances[i])
{
id = i;
break;
}
}
if (id == -1)
return false;
auto inst = new EmuInstance(id);
emuInstances[id] = inst;
return true;
}
static bool MimeTypeInList(const QMimeType& mimetype, const QStringList& superTypeNames)
void deleteEmuInstance(int id)
{
return std::any_of(superTypeNames.cbegin(), superTypeNames.cend(), [&](const auto& superTypeName) {
return mimetype.inherits(superTypeName);
});
auto inst = emuInstances[id];
if (!inst) return;
delete inst;
emuInstances[id] = nullptr;
}
void deleteAllEmuInstances()
{
for (int i = 0; i < kMaxEmuInstances; i++)
deleteEmuInstance(i);
}
static bool NdsRomByExtension(const QString& filename)
void pathInit()
{
return FileExtensionInList(filename, NdsRomExtensions);
}
// First, check for the portable directory next to the executable.
QString appdirpath = QCoreApplication::applicationDirPath();
QString portablepath = appdirpath + QDir::separator() + "portable";
static bool GbaRomByExtension(const QString& filename)
{
return FileExtensionInList(filename, GbaRomExtensions);
}
#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
static bool SupportedArchiveByExtension(const QString& filename)
{
return FileExtensionInList(filename, ArchiveExtensions);
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
}
}
static bool NdsRomByMimetype(const QMimeType& mimetype)
{
return mimetype.inherits(NdsRomMimeType);
}
static bool GbaRomByMimetype(const QMimeType& mimetype)
{
return mimetype.inherits(GbaRomMimeType);
}
static bool SupportedArchiveByMimetype(const QMimeType& mimetype)
{
return MimeTypeInList(mimetype, ArchiveMimeTypes);
}
static bool ZstdNdsRomByExtension(const QString& filename)
{
return filename.endsWith(".zst", Qt::CaseInsensitive) &&
NdsRomByExtension(filename.left(filename.size() - 4));
}
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)
{
if (ZstdNdsRomByExtension(filename) || ZstdGbaRomByExtension(filename))
return true;
if (NdsRomByExtension(filename) || GbaRomByExtension(filename) || SupportedArchiveByExtension(filename))
return true;
const auto matchmode = insideArchive ? QMimeDatabase::MatchExtension : QMimeDatabase::MatchDefault;
const QMimeType mimetype = QMimeDatabase().mimeTypeForFile(filename, matchmode);
return NdsRomByMimetype(mimetype) || GbaRomByMimetype(mimetype) || SupportedArchiveByMimetype(mimetype);
}
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<QFileOpenEvent*>(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<int>(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();
LocalMP::Init();
Net::Init();
mainWindow = new MainWindow();
if (options->fullscreen)
ToggleFullscreen(mainWindow);
createEmuInstance();
emuThread = new EmuThread();
emuThread->start();
emuThread->emuPause();
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<QString>& romPath, const std::optional<QString>& romArchivePath) -> QStringList
{
if (!romPath.has_value())
return {};
MainWindow* win = emuInstances[0]->getMainWindow();
bool memberSyntaxUsed = false;
const auto prepareRomPath = [&](const std::optional<QString> &romPath,
const std::optional<QString> &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;
}

View File

@ -31,9 +31,10 @@
#include <QScreen>
#include <QCloseEvent>
#include "EmuInstance.h"
#include "Window.h"
#include "EmuThread.h"
#include "FrontendUtil.h"
#include "ScreenLayout.h"
class MelonApplication : public QApplication
{
@ -45,5 +46,10 @@ public:
};
extern QString* systemThemeName;
extern QString emuDirectory;
bool createEmuInstance();
void deleteEmuInstance(int id);
void deleteAllEmuInstances();
#endif // MAIN_H

View File

@ -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

View File

@ -0,0 +1,64 @@
#ifndef TOML11_COLOR_HPP
#define TOML11_COLOR_HPP
#include <cstdint>
#include <ostream>
#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

View File

@ -0,0 +1,306 @@
// Copyright Toru Niina 2017.
// Distributed under the MIT License.
#ifndef TOML11_COMBINATOR_HPP
#define TOML11_COMBINATOR_HPP
#include <cassert>
#include <cctype>
#include <cstdio>
#include <array>
#include <iomanip>
#include <iterator>
#include <limits>
#include <type_traits>
#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<unsigned char const*>(std::addressof(c))))
{
return std::string(1, c);
}
else
{
std::array<char, 5> buf;
buf.fill('\0');
const auto r = std::snprintf(
buf.data(), buf.size(), "0x%02x", static_cast<int>(c) & 0xFF);
(void) r; // Unused variable warning
assert(r == static_cast<int>(buf.size()) - 1);
return std::string(buf.data());
}
}
template<char C>
struct character
{
static constexpr char target = C;
static result<region, none_t>
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<char C>
constexpr char character<C>::target;
// closed interval [Low, Up]. both Low and Up are included.
template<char Low, char Up>
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<region, none_t>
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<char L, char U> constexpr char in_range<L, U>::upper;
template<char L, char U> constexpr char in_range<L, U>::lower;
// keep iterator if `Combinator` matches. otherwise, increment `iter` by 1 char.
// for detecting invalid characters, like control sequences in toml string.
template<typename Combinator>
struct exclude
{
static result<region, none_t>
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<typename Combinator>
struct maybe
{
static result<region, none_t>
invoke(location& loc)
{
const auto rslt = Combinator::invoke(loc);
if(rslt.is_ok())
{
return rslt;
}
return ok(region(loc));
}
};
template<typename ... Ts>
struct sequence;
template<typename Head, typename ... Tail>
struct sequence<Head, Tail...>
{
static result<region, none_t>
invoke(location& loc)
{
const auto first = loc.iter();
auto rslt = Head::invoke(loc);
if(rslt.is_err())
{
loc.reset(first);
return none();
}
return sequence<Tail...>::invoke(loc, std::move(rslt.unwrap()), first);
}
// called from the above function only, recursively.
template<typename Iterator>
static result<region, none_t>
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<Tail...>::invoke(loc, std::move(reg), first);
}
};
template<typename Head>
struct sequence<Head>
{
// would be called from sequence<T ...>::invoke only.
template<typename Iterator>
static result<region, none_t>
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<typename ... Ts>
struct either;
template<typename Head, typename ... Tail>
struct either<Head, Tail...>
{
static result<region, none_t>
invoke(location& loc)
{
const auto rslt = Head::invoke(loc);
if(rslt.is_ok()) {return rslt;}
return either<Tail...>::invoke(loc);
}
};
template<typename Head>
struct either<Head>
{
static result<region, none_t>
invoke(location& loc)
{
return Head::invoke(loc);
}
};
template<typename T, typename N>
struct repeat;
template<std::size_t N> struct exactly{};
template<std::size_t N> struct at_least{};
struct unlimited{};
template<typename T, std::size_t N>
struct repeat<T, exactly<N>>
{
static result<region, none_t>
invoke(location& loc)
{
region retval(loc);
const auto first = loc.iter();
for(std::size_t i=0; i<N; ++i)
{
auto rslt = T::invoke(loc);
if(rslt.is_err())
{
loc.reset(first);
return none();
}
retval += rslt.unwrap();
}
return ok(std::move(retval));
}
};
template<typename T, std::size_t N>
struct repeat<T, at_least<N>>
{
static result<region, none_t>
invoke(location& loc)
{
region retval(loc);
const auto first = loc.iter();
for(std::size_t i=0; i<N; ++i)
{
auto rslt = T::invoke(loc);
if(rslt.is_err())
{
loc.reset(first);
return none();
}
retval += rslt.unwrap();
}
while(true)
{
auto rslt = T::invoke(loc);
if(rslt.is_err())
{
return ok(std::move(retval));
}
retval += rslt.unwrap();
}
}
};
template<typename T>
struct repeat<T, unlimited>
{
static result<region, none_t>
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

View File

@ -0,0 +1,472 @@
// Copyright Toru Niina 2019.
// Distributed under the MIT License.
#ifndef TOML11_COMMENTS_HPP
#define TOML11_COMMENTS_HPP
#include <initializer_list>
#include <iterator>
#include <stdexcept>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
#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<std::string>`
// but bahaves in the opposite way. `preserve_comments` is just the same as
// `std::vector<std::string>` 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<toml::preserve_comments> data =
// toml::parse<toml::preserve_comments>("example.toml");
//
// the interface is almost the same as std::vector<std::string>.
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<std::string>;
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<std::string>& c): comments(c){}
explicit preserve_comments(std::vector<std::string>&& c)
: comments(std::move(c))
{}
preserve_comments& operator=(const std::vector<std::string>& c)
{
comments = c;
return *this;
}
preserve_comments& operator=(std::vector<std::string>&& 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<std::string> x): comments(x) {}
template<typename InputIterator>
preserve_comments(InputIterator first, InputIterator last)
: comments(first, last)
{}
template<typename InputIterator>
void assign(InputIterator first, InputIterator last) {comments.assign(first, last);}
void assign(std::initializer_list<std::string> 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<typename InputIterator>
void insert(iterator p, InputIterator first, InputIterator last)
{
return comments.insert(p, first, last);
}
void insert(iterator p, std::initializer_list<std::string> ini)
{
return comments.insert(p, ini);
}
template<typename ... Ts>
iterator emplace(iterator p, Ts&& ... args)
{
return comments.emplace(p, std::forward<Ts>(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<typename InputIterator>
iterator insert(const_iterator p, InputIterator first, InputIterator last)
{
return comments.insert(p, first, last);
}
iterator insert(const_iterator p, std::initializer_list<std::string> ini)
{
return comments.insert(p, ini);
}
template<typename ... Ts>
iterator emplace(const_iterator p, Ts&& ... args)
{
return comments.emplace(p, std::forward<Ts>(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<typename ... Ts>
void emplace_back(Ts&& ... args) {comments.emplace_back(std::forward<Ts>(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<std::string>&);
friend void swap(std::vector<std::string>&, 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<std::string>& rhs)
{
lhs.comments.swap(rhs);
return;
}
inline void swap(std::vector<std::string>& lhs, preserve_comments& rhs)
{
lhs.swap(rhs.comments);
return;
}
template<typename charT, typename traits>
std::basic_ostream<charT, traits>&
operator<<(std::basic_ostream<charT, traits>& 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<typename T, bool is_const>
struct empty_iterator
{
using value_type = T;
using reference_type = typename std::conditional<is_const, T const&, T&>::type;
using pointer_type = typename std::conditional<is_const, T const*, T*>::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<typename T, bool C>
bool operator==(const empty_iterator<T, C>&, const empty_iterator<T, C>&) noexcept {return true;}
template<typename T, bool C>
bool operator!=(const empty_iterator<T, C>&, const empty_iterator<T, C>&) noexcept {return false;}
template<typename T, bool C>
bool operator< (const empty_iterator<T, C>&, const empty_iterator<T, C>&) noexcept {return false;}
template<typename T, bool C>
bool operator<=(const empty_iterator<T, C>&, const empty_iterator<T, C>&) noexcept {return true;}
template<typename T, bool C>
bool operator> (const empty_iterator<T, C>&, const empty_iterator<T, C>&) noexcept {return false;}
template<typename T, bool C>
bool operator>=(const empty_iterator<T, C>&, const empty_iterator<T, C>&) noexcept {return true;}
template<typename T, bool C>
typename empty_iterator<T, C>::difference_type
operator-(const empty_iterator<T, C>&, const empty_iterator<T, C>&) noexcept {return 0;}
template<typename T, bool C>
empty_iterator<T, C>
operator+(typename empty_iterator<T, C>::difference_type, const empty_iterator<T, C>& rhs) noexcept {return rhs;}
template<typename T, bool C>
empty_iterator<T, C>
operator+(const empty_iterator<T, C>& lhs, typename empty_iterator<T, C>::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<std::string, false>;
using const_iterator = detail::empty_iterator<std::string, true>;
using reverse_iterator = detail::empty_iterator<std::string, false>;
using const_reverse_iterator = detail::empty_iterator<std::string, true>;
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<std::string>&) noexcept {}
explicit discard_comments(std::vector<std::string>&&) noexcept {}
discard_comments& operator=(const std::vector<std::string>&) noexcept {return *this;}
discard_comments& operator=(std::vector<std::string>&&) 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<std::string>) noexcept {}
template<typename InputIterator>
discard_comments(InputIterator, InputIterator) noexcept {}
template<typename InputIterator>
void assign(InputIterator, InputIterator) noexcept {}
void assign(std::initializer_list<std::string>) 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<typename InputIterator>
iterator insert(const_iterator, InputIterator, InputIterator) {return iterator{};}
iterator insert(const_iterator, std::initializer_list<std::string>) {return iterator{};}
template<typename ... Ts>
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<typename ... Ts>
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<typename charT, typename traits>
std::basic_ostream<charT, traits>&
operator<<(std::basic_ostream<charT, traits>& os, const discard_comments&)
{
return os;
}
} // toml11
#endif// TOML11_COMMENTS_HPP

View File

@ -0,0 +1,631 @@
// Copyright Toru Niina 2017.
// Distributed under the MIT License.
#ifndef TOML11_DATETIME_HPP
#define TOML11_DATETIME_HPP
#include <cstdint>
#include <cstdlib>
#include <ctime>
#include <array>
#include <chrono>
#include <iomanip>
#include <ostream>
#include <tuple>
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<std::int16_t>(y)),
month(static_cast<std::uint8_t>(m)),
day (static_cast<std::uint8_t>(d))
{}
explicit local_date(const std::tm& t)
: year (static_cast<std::int16_t>(t.tm_year + 1900)),
month(static_cast<std::uint8_t>(t.tm_mon)),
day (static_cast<std::uint8_t>(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<int>(this->day);
t.tm_mon = static_cast<int>(this->month);
t.tm_year = static_cast<int>(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<typename charT, typename traits>
std::basic_ostream<charT, traits>&
operator<<(std::basic_ostream<charT, traits>& os, const local_date& date)
{
os << std::setfill('0') << std::setw(4) << static_cast<int>(date.year ) << '-';
os << std::setfill('0') << std::setw(2) << static_cast<int>(date.month) + 1 << '-';
os << std::setfill('0') << std::setw(2) << static_cast<int>(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<std::uint8_t>(h)),
minute(static_cast<std::uint8_t>(m)),
second(static_cast<std::uint8_t>(s)),
millisecond(static_cast<std::uint16_t>(ms)),
microsecond(static_cast<std::uint16_t>(us)),
nanosecond (static_cast<std::uint16_t>(ns))
{}
explicit local_time(const std::tm& t)
: hour (static_cast<std::uint8_t>(t.tm_hour)),
minute(static_cast<std::uint8_t>(t.tm_min)),
second(static_cast<std::uint8_t>(t.tm_sec)),
millisecond(0), microsecond(0), nanosecond(0)
{}
template<typename Rep, typename Period>
explicit local_time(const std::chrono::duration<Rep, Period>& t)
{
const auto h = std::chrono::duration_cast<std::chrono::hours>(t);
this->hour = static_cast<std::uint8_t>(h.count());
const auto t2 = t - h;
const auto m = std::chrono::duration_cast<std::chrono::minutes>(t2);
this->minute = static_cast<std::uint8_t>(m.count());
const auto t3 = t2 - m;
const auto s = std::chrono::duration_cast<std::chrono::seconds>(t3);
this->second = static_cast<std::uint8_t>(s.count());
const auto t4 = t3 - s;
const auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(t4);
this->millisecond = static_cast<std::uint16_t>(ms.count());
const auto t5 = t4 - ms;
const auto us = std::chrono::duration_cast<std::chrono::microseconds>(t5);
this->microsecond = static_cast<std::uint16_t>(us.count());
const auto t6 = t5 - us;
const auto ns = std::chrono::duration_cast<std::chrono::nanoseconds>(t6);
this->nanosecond = static_cast<std::uint16_t>(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<typename charT, typename traits>
std::basic_ostream<charT, traits>&
operator<<(std::basic_ostream<charT, traits>& os, const local_time& time)
{
os << std::setfill('0') << std::setw(2) << static_cast<int>(time.hour ) << ':';
os << std::setfill('0') << std::setw(2) << static_cast<int>(time.minute) << ':';
os << std::setfill('0') << std::setw(2) << static_cast<int>(time.second);
if(time.millisecond != 0 || time.microsecond != 0 || time.nanosecond != 0)
{
os << '.';
os << std::setfill('0') << std::setw(3) << static_cast<int>(time.millisecond);
if(time.microsecond != 0 || time.nanosecond != 0)
{
os << std::setfill('0') << std::setw(3) << static_cast<int>(time.microsecond);
if(time.nanosecond != 0)
{
os << std::setfill('0') << std::setw(3) << static_cast<int>(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<std::int8_t>(h)),
minute(static_cast<std::int8_t>(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<typename charT, typename traits>
std::basic_ostream<charT, traits>&
operator<<(std::basic_ostream<charT, traits>& os, const time_offset& offset)
{
if(offset.hour == 0 && offset.minute == 0)
{
os << 'Z';
return os;
}
int minute = static_cast<int>(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(&ltime));
this->time.millisecond = static_cast<std::uint16_t>(
std::chrono::duration_cast<std::chrono::milliseconds>(t_diff).count());
this->time.microsecond = static_cast<std::uint16_t>(
std::chrono::duration_cast<std::chrono::microseconds>(t_diff).count());
this->time.nanosecond = static_cast<std::uint16_t>(
std::chrono::duration_cast<std::chrono::nanoseconds >(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<int>(this->time.second);
t.tm_min = static_cast<int>(this->time.minute);
t.tm_hour = static_cast<int>(this->time.hour);
t.tm_mday = static_cast<int>(this->date.day);
t.tm_mon = static_cast<int>(this->date.month);
t.tm_year = static_cast<int>(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<internal_duration>(
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<typename charT, typename traits>
std::basic_ostream<charT, traits>&
operator<<(std::basic_ostream<charT, traits>& 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<int>(this->time.second);
t.tm_min = static_cast<int>(this->time.minute);
t.tm_hour = static_cast<int>(this->time.hour);
t.tm_mday = static_cast<int>(this->date.day);
t.tm_mon = static_cast<int>(this->date.month);
t.tm_year = static_cast<int>(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<internal_duration>(
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<char, 6> 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<typename charT, typename traits>
std::basic_ostream<charT, traits>&
operator<<(std::basic_ostream<charT, traits>& os, const offset_datetime& dt)
{
os << dt.date << 'T' << dt.time << dt.offset;
return os;
}
}//toml
#endif// TOML11_DATETIME

View File

@ -0,0 +1,65 @@
// Copyright Toru Niina 2017.
// Distributed under the MIT License.
#ifndef TOML11_EXCEPTION_HPP
#define TOML11_EXCEPTION_HPP
#include <stdexcept>
#include <string>
#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

View File

@ -0,0 +1,19 @@
// Copyright Toru Niina 2019.
// Distributed under the MIT License.
#ifndef TOML11_FROM_HPP
#define TOML11_FROM_HPP
namespace toml
{
template<typename T>
struct from;
// {
// static T from_toml(const toml::value& v)
// {
// // User-defined conversions ...
// }
// };
} // toml
#endif // TOML11_FROM_HPP

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,19 @@
// Copyright Toru Niina 2019.
// Distributed under the MIT License.
#ifndef TOML11_INTO_HPP
#define TOML11_INTO_HPP
namespace toml
{
template<typename T>
struct into;
// {
// static toml::value into_toml(const T& user_defined_type)
// {
// // User-defined conversions ...
// }
// };
} // toml
#endif // TOML11_INTO_HPP

View File

@ -0,0 +1,293 @@
// Copyright Toru Niina 2017.
// Distributed under the MIT License.
#ifndef TOML11_LEXER_HPP
#define TOML11_LEXER_HPP
#include <istream>
#include <sstream>
#include <stdexcept>
#include <fstream>
#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<' '>, character<'\t'>>;
using lex_ws = repeat<lex_wschar, at_least<1>>;
using lex_newline = either<character<'\n'>,
sequence<character<'\r'>, character<'\n'>>>;
using lex_lower = in_range<'a', 'z'>;
using lex_upper = in_range<'A', 'Z'>;
using lex_alpha = either<lex_lower, lex_upper>;
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<lex_digit, in_range<'A', 'F'>, in_range<'a', 'f'>>;
using lex_hex_prefix = sequence<character<'0'>, character<'x'>>;
using lex_oct_prefix = sequence<character<'0'>, character<'o'>>;
using lex_bin_prefix = sequence<character<'0'>, character<'b'>>;
using lex_underscore = character<'_'>;
using lex_plus = character<'+'>;
using lex_minus = character<'-'>;
using lex_sign = either<lex_plus, lex_minus>;
// digit | nonzero 1*(digit | _ digit)
using lex_unsigned_dec_int = either<sequence<lex_nonzero, repeat<
either<lex_digit, sequence<lex_underscore, lex_digit>>, at_least<1>>>,
lex_digit>;
// (+|-)? unsigned_dec_int
using lex_dec_int = sequence<maybe<lex_sign>, lex_unsigned_dec_int>;
// hex_prefix hex_dig *(hex_dig | _ hex_dig)
using lex_hex_int = sequence<lex_hex_prefix, sequence<lex_hex_dig, repeat<
either<lex_hex_dig, sequence<lex_underscore, lex_hex_dig>>, unlimited>>>;
// oct_prefix oct_dig *(oct_dig | _ oct_dig)
using lex_oct_int = sequence<lex_oct_prefix, sequence<lex_oct_dig, repeat<
either<lex_oct_dig, sequence<lex_underscore, lex_oct_dig>>, unlimited>>>;
// bin_prefix bin_dig *(bin_dig | _ bin_dig)
using lex_bin_int = sequence<lex_bin_prefix, sequence<lex_bin_dig, repeat<
either<lex_bin_dig, sequence<lex_underscore, lex_bin_dig>>, unlimited>>>;
// (dec_int | hex_int | oct_int | bin_int)
using lex_integer = either<lex_bin_int, lex_oct_int, lex_hex_int, lex_dec_int>;
// ===========================================================================
using lex_inf = sequence<character<'i'>, character<'n'>, character<'f'>>;
using lex_nan = sequence<character<'n'>, character<'a'>, character<'n'>>;
using lex_special_float = sequence<maybe<lex_sign>, either<lex_inf, lex_nan>>;
using lex_zero_prefixable_int = sequence<lex_digit, repeat<either<lex_digit,
sequence<lex_underscore, lex_digit>>, unlimited>>;
using lex_fractional_part = sequence<character<'.'>, lex_zero_prefixable_int>;
using lex_exponent_part = sequence<either<character<'e'>, character<'E'>>,
maybe<lex_sign>, lex_zero_prefixable_int>;
using lex_float = either<lex_special_float,
sequence<lex_dec_int, either<lex_exponent_part,
sequence<lex_fractional_part, maybe<lex_exponent_part>>>>>;
// ===========================================================================
using lex_true = sequence<character<'t'>, character<'r'>,
character<'u'>, character<'e'>>;
using lex_false = sequence<character<'f'>, character<'a'>, character<'l'>,
character<'s'>, character<'e'>>;
using lex_boolean = either<lex_true, lex_false>;
// ===========================================================================
using lex_date_fullyear = repeat<lex_digit, exactly<4>>;
using lex_date_month = repeat<lex_digit, exactly<2>>;
using lex_date_mday = repeat<lex_digit, exactly<2>>;
using lex_time_delim = either<character<'T'>, character<'t'>, character<' '>>;
using lex_time_hour = repeat<lex_digit, exactly<2>>;
using lex_time_minute = repeat<lex_digit, exactly<2>>;
using lex_time_second = repeat<lex_digit, exactly<2>>;
using lex_time_secfrac = sequence<character<'.'>,
repeat<lex_digit, at_least<1>>>;
using lex_time_numoffset = sequence<either<character<'+'>, character<'-'>>,
sequence<lex_time_hour, character<':'>,
lex_time_minute>>;
using lex_time_offset = either<character<'Z'>, character<'z'>,
lex_time_numoffset>;
using lex_partial_time = sequence<lex_time_hour, character<':'>,
lex_time_minute, character<':'>,
lex_time_second, maybe<lex_time_secfrac>>;
using lex_full_date = sequence<lex_date_fullyear, character<'-'>,
lex_date_month, character<'-'>,
lex_date_mday>;
using lex_full_time = sequence<lex_partial_time, lex_time_offset>;
using lex_offset_date_time = sequence<lex_full_date, lex_time_delim, lex_full_time>;
using lex_local_date_time = sequence<lex_full_date, lex_time_delim, lex_partial_time>;
using lex_local_date = lex_full_date;
using lex_local_time = lex_partial_time;
// ===========================================================================
using lex_quotation_mark = character<'"'>;
using lex_basic_unescaped = exclude<either<in_range<0x00, 0x08>, // 0x09 (tab) is allowed
in_range<0x0A, 0x1F>,
character<0x22>, character<0x5C>,
character<0x7F>>>;
using lex_escape = character<'\\'>;
using lex_escape_unicode_short = sequence<character<'u'>,
repeat<lex_hex_dig, exactly<4>>>;
using lex_escape_unicode_long = sequence<character<'U'>,
repeat<lex_hex_dig, exactly<8>>>;
using lex_escape_seq_char = either<character<'"'>, character<'\\'>,
character<'b'>, character<'f'>,
character<'n'>, character<'r'>,
character<'t'>,
lex_escape_unicode_short,
lex_escape_unicode_long
>;
using lex_escaped = sequence<lex_escape, lex_escape_seq_char>;
using lex_basic_char = either<lex_basic_unescaped, lex_escaped>;
using lex_basic_string = sequence<lex_quotation_mark,
repeat<lex_basic_char, unlimited>,
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<lex_quotation_mark, exactly<3>>;
using lex_ml_basic_string_open = lex_ml_basic_string_delim;
using lex_ml_basic_string_close = sequence<
repeat<lex_quotation_mark, exactly<3>>,
maybe<lex_quotation_mark>, maybe<lex_quotation_mark>
>;
using lex_ml_basic_unescaped = exclude<either<in_range<0x00, 0x08>, // 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_ws>, lex_newline,
repeat<either<lex_ws, lex_newline>, unlimited>>;
using lex_ml_basic_char = either<lex_ml_basic_unescaped, lex_escaped>;
using lex_ml_basic_body = repeat<either<lex_ml_basic_char, lex_newline,
lex_ml_basic_escaped_newline>,
unlimited>;
using lex_ml_basic_string = sequence<lex_ml_basic_string_open,
lex_ml_basic_body,
lex_ml_basic_string_close>;
using lex_literal_char = exclude<either<in_range<0x00, 0x08>, in_range<0x0A, 0x1F>,
character<0x7F>, character<0x27>>>;
using lex_apostrophe = character<'\''>;
using lex_literal_string = sequence<lex_apostrophe,
repeat<lex_literal_char, unlimited>,
lex_apostrophe>;
// the same reason as above.
using lex_ml_literal_string_delim = repeat<lex_apostrophe, exactly<3>>;
using lex_ml_literal_string_open = lex_ml_literal_string_delim;
using lex_ml_literal_string_close = sequence<
repeat<lex_apostrophe, exactly<3>>,
maybe<lex_apostrophe>, maybe<lex_apostrophe>
>;
using lex_ml_literal_char = exclude<either<in_range<0x00, 0x08>,
in_range<0x0A, 0x1F>,
character<0x7F>,
lex_ml_literal_string_delim>>;
using lex_ml_literal_body = repeat<either<lex_ml_literal_char, lex_newline>,
unlimited>;
using lex_ml_literal_string = sequence<lex_ml_literal_string_open,
lex_ml_literal_body,
lex_ml_literal_string_close>;
using lex_string = either<lex_ml_basic_string, lex_basic_string,
lex_ml_literal_string, lex_literal_string>;
// ===========================================================================
using lex_dot_sep = sequence<maybe<lex_ws>, character<'.'>, maybe<lex_ws>>;
using lex_unquoted_key = repeat<either<lex_alpha, lex_digit,
character<'-'>, character<'_'>>,
at_least<1>>;
using lex_quoted_key = either<lex_basic_string, lex_literal_string>;
using lex_simple_key = either<lex_unquoted_key, lex_quoted_key>;
using lex_dotted_key = sequence<lex_simple_key,
repeat<sequence<lex_dot_sep, lex_simple_key>,
at_least<1>
>
>;
using lex_key = either<lex_dotted_key, lex_simple_key>;
using lex_keyval_sep = sequence<maybe<lex_ws>,
character<'='>,
maybe<lex_ws>>;
using lex_std_table_open = character<'['>;
using lex_std_table_close = character<']'>;
using lex_std_table = sequence<lex_std_table_open,
maybe<lex_ws>,
lex_key,
maybe<lex_ws>,
lex_std_table_close>;
using lex_array_table_open = sequence<lex_std_table_open, lex_std_table_open>;
using lex_array_table_close = sequence<lex_std_table_close, lex_std_table_close>;
using lex_array_table = sequence<lex_array_table_open,
maybe<lex_ws>,
lex_key,
maybe<lex_ws>,
lex_array_table_close>;
using lex_utf8_1byte = in_range<0x00, 0x7F>;
using lex_utf8_2byte = sequence<
in_range<static_cast<char>(0xC2), static_cast<char>(0xDF)>,
in_range<static_cast<char>(0x80), static_cast<char>(0xBF)>
>;
using lex_utf8_3byte = sequence<either<
sequence<character<static_cast<char>(0xE0)>, in_range<static_cast<char>(0xA0), static_cast<char>(0xBF)>>,
sequence<in_range <static_cast<char>(0xE1), static_cast<char>(0xEC)>, in_range<static_cast<char>(0x80), static_cast<char>(0xBF)>>,
sequence<character<static_cast<char>(0xED)>, in_range<static_cast<char>(0x80), static_cast<char>(0x9F)>>,
sequence<in_range <static_cast<char>(0xEE), static_cast<char>(0xEF)>, in_range<static_cast<char>(0x80), static_cast<char>(0xBF)>>
>, in_range<static_cast<char>(0x80), static_cast<char>(0xBF)>>;
using lex_utf8_4byte = sequence<either<
sequence<character<static_cast<char>(0xF0)>, in_range<static_cast<char>(0x90), static_cast<char>(0xBF)>>,
sequence<in_range <static_cast<char>(0xF1), static_cast<char>(0xF3)>, in_range<static_cast<char>(0x80), static_cast<char>(0xBF)>>,
sequence<character<static_cast<char>(0xF4)>, in_range<static_cast<char>(0x80), static_cast<char>(0x8F)>>
>, in_range<static_cast<char>(0x80), static_cast<char>(0xBF)>,
in_range<static_cast<char>(0x80), static_cast<char>(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<character<0x09>, in_range<0x20, 0x7E>>;
using lex_comment = sequence<lex_comment_start_symbol, repeat<either<
lex_non_eol_ascii, lex_utf8_2byte, lex_utf8_3byte, lex_utf8_4byte>, unlimited>>;
} // detail
} // toml
#endif // TOML_LEXER_HPP

View File

@ -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<TOML11_DEFAULT_COMMENT_STRATEGY, std::unordered_map, std::vector>
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::sequence<
::toml::detail::maybe<::toml::detail::lex_ws>,
::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<value_type>(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<value_type>(loc))
{
return data.unwrap();
}
else // none of them.
{
throw ::toml::syntax_error(data.unwrap_err(), source_location(loc));
}
}
inline ::toml::basic_value<TOML11_DEFAULT_COMMENT_STRATEGY, std::unordered_map, std::vector>
operator"" _toml(const char* str, std::size_t len)
{
::toml::detail::location loc(
std::string("TOML literal encoded in a C++ code"),
std::vector<char>(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<TOML11_DEFAULT_COMMENT_STRATEGY, std::unordered_map, std::vector>
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<char>(reinterpret_cast<const char*>(str),
reinterpret_cast<const char*>(str) + len));
return literal_internal_impl(std::move(loc));
}
#endif
} // toml_literals
} // literals
} // toml
#endif//TOML11_LITERAL_HPP

View File

@ -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<foo::Foo>(file, "foo");`
//
#define TOML11_FIND_MEMBER_VARIABLE_FROM_VALUE(VAR_NAME)\
obj.VAR_NAME = toml::find<decltype(obj.VAR_NAME)>(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<NAME> \
{ \
template<typename C, template<typename ...> class T, \
template<typename ...> class A> \
static NAME from_toml(const basic_value<C, T, A>& v) \
{ \
NAME obj; \
TOML11_FOR_EACH_VA_ARGS(TOML11_FIND_MEMBER_VARIABLE_FROM_VALUE, __VA_ARGS__) \
return obj; \
} \
}; \
template<> \
struct into<NAME> \
{ \
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

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More